1.以关键字序列(265,301,751,129,937,863,742,694,076,438)为例,分别写出执行以下排序算法的各趟排序结束时,关键字序列的状态。
(1) 直接插入排序 (2)希尔排序 (3)冒泡排序 (4)快速排序
(5) 直接选择排序 (6) 堆排序 (7) 归并排序 (8)基数排序
上述方法中,哪些是稳定的排序?哪些是非稳定的排序?对不稳定的排序试举出一个不稳定的实例。
答:
(1)直接插入排序:(方括号表示无序区)
初始态: 265[301 751 129 937 863 742 694 076 438]
第一趟:265 301[751 129 937 863 742 694 076 438]
第二趟:265 301 751[129 937 863 742 694 076 438]
第三趟:129 265 301 751[937 863 742 694 076 438]
第四趟:129 265 301 751 937[863 742 694 076 438]
第五趟:129 265 301 751 863 937[742 694 076 438]
第六趟:129 265 301 742 751 863 937[694 076 438]
第七趟:129 265 301 694 742 751 863 937[076 438]
第八趟:076 129 265 301 694 742 751 863 937[438]
第九趟:076 129 265 301 438 694 742 751 863 937
(2)希尔排序(增量为5,3,1)
初始态: 265 301 751 129 937 863 742 694 076 438
第一趟:265 301 694 076 438 863 742 751 129 937
第二趟:076 301 129 265 438 694 742 751 863 937
第三趟:076 129 265 301 438 694 742 751 863 937
(3)冒泡排序(方括号为无序区)
初始态 [265 301 751 129 937 863 742 694 076 438]
第一趟: 076 [265 301 751 129 937 863 742 694 438]
第二趟: 076 129 [265 301 751 438 937 863 742 694]
第三趟: 076 129 265 [301 438 694 751 937 863 742]
第四趟: 076 129 265 301 [438 694 742 751 937 863]
第五趟: 076 129 265 301 438 [694 742 751 863 937]
第六趟: 076 129 265 301 438 694 742 751 863 937
(4)快速排序:(方括号表示无序区,层表示对应的递归树的层数)
初始态: [265 301 751 129 937 863 742 694 076 438]
第二层: [076 129] 265 [751 937 863 742 694 301 438]
第三层: 076 [129] 265 [438 301 694 742] 751 [863 937]
第四层: 076 129 265 [301] 438 [694 742] 751 863 [937]
第五层: 076 129 265 301 438 694 [742] 751 863 937
第六层: 076 129 265 301 438 694 742 751 863 937
(5)直接选择排序:(方括号为无序区)
初始态 [265 301 751 129 937 863 742 694 076 438]
第一趟: 076 [301 751 129 937 863 742 694 265 438]
第二趟: 076 129 [751 301 937 863 742 694 265 438]
第三趟: 076 129 265[ 301 937 863 742 694 751 438]
第四趟: 076 129 265 301 [937 863 742 694 751 438]
第五趟: 076 129 265 301 438 [863 742 694 751 937]
第六趟: 076 129 265 301 438 694 [742 751 863 937]
第七趟: 076 129 265 301 438 694 742 [751 863 937]
第八趟: 076 129 265 301 438 694 742 751 [937 863]
第九趟: 076 129 265 301 438 694 742 751 863 937
(6)堆排序:(通过画二叉树可以一步步得出排序结果)
初始态 [265 301 751 129 937 863 742 694 076 438]
建立初始堆: [937 694 863 265 438 751 742 129 075 301]
第一次排序重建堆:[863 694 751 765 438 301 742 129 075] 937
第二次排序重建堆:[751 694 742 265 438 301 075 129] 863 937
第三次排序重建堆:[742 694 301 265 438 129 075] 751 863 937
第四次排序重建堆:[694 438 301 265 075 129] 742 751 863 937
第五次排序重建堆:[438 265 301 129 075] 694 742 751 863 937
第六次排序重建堆:[301 265 075 129] 438 694 742 751 863 937
第七次排序重建堆:[265 129 075] 301 438 694 742 751 863 937
第八次排序重建堆:[129 075]265 301 438 694 742 751 863 937
第九次排序重建堆:075 129 265 301 438 694 742 751 863 937
(7)归并排序(为了表示方便,采用自底向上的归并,方括号为有序区)
初始态:[265] [301] [751] [129] [937] [863] [742] [694] [076] [438]
第一趟:[265 301] [129 751] [863 937] [694 742] [076 438]
第二趟:[129 265 301 751] [694 742 863 937] [076 438]
第三趟:[129 265 301 694 742 751 863 937] [076 438]
第四趟:[076 129 265 301 438 694 742 751 863 937]
(8)基数排序(方括号内表示一个箱子共有10个箱子,箱号从0到9)
初始态:265 301 751 129 937 863 742 694 076 438
第一趟:[] [301 751] [742] [863] [694] [265] [076] [937] [438] [129]
第二趟:[301] [] [129] [937 438] [742] [751] [863 265] [076] [] [694]
第三趟:[075] [129] [265] [301] [438] [] [694] [742 751] [863] [937]
在上面的排序方法中,直接插入排序、冒泡排序、归并排序和基数排序是稳定的,其他排序算法均是不稳定的,现举实例如下:以带*号的表示区别。
希尔排序:[8,1,10,5,6,*8]
快速排序:[2,*2,1]
直接选择排序:[2,*2,1]
堆排序:[2,*2,1]
2.上题的排序方法中,哪些易于在链表(包括各种单、双、循环链表)上实现?
答:
上题的排序方法中,直接插入排序、冒泡排序、直接选择排序、基数排序和归并排序等方法易于在链表上实现。
3.当R[low..high]中的关键字均相同时,Partion返回值是什么?此时快速排序的的运行时间是多少?能否修改Partion,使得划分结果是平衡的(即划分后左右区间的长度大致相等)?
答:
此时Partion 返回值是low.此时快速排序的运行时间是
(high-low)(high-low-1)/2=O((high-low)^2),可以修改Partion ,将其中RecType pivot=R[i];句改为:RecType pivot=R[(j+i)/2];也就是取中间的关键字为基准,这样就能使划分的结果是平衡的。
4.若文件初态是反序的,则直接插入,直接选择和冒泡排序哪一个更好?
答:
应选直接选择排序为更好。分析如下:
(1)在直接插入排序算法中,反序输入时是最坏情况,此时:
关键字的比较次数:Cmax=(n+2)(n-2)/2;
记录移动次数为:Mmax=(n-1)(n+4)/2;
Tmax=n^2-4n-3 (以上二者相加)
(2)在冒泡排序算法中,反序也是最坏情况,此时:
Cmax=n(n-1)/2; Mmax=3n(n-1)/2
Tmax=2n^2-2n
(3)在选择排序算法中,
Cmax=n(n-1)/2 Mmax=3(n-1)
Tmax=n^2/2-5n/2-3
由此可见,虽然它们的时间复杂度都是O(n^2),但是选择排序的常数因子为1/2,因此选择排序最省时间。
5.若文件初态是反序的,且要求输入稳定,则在直接插入、直接选择、冒泡和快速排序中就选选哪种方法为宜?
答:
这四种排序算法中,直接选择、快速排序均是不稳定的,因此先予以排除,剩下两种算法中,由于直接插入算法所费时间比冒泡法更少(见上题分析),因此选择直接排序算法为宜。
6.有序数组是堆吗?
答:
有序数组是堆。因为有序数组中的关键字序列满足堆的性质。若数组为降序,则此堆为大根堆,反之为小根堆。
7.高度为h的堆中,最多有多少个元素?最少有多少个元素?在大根堆中,关键字最小的元素可能存放在堆的哪些地方?
答:
高度为h的堆实际上为一棵高度为h的完全二叉树,因此根据二叉树的性质可以算出,它最少应有2h-1个元素;最多可有2h-1个元素(注意一个有括号,一个没有)。在大根堆中,关键字最小的元素可能存放在堆的任一叶子结点上。
8.判别下列序列是否为堆(小根堆或大根堆),若不是,则将其调整为堆:
(1) (100,86,73,35,39,42,57,66,21);
(2) (12,70,33,65,24,56,48,92,86,33);
(3) (103,97,56,38,66,23,42,12,30,52,06,20);
(4) (05,56,20,23,40,38,29,61,35,76,28,100).
答:
堆的性质是:任一非叶结点上的关键字均不大于(或不小于)其孩子结点上的关键字。据此我们可以通过画二叉树来进行判断和调整:
(1) 此序列是大根堆。
(2) 此序列不是堆,经调整后成为小根堆:
(12,24,33,65,33,56,48,92,86,70)
(3) 此序列是一大根堆。
(4) 此序列不是堆,经调整后成为小根堆:
(01,05,20,23,28,38,29,56,35,76,40,100)
9.将两个长度为n的有序表归并为一个长度为2n的有序表,最小需要比较n次,最多需要比较2n-1次,请说明这两种情况发生时,两个被归并的表有何特征?
答:
前一种情况下,这两个被归并的表中其中一个表的最大关键字不大于另一表中最小的关键字,也就是说,两个有序表是直接可以连接为有序的,因此,只需比较n次就可将一个表中元素转移完毕,另一个表全部照搬就行了。
另一种情况下,是两个被归并的有序表中关键字序列完全一样,这时就要按次序轮流取其元素归并,因此比较次数达到2n-1.
10.设关键字序列为(0.79,0.13,0.16,0.64,0.39,0.20,0.89,0.53,0.71,0.42),给出桶排序的结果。
答:
桶排序的结果如图:
B[0..9]
┌──┐
0│ ∧ │
├──┤
1│ → 0.13→0.16→ ∧
├──┤
2│ →0.20→ ∧
├──┤
3│ →0.39→ ∧
├──┤
4│ →0.42→ ∧
├──┤
5│ →0.53→ ∧
├──┤
6│ →0.64→ ∧
├──┤
7│ →0.71→0.79→ ∧
├──┤
8│ →0.89→ ∧
├──┤
9│ ∧ │
└──┘
结果为:0.13 0.16 0.20 0.39 0.42 0.53 0.64 0.71 0.79 0.89
11.若关键字是非负整数、快速排序、归并、堆和基数排序啊一个最快?若要求辅助空间为O(1),则应选择谁? 若要求排序是稳定的,且关键字是实数,则应选择谁?
答:
若关键字是非负整数,则基数排序最快;若要求辅助空间为O(1),则应选堆排序;若要求排序是稳定的,且关键字是实数,则应选归并排序,因为基数排序不适用于实数,虽然它也是稳定的。
12.对于8.7节的表8.2,解释下述问题:
(1)当待排序的关键字序列的初始态分别为正序和反序时,为什么直接选择排序的时间基本相同?若采用本书8.4.1节的算法,这两种情况下的排序时间是否基本相同?
(2)为什么数组的初态为正序时,冒泡和直接插入排序的执行时间最少?
(3)若采用8.3.2节的快速排序,则数组初态为正序和反序时,能得到与表8.2类似的结果吗?
答:
(1)由于在直接选择排序中,主要的操作是比较操作和移动操作。无论文件的初始状态如何,若文件有n个记录,则都要进行n-1趟直接选择排序,第i趟直接选择排序中都要做n-i次比较才能选出最小关键字的记录。所以总的比较次数都为O(n2)。至于记录的移动次数,初始文件为正序时,移动次数为0,当文件初始时为反序,总的移动次数为3(n-1)。因此当待排序的关键字序列的初始态分别为正序和反序时,直接选择排序的时间基本相同,为O(n2)。若采用本书8.4.1节的算法,这两种情况下的排序时间基本相同。
(2)当冒泡排序是正序时,只需做一趟冒泡排序就可完成,共做n-1次比较,移动次数为0,所以执行时间最少。而直接插入排序时,若初始为正序,则做了n-1趟直接插入排序,但每趟排序只做了一次比较,共做n-1次比较。移动次数为0。所以当数组初态为正序,直接插入排序时间也最少。
(3)不能,其中辅助空间不同