第1章 算法与程序

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

本人精心整理的文档,文档来自网络
本人仅收藏整理
如有错误
还请自己查证!
第8章 排序及基本算法
8.1 基本知识点
1. 排序的基本概念
排序的确切定义可以描述为:
设(R1
R2 ... Rn)是某文件的n个记录
其相应的关键字为(K1
K2 ... Kn)
排序(sort)是这样一种操作
它确定文件中n个记录的一种排列(Rj1
Rj2 ... Rjn)
使得其相应关键字满足递增(即不减)关系Kj1≤Kj2≤...≤Kjn或递减(即不增)关系Kj1≥Kj2≥...≥Kjn
若关键字满足递增(即不减)关系时
称作按关键字升序排序(ascending sort);若关键字满足递减(即不增)关系时
称作按关键字降序排序(descending sort)

根据排序过程中待排序文件存放的位置不同
可以把排序分为内部和外部排序两大类
内部排序适用于记录个数不很多的较小待排序文件的排序;外部排序则适用于记录个数太多不能一次全部放入内存的较大待排序文件的排序
按排序中所用策略的不同
内部排序通常可以分为插入排序、选择排序、交换排序、归并排序和分配排序这五类
每一类中不同的排序算法都是基于同一策略的不同方法
外部排序多是采用多路归并方法进行排序

2. 插入排序
插入排序的基本方法是
每次将一个待排序的记录Ri
按其关键字Ki的大小插入到前面已经排好序的部分文件中的适当位置
直到全部记录插完整个文件已排好序为止
主要的插入排序方法有直接插入排序、希尔排序、二分法插入排序、二路插入排序和共享栈插入排序等

3. 交换排序
交换排序的基本方法是
两两比较待排序记录的关键字值
并交换那些不满足顺序要求的记录对
直到全部待排序记录都已满足顺序要求时为止
常用的交换排序方法有冒泡排序和快速排序

4. 选择排序
选择排序的基本方法是
每次从待排序文件的各个记录中
依次选出关键字值最小的记录放在已排好序的序列中
直到选完为止
常用的选择排序方法有直接选择排序、树型选择排序和堆排序

5. 归并择排序
归并排序(merge sort)的基本思想是
将一些已排序的子文件进行合并而得到一个完整的有序文件
归并时只要比较各子文件的第一个记录的关键字
其最小者就是全局最小者;取出它后继续比较各子文件的第一个记录的关键码
就可以得到全局的次小者......如此下去就可完成排序任务

6. 基数排序
基数排序(radix sort)不用进行关键字值的大小比较
而是借助于多关键字排序的思想
通过对待排序记录按单逻辑关键字进行分配和收集来实现的一种排序方法

基数排序就是

按照LSD法
对待排序文件中各记录用单逻辑关键字进行分配和收集的一种排序方法

7. 各种内部排序方法的比较和选择
⑴ 从时间性能上讲
较快的是O(nlog2n)的二路归并排序、堆排序和快速排序
其次是希尔排序
较慢的是O(n2)的直接选择排序、冒泡排序、直接插入排序等简单排序方法
其中直接插入排序最为简单

⑵ 当n较大时
O(d(n+rd))的链式基数排序的时间复杂度可以写成O(d?n)
这是因为基数rd相对于n而言可以忽略不计(由加法准则)
当n>2d时
链式基数排序的时间性能比二路归并排序、堆排序和快速排序还要好
是时间性能最好的排序方法

⑶ 从空间性能上讲
各种排序方法都比较好;链式基数排序和希尔排序的空间性能为问题规模n的线性函数
其它排序方法的空间性能与问题规模无关是个常数

⑷ 从稳定性上看
链式基数排序、二路归并排序和除直接选择排序之外的其它简单排序方法都是稳定的;而直接选择排序和时间性能较好的堆排序、快速排序和希尔排序方法是不稳定的

8. 外部排序
如果待排序文件大到不能一次全部装入内存而有一部分必须放在外存储器上时
相应的排序方法称之为外部排序(external sorting)

8.2 习题解答
1. 什么是排序?什么是内排序?什么是外排序?
解答:所谓排序
就是要整理文件中的记录
使之按关键字递增(或递减)次序排列起来
其确切定义如下:输入n个记录R1
R2
...
Rn
其相应的关键字分别为K1
K2
...
Kn
输出Ril
Ri2
...
Rin
使得Ki1≤Ki2≤...≤Kin
(或Ki1≥Ki2≥...≥Kin)

在排序过程中
若整个文件都是放在内存中处理
排序时不涉及数据的内、外存交换
则称之为内部排序(简称内排序);反之
若排序过程中要进行数据的内、外存交换
则称之为外部排序

2. 排序方法稳定性的含意是什么?试各举三种稳定的和不稳定的排序方法

解答:在待排序的文件中
若存在多个关键字相同的记录
经过排序后这些具有相同关键字的记录之间的相对次序保持不变
该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生变化
则称这种排序方法是不稳定的

稳定排序有:直接插入排序、冒泡排序、归并排序;
不稳定排序有:希尔排序、快速排序、堆排序

3. 给定排序码序列为(17
8
21
35
32
15

25
12
23)
试分别写出使用以下排序方法进行排序的过程
并说明关键字的比较次数

⑴ 直接插入排序 ⑵ 希尔排序(增量为5
2
1) ⑶ 二路插入排序
⑷ 折半插入排序 ⑸ 共享栈插入排序 ⑹ 冒泡排序
⑺ 快速

排序 ⑻ 直接选择排序 ⑼ 树型选择排序
⑽ 堆排序 ⑾ 二路归并排序 ⑿ 基数排序
解答:
⑴ 直接插入排序的过程如下:
(8
17)21
35
32
15

25
12
23
(8
17
21)35
32
15

25
12
23
(8
17
21
35)32
15

25
12
23
(8
17
21
32
35)15

25
12
23
(8
15
17
21
32
35)
25
12
23
(8
15
17
21

32
35)25
12
23
(8
15
17
21

25
32
35)12
23
(8
12
15
17
21

25
32
35)23
(8
12
15
17
21

23
25
32
35)
⑵ 希尔排序(增量为5
2
1)的过程如下:














⑶ 二路插入排序的过程如下:
17)21
35
32
15

25
12
23
(8
17
21)35
32
15

25
12
23
(8
17
21
35)32
15

25
12
23
(8
17
21
32
35)15

25
12
23
(8
15
17
21
32
35)
25
12
23
(8
15
17
21

32
35)25
12
23
(8
15
17
21

25
32
35)12
23
(8
12
15
17
21

25
32
35)23
(8
12
15
17
21

23
25
32
35)(8
⑷ 折半插入排序的过程同⑶
⑸ 共享栈插入排序
(8
17)21
35
32
15

25
12
23
(8
17
21)35
32
15

25
12
23
(8
17
21
23)32
15

25
12
35
(8
17
21
23
32)15

25
12
35
⑹ 冒泡排序的过程如下:
17
8
21
35
32
15

25
12
23
8
17
12
21
35
32
15

25
23
8
12
17
15
21
35
32

23
25
8
12
15
17
21

35
32
23
25
8
12
15
17
21

23
35
32
25
8
12
15
17
21

23
25
35
32
8
12
15
17
21

23
25
32
35
⑺ 快速排序的过程如下:
12
8
15
17
32
35

25
21
23
8
12
15
17
23
21

25
32
35
8
12
15
17
21

23
25
32
35
⑻ 直接选择排序的过程如下:
8
17
21
35
32
15

25
12
23
8
12
21
35
32
15

25
17
23
8
12
15
35
32
21

25
17
23
8
12
15
17
32
21

25
35
23
8
12
15
17
21
32

25
35
23
8
12
15
17
21

32
25
35
23
8
12
15
17
21

23
25
35
32
8
12
15
17
21

23
25
35
32
8
12
15
17
21

23
25
32
35
⑼ 树型选择排序的过程如下:







































所以
最后输出的序列为8
12
15
17
21

23
25
32
35
⑽ 堆排序的过程如下:



















 

 

所以
最后输出的序列为8
12
15
17

21
23
25
32
35
⑾ 二路归并排序的过程如下:
[17 8] [ 21 35] [32 15] [ 25] [12 23]
[8 17 21 35] [15 25 32] [12 23]
[8 15 17 21 25 32 35] [12 23]
[8 12 15 17 21 23 25 32 35]
⑿ 基数排序的过程如下:





个位排序得数列:21 32 12 23 35 15 25 17 8








十位排序后得最终数列:8
12
15
17
21

23
25
32
35
4. 设有5000个无序的元素希望用最快的速度挑选出其中最大的10个元素
使用下列排序方法中的哪一种方法最好?为什么?
⑴ 希尔排序 ⑵ 冒泡排序 ⑶ 快速排序
⑷ 堆排序 ⑸ 二路归并排序 ⑹ 基数排序
解答:题中所说的几种排序方法
其排序速度都很快
但快速排序
归并排序
基数排序和SHELL排序都是在排序结束后才能确定数据元素的全部序列
而排序过程中无法知道部分连续位置上的最终元素
堆排序则是每次输出一个堆顶元素
然后对堆进行再调整
保证堆顶元素总是当前剩下元素的最大或最小
从而可知
欲在一个大量数据的文件中
如含有5000个元素的记录文件中选取前10个最大的元素
可采用堆排序

5. 设已排序元素存放于单链表中
头指针为head;试写出插入一个新元素使链表仍然有序的实现算法

解答:
[解题思路]设单链表结点的类型为linklist
其中含next域
key域和其他域
并且从小到大排序
首先是为待插入结点开辟空间并生成待插入结点
然后检索待插入位置
最后把待插入结点插入到相应位置

[程序实现]
void insert(linklist *head,keytype x)
{linklist *s,*q,*p;
s=(linklist*)malloc(sizeof(linklist);
s->key=x;
s->next=NULL; /*生成待插入结点*/
if(head==NULL)
head=s;
else
{p=head;
q=NULL;
while(p!=NULL&&(s->key>p->key)) /*检索待插入位置*/
{q=p;
p=p->next;
}
if(q==NULL) /*插入位置是头结点*/
{s->next=head;
head=s;
}
else
if(p==NULL) /*插入位置是末结点*/
q->next=s;
else的 /*插入位置是中间结点*/
{s->next=q->next;
q->next=s;
}
}
} /*End of insert*/
6. 采用插入排序方法
将一个无序链表排列成为一个降序链表

解答:
[解答思路]设单链表结点的类型为linklist,含两个域:一个为num域
表示待排序元素值;另一个为next域
用于链接链表
首先构造一条新链表
末结点为最小值
然后根据插入排序

算法思想把待排序结点一一添加到新链表中
最后删除头结点和末结点(即排序前构造的结点)

[程序实现]
insertsort(linklist *head)
{linklist *p,*q,*s;
if(head!=NULL) /*非空表*/
{s=head;
head=(linklist *)malloc(sizeof(linklist)); /*生成新链表*/
head->next=(linklist *)malloc(sizeof(linklist));
head->next->num=-maxint; /*设置临时的辅助元素-maxint*/
head->next->next=NULL;
while(s!=NULL)
{p=head;
q=p->next;
while(!(p->num>=s->num)&&(s->num>=q->num))
{p=q;
q=q->next;
}
p->next=s;
s=s->next;
p->next->next=q; /*链接*/
}
p=head;
head=head->next;
free(p); /*删除头结点*/
p=head;
q=p->next;
while(q->next!=NULL)
{p=q;
q=q->next;
}
p->next=null;
free(q); /*删除末结点*/
}
}/* End of Insertsort*/
7. 在冒泡排序过程中
什么情况下排序码会朝着与最终位置相反的方向移动?试举例说明

解答:例如(5
4
2
1)
第一趟冒泡排序后为(4
2
1
5)
关键字4的位置被移动到首位
朝着于最终排序相反的方向移动

8. 试修改冒泡排序算法
在正反两个方向交替扫描
即第一趟把关键字值最小的元素移到最前面
第二趟把关键字值最大的元素移到最后面
如此反复交替进行直到次序全部排定

解答:
[解题思路]可通过设置一个标志位进行区分的方式来进行交替扫描

[程序实现]
alterbubblesort(rectype R[]) /*交替扫描法起泡排序*/
{int i,j,temp,flag; /*设置扫描标志flag*/
flag=true;
i=0;
while(flag) /*开始扫描*/
{flag=flase;
for(j=n-1;j>i;j--)
if(R[j].key{flag=true;
temp=R[j];
R[j]=R[j-1];
R[j-1]=temp;
}
for(j=i;jif(R[j].key>R[j+1].key)
{flag=true;
temp=R[j];
R[j]=R[j+1];
R[j+1]=temp;
}
i++; /*往右扫描*/
}
} /* End of alterbubblesort*/
9. 对于n个排序码进行快速排序时
所需的比较次数与排序码的初始序列有关
当n=7时
回答下列问题:
⑴ 在最好情况下需要进行多少次比较?请说明理由;
⑵ 给出在最好情况下的排序码初始序列的一个实例;
⑶ 在最坏情况下需要进行多少次比较?请说明理由;
⑷ 给出在最坏情况下的排序码初始序列的一个实例

解答:
⑴ 在最好情况下
由于快速排序是一个划分子区间的排序
每次划分时最好能得到两个长度相等的子文件
设文件的长度为
显然
第一遍划分得到两个长度为的子文件
第二遍划分得到4个

长度均为的子文件
以次类推
总共进行遍划分
各文件的长度均为1
此时排序结束

由于n=7
k=3
在最好情况下
第一遍经过6次
可找到一个其基准是正中间的元素
第二遍分别对两个子文件(此时长度为3)进行排序
各需要两次
这样就可将整个文件排序完毕
从而知n=7的最好情况下需进行10次比较

⑵ 在最好情况下的排序码初始序列的一个实例为:4
7
5
6
3
1
2
⑶ 在最坏情况下
若每次划分用的基准
其关键字值是当前记录中最大(或最小)
那么每次划分只能得到左子文件(或右子文件)
子文件长度只比原文件减少1个
因此
若初始排序的记录是按关键字递增或递减
而所得的结果须为递减或递增排序的
此时快速排序就退化为与冒泡排序相似
而且时间复杂度为O(n2)
此时反而不快了
对于n=7的记录文件
显然最坏情况下的比较次数为21

⑷ 在最坏情况下的排序码初始序列的一个实例为:7
6
5
4
3
2
1
10. 在实现快速排序算法时
可先检查位于区间两端和中间点的关键字值
取三者之中关键字值居中的一个作为基准
试编写基于这种思想的快速排序算法
并证明对已排好序的排序码序列
该算法的时间开销不会象快速排序算法那样下降到O(n2)
而是O(nlog2n)

解答:
int Partition(int a[], int left, int right)
{int mid,pivotpos,pivot;
mid = (left + right) / 2;
if(a[left]>a[mid])
{if(a[left] > a[right])
if(a[mid] > a[right])
swap(a[mid],a[left]);
else
swap(a[right],a[left]);
}
else
{if(a[left] < a[right])
{if(a[mid] < a[right])
swap(a[mid], a[left]);
else
swap(a[right], a[left]);
}
}
pivotpos = left; pivot = a[left];
for(int i = left + 1; i <= right; i++)
if(a[i] < pivot && ++pivotpos != i)
swap(a[i], a[pivotpos]);
swap(a[left], a[pivotpos]);
return pivotpos;
}
11. 试写出快速排序的非递归算法
并回答下面问题:
⑴ 在非递归实现快速排序时
通常要用一个栈来保存待排序区间的两个端点
这个栈能否用队列来代替?为什么?
解答:
可以用队列来取代
在快速排序的过程中
通过一趟划分
可以把一个待排序区间分为两个子区间
然后分别对这两个子区间施行同样的划分
栈的作用是在处理一个子区间时
保存另一个区间的上界和下界
这个功能利用队列可以实现
只不过是处理子区间的顺序有所变动而己

⑵ 在非递归实现快速排序时
可根据基准把待排序区间分割为两个区间
若下一趟先对较短的区间进行排序
试证明在这种情况下快

速排序所需栈的深度为O(nlog2n)

解答:
由快速排序的算法可知
所需递归工作栈的深度取决于所需划分的最大次数
如果在排序过程中每次划分都能把整个待排序序列根据基准对象划分为左、右两个子序列
假定这两个子序列的长度相等
则所需栈的深度为
S(n) = 1 + S(n/2) =
= 1 + { 1 + S(n/4) } = 2 + S(n/4)
= 2 + { 1 + S(n/8) } = 3 + S(n/8)
= ......
= log2n + S(1) = log2n (假设1个对象的序列所用递归栈的深度为0)
如果每次递归左、右子序列的长度不等
并且先将较长的子序列的左、右端点保存在递归栈中
再对较短的子序列进行排序
可用表示最坏情况的大O表示法表示
此时其递归栈的深度不一定正好是log2n
其最坏情况为O(log2n)

12. 奇偶交换排序是另一种交换排序
第一趟对排序码中的所有奇数项i扫描
第二趟对排序码中的偶数项i扫描;扫描过程中比较位置i和位置i+1上元素的关键码值
若不满足次序关系则交换它们
以后的扫描始终是奇数趟对所有的奇数项
偶数趟对所有的偶数项
如此反复地扫描、比较和交换
直到整个序列全部排好序为止

⑴ 这种排序方法的结束条件是什么?
⑵ 写出奇偶交换排序的算法

⑶ 若待排序码的初始序列是从小到大有序或是从大到小有序时
在奇偶交换排序过程中的关键字比较次数各是多少?
解答:
⑴ 结束条件是:没有交换元素为止

⑵ 奇偶交换排序的算法如下:
void changesort(int a[],int n)
{int flag=1,temp,i;
while(flag==1)
{flag=0;
for(i=1;iif(a[i]>a[i+1])
{temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
flag=1;
}
for(i=0;iif(a[i]>a[i+1])
{temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
flag=1;
}
}
} /* End of changesort*/
⑶ 设待排序对象序列中总共有N个对象
序列中各个对象的序号从0开始
则当所有待排序对象序列中的对象按排序码从大到小初始排序时
执行M=(N+1)/2趟奇偶排序
当所有待排序对象序列中的对象按排序码从小到大初始排列时
执行1趟奇偶排序

在一趟奇偶排序过程中
对所有奇数项扫描一遍
排序码比较(N-1)/2次;对所有偶数项扫描一遍
排序码比较N/2次
所有每趟奇偶排序两遍扫描的结果
排序码的总比较次数为(N-1)/2+N/2=N-1

13. 请设计一个算法
对用单链表表示的排序码序列进行直接选择排序

解答:
[解题思路]设单链表节点的类型为linklist
其含next域
key域和其他域

 

 [程序实现]
Selection(linklist *head)
{int t;
linklist *s,*q,*p;
P=head;
while(p!=NULL) /*单链表不为空时*/
{s=p;
q=p->next;
while(q!=NULL)
{if(q->keykey)
s=q;
q=q->next;/*在每一趟中
指针S指示最小关键字节点*/
}
if (s!=p)//交换
{t=p->key;
p->key=s->key;
s->key=t;
}
p=p->next;
}
}
14. 若排序码有11个
为了完成树型选择排序
至少需要进行多少次关键字值的比较?
解答:对于有N(N>0)个数据的序列
排序选最小数据需要进行N-1次数据比较
以后每选一个数据
进行数据比较的次数
均需要次(在外层结点无比较)
对于有11个排序码的序列
第一次选具有最小序列码的数据
需要进行10次排序码比较
以后在剩下的序列中没选择一个具有最小的序列
都需要次排序码的比较
因此
为了完成排序
需要10+2×10=30次排序码比较

15. 判断下列序列是否为堆
若不是堆则把它们调整为堆

⑴(100
85
98
77
80
60
82
40
20
10
66)
⑵(100
98
85
82
80
77
66
60
40
20
10)
⑶(100
85
40
77
80
60
66
98
82
10
20)
⑷(10
20
40
60
66
77
80
82
85
98
100)
解答:依据堆定义可知
序列(1)(2)(4)是堆
(3)不是堆
从而可对其调整使之为如下的大根堆(100
95
65
85
80
60
40
75
82
10
20)

16. 写出对于排序码(tim,dot,eva,rom,kim,guy,ann,jim,kay,ron,jan)按字母顺序排序的堆排序过程
要求给出形成初始堆和每选出一个关键码后堆的变化情况

解答:为节省篇幅
将用数组方式给出形成初始堆和进行堆排序的变化结果
阴影部分表示参与比较的排序码
请读者按照完全二叉树的顺序存储表示画出堆的树形表示

形成初始堆(按最大堆)

0 1 2 3 4 5 6 7 8 9 10 Tim Dot Eva Rom Kim Guy Ann Jim Kay Ron Jan i=4 Tim Dot Eva Rom [ Ron Guy Ann Jim Kay Kim Jan ] i=3 Tim Dot Eva [ Rom Ron Guy Ann Jim Kay Kim Jan ] i=2 Tim Dot [ Guy Rom Ron Eva Ann Jim Kay Kim Jan ] i=1 Tim [ Ron Guy Rom Kim Eva Ann Jim Kay Dot Jan ] i=0 [ Tim Ron Guy Rom Kim Eva Ann Jim Kay Dot Jan ]

堆排序
0 1 2 3 4 5 6 7 8 9 10 j=10 [ Jan Ron Guy Rom Kim Eva Ann Jim Kay Dot Tim ] 交换 [ Ron Rom Guy Kay Kim Eva Ann Jim Jan Dot ] Tim 调整 j=9 [ Dot Rom Guy Kay Kim Eva Ann Jim Jan Ron ] Tim 交换 [ Rom Kim Guy Kay Dot Eva Ann Jim Jan ] Ron Tim 调整 j=8 [ Jan Kim Guy Kay Dot Eva Ann Jim Rom ] Ron Tim 交换 [ Kim Kay Guy Jim Dot Eva Ann Jan ] Rom Ron Tim 调整 j=7 [ Jan Kay Guy Jim Dot Eva Ann Kim ] Rom Ron Tim 交换 [ Kay Jim Guy Jan Dot Eva Ann ] Kim Rom Ron Tim 调整 j=6 [ Ann Jim Guy Jan Dot Eva Kay ] Kim Rom Ron Tim 交换

[ Jim Jan Guy Ann Dot Eva ] Kay Kim Rom Ron Tim 调整 j=5 [ Eva Jan Guy Ann Dot Jim ] Kay Kim Rom Ron Tim 交换 [ Jan Eva Guy Ann Dot ] Jim Kay Kim Rom Ron Tim 调整 j=4 [ Dot Eva Guy Ann Jan ] Jim Kay Kim Rom Ron Tim 交换 [ Guy Eva Dot Ann ] Jan Jim Kay Kim Rom Ron Tim 调整 j=3 [ Ann Eva Dot Guy ] Jan Jim Kay Kim Rom Ron Tim 交换 [ Eva Ann Dot ] Guy Jan Jim Kay Kim Rom Ron Tim 调整 j=2 [ Dot Ann Eva ] Guy Jan Jim Kay Kim Rom Ron Tim 交换 [ Dot Ann ] Eva Guy Jan Jim Kay Kim Rom Ron Tim 调整 j=1 [ Dot Ann ] Eva Guy Jan Jim Kay Kim Rom Ron Tim 交换 [ Ann ] Dot Eva Guy Jan Jim Kay Kim Rom Ron Tim 调整 17. 希尔排序、直接选择排序、快速排序、堆排序都是不稳定的排序方法
试举例说明之

解答:
希尔排序:
初始序列:52
38
66
87
77
16
30

62
07
排序后序列:07
16
30
38

52
62
66
77
87
直接选择排序:
初始序列:
36
48
97
15
27
1
排序后序列:1
15
27
36
48

97
快速排序:
初始序列:15

2
7
排序后序列:2
7

15
堆排序:
初始序列:47

67
43
排序后序列:43

47
67
18. 对于给定的以单链表作存储结构的两个有序表
均不带表头结点且指向第一个元素结点的头指针分别为la和lb
请设计算法把它们归并为一个有序链表;要求利用两个链表中原有的结点空间
且结果链表的头指针为la

解答:
[解题思路]先对待排序的单链表进行一次扫描
将它划分为若干有序的子链表
其表头指针存放在一个指针队列中
当队列不空时重复执行
从队列中退出两个有序子链表
对它们进行二路归并
结果链表的表头指针存放到队列中
如果队列中退出一个有序子链表后变成空队列
则算法结束
这个有序子链表即为所求

[程序实现]
linklist *merge(linklist *la,linklist *lb)
{linklist *lc,*s,*q;
lc=NULL;
while(lb!=NULL) /*lb表不为空*/
if(la==NULL) /*la表为空*/
{la=lb;
lb=NULL;
}
else /*la,lb表均不空*/
{if(lb->key>la->key)
{s=la;
la=la->next;
}
s->next=NULL;
if(lc==NULL)
lc=s;
else
q->next=s;
q=s;
}
if(lc==NULL)
lc=la;
else
q->next=la;
q=la;
return (lc);
}
19. 设有n个元素的排序码存放在不带表头结点的单链表中
每个结点存放一个元素
头指针为r
试设计算法对其进行二路归并排序
要求只修改结点中的链指针而不移动结点中的元素值
排序后r仍指向结果链表的第一个结点

解答:
⑴ 两路归并算法
void merge(L

istNode *ha,ListNode*hb,ListNode *hc)
{ListNode *pa,*pb,*pc;
if(ha->data<=hb->data )
{hc=ha;pa=ha->link;pb=hb;}
else
{hc=hb;pb=hb->link;pa=ha;}
pc=hc;
while(pa&&pb)
if(pa->data<=pb->data )
{pc->link=pa;
pc=pa;
pa=pa->link;
}
else
{pc->link=pb;
pc=pb;
pb=pb->link ;
}
if(pa)
pc->link=pa;
if(pb)
pc->link=pb;
}
⑵ 归并排序主程序
void mergesort(ListNode *r)
{ListNode *s,t;
Queue Q;
if(!r)
return;
s=r;
Q.EnQueue(r);
while(s)
{t=s->link;
while(t!=0 && s->data<=t->data)
{s=t;
t=t->link;
}
if(t)
{s->link=0;
s=t;
Q.EnQueue(s);
}
}
while(!Q.IsEmpty())
{r=Q.DlQueue( );
if(Q.IsEmpty( ))
break;
s=Q.DlQueue( );
merge(r,s,t);
Q.EnQueue(t);
}
}
20. 二路归并排序是从n个长度为1的有序子序列开始的
其一种改进方式是先对排序码序列扫描一遍
并把它划分为若干长度最大的有序子序列
然后从这些有序子序列开始进行两两归并
例如
若排序码为(15,18,02,26,43,92,87,25,28,30,36,12)
先扫描一遍划分为(15,18)、(02,26,43,92)、(87)、(25,28,30,36)、(12)这五个有序子序列
然后从这五个子序列开始两两归并
试写出这种改进后的二路归并排序算法
并分析算法的时间与空间性能

解答:
[解题思路] 把n个待排序元素存放在一个不带表头结点的单链表中
每个链表结点只存放一个元素
头指针为r
先对待排序的单链表进行一次扫描
将它划分为若干有序的子链表
其表头指针存放在一个指针队列中
当队列不空时重复执行
从队列中退出两个有序子链表
对它们进行二路归并
结果链表的表头指针存放到队列中
如果队列中退出一个有序子链表后变成空队列
则算法结束
这个有序子链表即为所求

[程序实现]
请参考本章的第19题

21. 试设计一个算法
它将序列(x1
x2 ... xn)循环右移p个位置
0≤p≤n
要求该算法的时间复杂度为O(n)而空间复杂度为O(1)

解答:
[解题思路]使用序列逆置的方法
首先将序列x0...xn-p-1逆置
然后将序列xn-p...xn-1逆置
最后将序列x0...xn-1逆置
就可得到序列(x1
x2 ... xn)循环右移p个位置后的序列

[程序实现]
void right_move(int x[],int n,int p)
{int i,j,t;
for(i=0,j=n-p-1;i{t=x[i];x[i]=x[j];x[j]=t;}
for(i=n-p,j=n-1;i{t=x[i];x[i]=x[j];x[j]=t;}
for(i=0,j=n-1;i{t=x[i];x[i]=x[j];x[j]=t;}
}
22. 在什么条件下
高位优先基数排序比低位优先基数排序效率更高?
解答:
由于高位优先的基数排序方法是递归的

方法
就一般情况来说
不像低位优先的基数排序方法那样直观自然
而且实现的效率较低
但如果待排序的排序码的大小只取决于高位的少数几位而与大多数低位无关时
采用高位优先基数排序方法比低位优先基数排序方法的效率要高

23. 试证明对于一个具有n个元素的排序码进行基于比较的排序
最少需要进行nlog2n次关键字值的比较

证明:
基于比较的排序方法中
采用分治法进行排序是平均性能最好的方法
方法描述如下:
Sort ( List )
{if(List的长度大于1)
{将序列List划分为两个子序列LeftList和Right List;
Sort(LeftList);Sort(RightList);//分别对两个子序列施行排序
将两个子序列LeftList和RightList合并为一个序列List;
}
}
典型的例子就是快速排序和归并排序
若设T(n) 是对n个对象的序列进行排序所需的时间
而且把序列划分为长度相等的两个子序列后
对每个子序列进行排序所需的时间为T(n/2)
最后合并两个已排好序的子序列所需时间为cn(c是一个常数)
此时
总的计算时间为:
T(n)≤cn + 2 T(n/2 ) // c是一个常数
≤cn + 2 ( cn/2 + 2T(n/4) ) = 2cn + 4T(n/4)
≤2cn + 4 ( cn/4 +2T(n/8) ) = 3cn + 8T(n/8)
.........
≤cn log2n + nT(1) = O(n log2n )
命题得证

24. 在已排好序的序列中
一个元素所处的位置取决于具有更小关键字值的元素的个数
基于这一事实可有如下的计数排序方法:为排序码中的每个元素增加一个计数域count
用于统计排序码中关键字值小于该元素关键字值的元素个数
最后依count域的值将排序码重新排列就可以完成排序
试设计算法实现该计数排序

解答:
[解题思路]


0 0 0 0 初始状态 2 1 0 0 第1趟排序结果 3 0 0 第2趟排序结果 0 1 第3趟排序结果


[程序实现]
void count_sort ( )
{int i,j;
int *c = new datalist ; /*c是存放计数排序结果的临时表*/
for(i=0;iVector[i].count=0; /*初始化
计数值都为0*/
for(i=0;ifor(j=i+1;jif(Vector[j].keyVector[i].count++;
else
Vector[j].count++; /*统计*/
for(i=0;iVector[ ]中各就各位*/
c->Vector[Vector[i].count]=Vector[i];
for(i=0;iVector[i]=c->Vector[i]; /*结果复制回当前表对象中*/
free(c);
}
25. 假设文件有4500个记录
在磁盘上每个页块可放75个记录;计算机中用于排序的内存区可容纳450个记录
试问:
⑴ 可以建立多少个初始归并段?每个初始归并段多少个记录?存放于多少个页块中?
⑵ 应采用几路归并?请写出归并过程及每趟需要

读写磁盘的页块数

解答:
⑴ 文件有4500个记录
计算机中用于排序的内存区可容纳450个记录
可建立的初始归并段有4500∕450 = 10个
每个初始归并段中有450个记录
存于450∕75 = 6个页块中

⑵ 内存区可容纳6个页块
可建立6个缓冲区
其中5个缓冲区用于输入
1个缓冲区用于输出
因此
可采用5路归并
归并过程如下:








共做了2趟归并
每趟需要读60个磁盘页块
写出60个磁盘页块


8.3 上机实验指导
1. 实验目的
⑴ 深刻理解排序的定义和各种排序方法的特点
并能灵活运用

⑵ 掌握常用的排序方法
并掌握用高级语言实现排序算法的方法

⑶ 了解各种方法的排序过程及其依据的原则
并掌握各种排序方法的性能的分析方法

2. 实验内容
⑴ 快速排序的非递归算法的程序实现

解答:
#include "stdio.h"
int stack[200],sp;
pushstack(int a)
{sp=sp+1;
stack[sp]=a;
}
int popstack()
{if(sp<0)
return(0);
else
{sp--;
return(stack[sp+1]);
}
}
void quicksort(int r[],int s,int t)
{int i,j,temp;
i=s;
j=t;
temp=r[s];
while(j>i)
{while((r[j]>=temp)&&(ij--;
if((j>i)&&(r[j]{r[i]=r[j];
r[j]=temp;
i++;
}
while((r[i]<=temp)&&(ii++;
if((j>i)&&(r[i]>temp))
{r[j]=r[i];
r[i]=temp;
j--;
}
}
if(s<(i-1))
{pushstack(s);
pushstack(i-1);
}
if(t>(i+1))
{pushstack(i+1);
pushstack(t);
}
}
main()
{int i,n,r[100],s,t;
printf("please input the number of the data you want to sort:");
scanf("%d",&n);
for(i=0;iscanf("%d",&r[i]);
sp=-1;
pushstack(0);
pushstack(n-1);
while(sp>=0)
{t=popstack();
s=popstack();
quicksort(r,s,t);
}
printf("\n");
for(i=0;iprintf("%d ",r[i]);
}
⑵ 实现二路归并排序或改进了的二路归并排序算法(参考习题20)

解答:
#include "stdio.h"
merge(int *r,int *a,int s,int m,int t)
{int i,j,k;
i=s;
j=m+1;
k=s;
while((i<=m)&&(j<=t))
if(r[i]<=r[j])
{a[k]=r[i];
i++;
k++;
}
else
{a[k]=r[j];
j++;
k++;
}
while(i<=m)
{a[k]=r[i];
i++;
k++;
}
while(j<=t)
{a[k]=r[j];
j++;
k++;
}
}
mergepass(int *r,int *a,int n,int c)
{int i,j;
i=0;
while(i+2*c-1<=n)
{merge(r,a,i,i+c-1,i+2*c-1);
i=i+2*c;
}
if(i+c-1merge(r,a,i,i+c-1,n);
else
for(j=i;j<=n;j++)
a[j]=r[j];
}
mergesort(int *r,int n)
{int c,a[100];
c=1;
while(c{mergepass(r,a,n,c);
c=2*c;
mergepass(a,r,n,c);
c=2*c;
}
}
main()
{int i,n,r[100];
printf("please input

the number of the data you want to sort:");
scanf("%d",&n);
for(i=0;iscanf("%d",&r[i]);
mergesort(r,n-1);
printf("\n");
for(i=0;iprintf("%d ",r[i]);
}
⑶ 实现奇偶交换排序(参见习题12)

解答:
#include "stdio.h"
void changesort(int a[],int n)
{int flag=1,temp,i;
while(flag)
{flag=0;
for(i=1;iif(a[i]>a[i+1])
{temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
flag=1;
}
for(i=0;iif(a[i]>a[i+1])
{temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
flag=1;
}
} /*End of while*/
}/*End of changesort*/
main()
{int a[100],n,i;
printf("please input the number of the data you want to sort:");
scanf("%d",&n);
for(i=0;iscanf("%d",&a[i]);
changesort(a, n);
for(i=0;iprintf("%d ",a[i]);
}
⑷ 编程实现堆排序算法

解答:
#include "stdio.h"
creatheap(int *r,int i,int n)
{int j,temp;
temp=r[i];
j=2*i;
while(j<=n)
{if((jj++;
if(temp{r[i]=r[j];
r[j]=temp;
i=j;
j=2*i;
}
else
j=n+1;
}
}
heapsort(int *r,int n)
{int i,j,temp;
for(i=n/2;i>=1;i--)
creatheap(r,i,n);
for(i=n;i>=1;i--)
{temp=r[1];
r[1]=r[i];
r[i]=temp;
creatheap(r,1,i-1);
}
}
main()
{int i,n,r[100];
printf("please input the number of the data you want to sort:");
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&r[i]);
heapsort(r,n);
printf("\n");
for(i=1;i<=n;i++)
printf("%d ",r[i]);
}
⑸ 某班级有60名学生
本学期共开设6门课程
要求编程实现如下功能:
① 输入每个学生的学号、姓名和六门功课的成绩;
② 计算每个学生本学期的总分和平均分数;
③ 把学生按总分排列名次(总分相同者为同名次)
并按名次列出学生的学号
姓名和各科成绩;
④ 列出每门功课成绩排列在前5名的学生的学号、姓名和成绩

解答:
#include "stdio.h"
#include "string.h"
#define N 6
struct student
{char name[20];
int number; /*学号*/
int score[8];/*score[6]为总分
score[7]为平均分
score[0]--score[5]为学科成绩*/
}stu[60];
void changesort(struct student a[],int n,int j)
{int flag=1,i;
struct student temp;
while(flag)
{flag=0;
for(i=1;iif(a[i].score[j]>a[i+1].score[j])
{temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
flag=1;
}
for(i=0;i

if(a[i].score[j]>a[i+1].score[j])
{temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
flag=1;
}
} /*End of while*/
}
void print_score(struct student a[],int n,int j)
{int i,k;
printf("Student Course score sort:\n");
printf("No Number Name Score%d\n",j);
k=1;
for(i=0;k<=5&&i{if(i>0&&a[i].score[j]!=a[i-1].score[j])
k++;
printf("%4d",k);
printf("%8d",a[i].number);
printf("%s",a[i].name);
printf("%8d",a[i].score[j]);
printf("\n");
}
}
main()
{int i,j,k;
struct student temp;
for(i=0;i{printf("Input Name Num score1 score2 score3 score4 score5 score6:\n");
getchar(); /*消除printf输出的回车符*/
scanf("%s",stu[i].name);
scanf("%d",&stu[i].number);
for(j=0;j<6;j++)
scanf("%d",&stu[i].score[j]);
}
for(i=0;i平均分*/
{stu[i].score[6]=0;
for(j=0;j<6;j++)
stu[i].score[6]+=stu[i].score[j];
stu[i].score[7]=stu[i].score[6]/6;
}
changesort(stu,N,6);/*对总分进行排序*/
printf("Student Sort:\n");
printf("Grade Num Name score1 score2 score3 score4 score5 score6 Sum Aver");
k=1;
for(i=0;i{if(i>0&&stu[i].score[6]!=stu[i-1].score[6])
k++;
printf("%4d",k);
printf("%8d",stu[i].number);
printf("%s",stu[i].name);
for(j=0;j<8;j++)
printf("%8d",stu[i].score[j]);
printf("\n");
}
changesort(stu,N,0); /*对成绩1进行排序*/
print_score(stu,N,0); /*输出成绩1前5名数据*/
changesort(stu,N,1); /*对成绩2进行排序*/
print_score(stu,N,1); /*输出成绩2前5名数据*/
changesort(stu,N,2); /*对成绩3进行排序*/
print_score(stu,N,2); /*输出成绩3前5名数据*/
changesort(stu,N,3); /*对成绩4进行排序*/
print_score(stu,N,3); /*输出成绩4前5名数据*/
changesort(stu,N,4); /*对成绩5进行排序*/
print_score(stu,N,4); /*输出成绩5前5名数据*/
changesort(stu,N,5); /*对成绩6进行排序*/
print_score(stu,N,5); /*输出成绩6前5名数据*/
}
??

??

??

??




211



相关文档
最新文档