43
1 第 10 第 第第 第第第第第第第第第第第第第第第第 第第 1 第第第第第第第第第 )、; 第第第第 第第第第第第第 第第第第第第第 :、、 * 第第第第第第第第 第第第 第第第第第 第第第第第第第第第第第第第 第第第第 :、;( 3 ); 第第第第 第第第第第第第 第第第第 第第第第 第第第 第第第 第第第第 第 :、(,、、); 第第第第2- 第第第第第

第 10 章 排序

Embed Size (px)

DESCRIPTION

第 10 章 排序. 要求: 本章要理解各种排序算法的思想(层次 1 )、稳定性和时空性能; 插入排序:直接插入排序、折半插入排序、*希尔排序; 交换排序:冒泡、快速排序;快速排序算法要求灵活应用(算法层次 3 ); 选择排序:直接选择排序、堆排序(堆概念,筛选、建堆、堆排序); 归并排序: 2- 路归并排序. 第 10 章 排序. 10.1 概述 排序定义 —— 将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列叫 ~ 排序分类 按待排序记录所在位置 内部排序:待排序记录存放在内存 外部排序:排序过程中需对外存进行访问的排序 - PowerPoint PPT Presentation

Citation preview

Page 1: 第 10 章  排序

1

第 10 章 排序要求:•本章要理解各种排序算法的思想(层次 1 )、稳定性和时空性能;•插入排序:直接插入排序、折半插入排序、 * 希尔排序;•交换排序:冒泡、快速排序;快速排序算法要求灵活应用(算法层次 3 );•选择排序:直接选择排序、堆排序(堆概念,筛选、建堆、堆排序);•归并排序: 2- 路归并排序

Page 2: 第 10 章  排序

2

第 10 章 排序10.1 概述

– 排序定义——将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列叫 ~

– 排序分类• 按待排序记录所在位置

– 内部排序:待排序记录存放在内存– 外部排序:排序过程中需对外存进行访问的排序

• 按排序依据原则– 插入排序:直接插入排序、折半插入排序、 * 希尔排序– 交换排序:冒泡排序、快速排序– 选择排序:简单选择排序、堆排序– 归并排序: 2- 路归并排序– * 基数排序

Page 3: 第 10 章  排序

3

• 按排序所需工作量– 简单的排序方法: T(n)=O(n²)– 先进的排序方法: T(n)=O(logn)– * 基数排序: T(n)=O(d.n)

– 排序基本操作• 比较两个关键字大小• 将记录从一个位置移动到另一个位置

Page 4: 第 10 章  排序

4

排序的稳定性  当待排序记录的关键字均不相同时,排序结果是唯一的,否则排序结果不唯一。

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

注意:

  排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入实例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的。

Page 5: 第 10 章  排序

5

10.2 插入排序10.2.1 直接插入排序

– 排序过程:整个排序过程为 n-1 趟插入,即先将序列中第 1 个记录看成是一个有序子序列,然后从第 2 个记录开始,逐个进行插入,直至整个序列有序

Page 6: 第 10 章  排序

例 49 38 65 97 76 13 27 49

i=2 38 (38 49) 65 97 76 13 27 49

i=3 65 (38 49 65) 97 76 13 27 49

i=4 97 (38 49 65 97) 76 13 27 49

i=5 76 (38 49 65 76 97) 13 27 49

i=6 13 (13 38 49 65 76 97) 27 49

i=1 ( )

i=7 (13 38 49 65 76 97 ) 27 4927

jj j jjj

977665493827

(13 27 38 49 49 65 76 97)排序结果:

• 算法描述 算法 10.1 P265

(13 27 38 49 65 76 97 ) 49

Page 7: 第 10 章  排序

void InsertSort(SqList &L) { // 算法 10.1

// 对顺序表 L 作直接插入排序。 for (i=2; i<=L.length; ++i)

if (LT(L.r[i].key, L.r[i-1].key)) {

// "<" 时,需将 L.r[i] 插入有序子表 L.r[0] = L.r[i]; // 复制为哨兵 for (j=i-1; LT(L.r[0].key, L.r[j].key); --j)

L.r[j+1] = L.r[j]; // 记录后移 L.r[j+1] = L.r[0]; // 插入到正确位置 }

} // InsertSort

Page 8: 第 10 章  排序

– 算法评价• 时间复杂度

– 若待排序记录按关键字从小到大排列 ( 正序 )关键字比较次数: 11

2

nn

i

记录移动次数: 0 或 )1(222

nn

i

– 若待排序记录按关键字从大到小排列 ( 逆序 )关键字比较次数:

2

)1)(2(

2

nni

n

i

记录移动次数:2

)1)(4()1(

2

nni

n

i

– 若待排序记录是随机的,取平均值关键字比较次数:

4

2n

记录移动次数:4

2nT(n)=O(n²)

• 空间复杂度: S(n)=O(1)

Page 9: 第 10 章  排序

9

10.2.2 折半插入排序– 排序过程:用折半查找方法确定插入位置的排序叫 ~

例 i=1 (30) 13 70 85 39 42 6 20

i=2 13 (13 30) 70 85 39 42 6 20

i=7 6 (6 13 30 39 42 70 85 ) 20

….

..

i=8 20 (6 13 30 39 42 70 85 ) 20s jm

i=8 20 (6 13 30 39 42 70 85 ) 20s jm

i=8 20 (6 13 30 39 42 70 85 ) 20s jm

i=8 20 (6 13 30 39 42 70 85 ) 20sj

i=8 20 (6 13 20 30 39 42 70 85 )

Page 10: 第 10 章  排序

算法描述 算法 10.2 P267void BInsertSort(SqList &L) {// 对顺序表 L 作折半插入排序。 for (i=2; i<=L.length; ++i) { L.r[0] = L.r[i]; // 将 L.r[i] 暂存到 L.r[0] low = 1; high = i-1; while (low<=high) {// 在 r[low..high] 中折半查找有序插入的位置 m = (low+high)/2; // 折半 if (LT(L.r[0].key, L.r[m].key)) high = m-1; // 插入点在低半区 else low = m+1; // 插入点在高半区 } for (j=i-1; j>=high+1; --j) L.r[j+1] = L.r[j]; // 记录后移 L.r[high+1] = L.r[0]; // 插入 }} // BInsertSort

– 算法评价• 时间复杂度: T(n)=O(n²)• 空间复杂度: S(n)=O(1)

Page 11: 第 10 章  排序

11

10.2.3 * 希尔排序 ( 缩小增量法 )– 排序过程:先取一个正整数 d1<n ,把所有相隔

d1 的记录放一组,组内进行直接插入排序;然后取 d2<d1 ,重复上述分组和排序操作;直至 di=1 ,即所有记录放进一个组中排序为止

Page 12: 第 10 章  排序

取 d3=1三趟分组:13 27 48 55 4 49 38 65 97 76

三趟排序:4 13 27 38 48 49 55 65 76 97

例 初始: 49 38 65 97 76 13 27 48 55 4

一趟排序:13 27 48 55 4 49 38 65 97 76

二趟排序:13 4 48 38 27 49 55 65 97 76

取 d1=5一趟分组:49 38 65 97 76 13 27 48 55 4

取 d2=3二趟分组:13 27 48 55 4 49 38 65 97 76

Page 13: 第 10 章  排序

13

– 算法描述 算法 10.5 P272

例 49 38 65 97 76 13 27 48 55 4

#define T 3int d[]={5,3,1};

j i

4913 3827

一趟排序:13 27 48 55 4 49 38 65 97 76

j ij i

274

j ij i

55

j i

38

j ij ij i

二趟排序:13 4 48 38 27 49 55 65 97 76

j ij i

6548

j i

9755

j i

764

Page 14: 第 10 章  排序

14

• 希尔排序特点– 子序列的构成不是简单的“逐段分割”,而是将

相隔某个增量的记录组成一个子序列– 希尔排序可提高排序速度,因为

• 分组后 n 值减小, n² 更小,而 T(n)=O(n²), 所以 T(n) 从总体上看是减小了

• 关键字较小的记录跳跃式前移,在进行最后一趟增量为 1 的插入排序时,序列已基本有序

– 增量序列取法• 无除 1 以外的公因子• 最后一个增量值必须为 1

Page 15: 第 10 章  排序

15

10.3 交换排序10.3.1 冒泡排序

– 排序过程将第一个记录的关键字与第二个记录的关键字进行比

较,若为逆序 r[1].key>r[2].key ,则交换;然后比较第二个记录与第三个记录;依次类推,直至第 n-1 个记录和第 n 个记录比较为止——第一趟冒泡排序,结果关键字最大的记录被安置在最后一个记录上

对前 n-1 个记录进行第二趟冒泡排序,结果使关键字次大的记录被安置在第 n-1 个记录位置

重复上述过程,直到“在一趟排序过程中没有进行过交换记录的操作”为止

Page 16: 第 10 章  排序

例 49 38 65 97 76 13 27 30

初始关键

38 49 65 76 13 27 30 97

第一

38 49 65 13 27 30 76

第二

38 49 13 27 30 65

第三

趟38 13 27 30 49

第四

13 27 30 38

第五

13 27 30

第六

38

49

76

9713

9727

9730

97

13

76

76

7627

30

13

6527

6530

65

13

13

49

4930

4927

3827

3830

38

Page 17: 第 10 章  排序

17

– 算法描述 C 语言

– 算法评价• 时间复杂度

– 最好情况(正序)比较次数: n-1移动次数: 0

– 最坏情况(逆序)比较次数: )(

2

1)( 2

1

1

nninn

i

移动次数: )(2

3)(3 2

1

nninn

i

• 空间复杂度: S(n)=O(1)

T(n)=O(n²)

Page 18: 第 10 章  排序

10.3.2 快速排序– 基本思想:通过一趟排序,将待排序记录分割成独立

的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录进行排序,以达到整个序列有序

– 排序过程:对 r[s……t] 中记录进行一趟快速排序,附设两个指针 i 和 j ,设枢轴记录 rp=r[s] , x=rp.key

T. Hoare partition初始时令 i=s , j=t首先从 j 所指位置向前搜索第一个关键字小于 x 的记录,并

和 rp 交换再从 i 所指位置起向后搜索,找到第一个关键字大于 x 的记

录,和 rp 交换重复上述两步,直至 i==j 为止再分别对两个子序列进行快速排序,直到每个子序列只含有

一个记录为止

Page 19: 第 10 章  排序

例 初始关键字: 49 38 65 97 76 13 27 50

i j

x

ji

完成一趟排序: ( 27 38 13) 49 (76 97 65 50)

分别进行快速排序: ( 13) 27 (38) 49 (50 65) 76 (97)

快速排序结束: 13 27 38 49 50 65 76 97

4927

i ji ji j

49 65

ji

13 49

i j

49 97

i j

Page 20: 第 10 章  排序

int Partition(SqList &L, int low, int high) { // 算法 10.6(a)

// 交换顺序表 L 中子序列 L.r[low..high] 的记录,使枢轴记录到位, // 并返回其所在位置,此时,在它之前(后)的记录均不大(小)于它 KeyType pivotkey;

RedType temp;

pivotkey = L.r[low].key; // 用子表的第一个记录作枢轴记录 while (low<high) { // 从表的两端交替地向中间扫描 while (low<high && L.r[high].key>=pivotkey) --high;

temp=L.r[low]; L.r[low]=L.r[high]; L.r[high]=temp;

// 将比枢轴记录小的记录交换到低端 while (low<high && L.r[low].key<=pivotkey) ++low;

temp=L.r[low]; L.r[low]=L.r[high]; L.r[high]=temp;

// 将比枢轴记录大的记录交换到高端 }

return low; // 返回枢轴所在位置} // Partition

Page 21: 第 10 章  排序

int Partition(SqList &L, int low, int high) { // 算法 10.6(b)

// 交换顺序表 L 中子序列 L.r[low..high] 的记录,使枢轴记录到位, // 并返回其所在位置,此时,在它之前(后)的记录均不大(小)于它 KeyType pivotkey;

L.r[0] = L.r[low]; // 用子表的第一个记录作枢轴记录 pivotkey = L.r[low].key; // 枢轴记录关键字 while (low<high) { // 从表的两端交替地向中间扫描 while (low<high && L.r[high].key>=pivotkey) --high;

L.r[low] = L.r[high]; // 将比枢轴记录小的记录移到低端 while (low<high && L.r[low].key<=pivotkey) ++low;

L.r[high] = L.r[low]; // 将比枢轴记录大的记录移到高端 }

L.r[low] = L.r[0]; // 枢轴记录到位 return low; // 返回枢轴位置} // Partition

Page 22: 第 10 章  排序

void QSort(SqList &L, int low, int high) { // 算法 10.7

// 对顺序表 L 中的子序列 L.r[low..high] 进行快速排序 int pivotloc;

if (low < high) { // 长度大于 1

pivotloc = Partition(L, low, high);

// 将 L.r[low..high] 一分为二 QSort(L, low, pivotloc-1);

// 对低子表递归排序, pivotloc 是枢轴位置 QSort(L, pivotloc+1, high); // 对高子表递归排序 }

} // Qsort

void QuickSort(SqList &L) { // 算法 10.8

// 对顺序表 L 进行快速排序 QSort(L, 1, L.length);

} // QuickSort

Page 23: 第 10 章  排序

– 算法评价• 时间复杂度

– 最好情况(每次总是选到中间值作枢轴) T(n)=O(nlog2n)

– 最坏情况(每次总是选到最小或最大元素作枢轴) T(n)=O(n²)

• 空间复杂度:需栈空间以实现递归– 最坏情况: S(n)=O(n)

– 一般情况: S(n)=O(log2n)

T(n)=O(n²)

思考: 5 3 3 4 3 8 9 10 11

Page 24: 第 10 章  排序

24

10.4 选择排序10.4.1 简单选择排序

– 排序过程• 首先通过 n-1 次关键字比较,从 n 个记录中找出关键字最小的记录,将它与第一个记录交换

• 再通过 n-2 次比较,从剩余的 n-1 个记录中找出关键字次小的记录,将它与第二个记录交换

• 重复上述操作,共进行 n-1 趟排序后,排序结束

Page 25: 第 10 章  排序

例 初始: [ 49 38 65 97 76 13 27 ]

k

j j j j j j

k k

i=1 13 49

一趟: 13 [38 65 97 76 49 27 ]i=2

k k

j j j jj

27 38

二趟: 13 27 [65 97 76 49 38 ]

三趟: 13 27 38 [97 76 49 65 ]

四趟: 13 27 38 49 [76 97 65 ]

五趟: 13 27 38 49 65 [97 76 ]

六趟: 13 27 38 49 65 76 [97 ]

排序结束: 13 27 38 49 65 76 97

Page 26: 第 10 章  排序

void SelectSort(SqList &L) { // 算法 10.9 // 对顺序表 L 作简单选择排序。 for (i=1; i<L.length; ++i) { // 选择第 i 小的记录,并交换到位 j = SelectMinKey(L, i); // 在 L.r[i..L.length] 中选择 key 最小的记录 if (i!=j) L.r[i]←→L.r[j]; // 与第 i 个记录交换 }} // SelectSortint SelectMinKey(SqList L, int i){ min = i; for (j = i+1; j < =L.length; j++) if (L.r[j] <L.r[min]) min = j; return min;}

Page 27: 第 10 章  排序

– 算法评价• 时间复杂度

– 记录移动次数最好情况: 0最坏情况: 3(n-1)– 比较次数: )(

2

1)( 2

1

1

nninn

i

• 空间复杂度: S(n)=O(1)

T(n)=O(n²)

思考: 5 8 5 2 9

Page 28: 第 10 章  排序

28

10.4.2 堆排序– 堆的定义: n 个元素的序列 (k1,k2,……kn) ,当且仅当满足下列关系时,称之为堆

或 (i=1,2,…...n/2)kik2i

kik2i+1

kik2i

kik2i+1

例 ( 96 , 83 , 27 , 38 , 11 , 9 ) 例 ( 13 , 38 , 27 , 50 , 76 , 65 ,49 , 97 )96

27

91138

83

13

2738

49657650

97可将堆序列看成完全二叉树,则堆顶元素(完全二叉树的根)必为序列中n 个元素的最小值或最大值

Page 29: 第 10 章  排序

– 堆排序:将无序序列建成一个堆,得到关键字最小(或最大)的记录;输出堆顶的最小(大)值后,使剩余的 n-1 个元素重又建成一个堆,则可得到 n 个元素的次小值;重复执行,得到一个有序序列,这个过程叫 ~

– 堆排序需解决的两个问题:1.如何由一个无序序列建成一个堆?2.如何在输出堆顶元素之后,调整剩余元素,使之成为

一个新的堆?– 第 2 个问题解决方法——筛选

• 输出堆顶元素之后,以堆中最后一个元素替代之;然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换;重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”

Page 30: 第 10 章  排序

例13

2738

49657649

97

97

2738

49657649

13

输出: 13

27

4938

97657649

13

输出: 13

97

4938

27657649

13

输出: 13 27

38

4949

27657697

13

输出: 13 27

65

4949

27387697

13

输出: 13 27 38

Page 31: 第 10 章  排序

49

6549

27387697

13

输出: 13 27 38

76

6549

27384997

13

输出: 13 27 38 49

49

6576

27384997

13

输出: 13 27 38 49

97

6576

27384949

13

输出: 13 27 38 49 49

65

9776

27384949

13

输出: 13 27 38 49 49

97

6576

27384949

13

输出: 13 27 38 49 49 65

Page 32: 第 10 章  排序

76

6597

27384949

13

输出: 13 27 38 49 49 65

97

6576

27384949

13

输出: 13 27 38 49 49 65 76

97

6576

27384949

13

输出: 13 27 38 49 49 65 76 97

Page 33: 第 10 章  排序

P282 筛选算法 10.10Type SqList HeapType;Void HeapAdjust(HeapType &H, int s, int m){ //H.r[s+1…m] 为大顶堆 rc = H.r[s]; for (j=2*s; j<=m; j*=2) {// 沿key较大的孩子结点向下筛选 if (j<m && LT(H.r[j].key, H.r[j+1].key)) ++j; if (!LT(rc.key, H.r[j].key)) break; H.r[s] = H.r[j]; // 大值上移到父结点 s = j; } H.r[s] = rc; }

Page 34: 第 10 章  排序

– 第 1 个问题解决方法• 从无序序列的第 n/2 个元素(即此无序序列对应的完全二叉树的最后一个非终端结点)起,至第一个元素止,进行反复筛选

例 含 8 个元素的无序序列( 49 , 38 , 65 , 97 , 76 , 13 , 27 , 49 )49

6538

27137697

49

49

6538

27137649

97

49

1338

27657649

97

49

1338

27657649

97

13

2738

49657649

97

Page 35: 第 10 章  排序

堆排序 算法描述 10.11

Void HeapSort (HeapType &H)

{

for (i = H.length/2; i>0; --i) // 建成大顶堆 HeapAdjust(H, i, H.length);

for (i = H.length; i>1; --i) {

temp = H.r[1]; H.r[1] = H.r[i]; H.r[i] = temp;

HeapAdjust(H, 1, i-1); //调整为大顶堆 }

}

– 算法评价• 时间复杂度:最坏情况下 T(n)=O(nlogn)• 空间复杂度: S(n)=O(1)

Page 36: 第 10 章  排序

36

10.5 归并排序• 归并——将两个或两个以上的有序表组合成一

个新的有序表,叫 ~• 2- 路归并排序 排序过程

– 设初始序列含有 n 个记录,则可看成 n 个有序的子序列,每个子序列长度为 1

– 两两合并,得到 n/2 个长度为 2 或 1 的有序子序列

– 再两两合并,……如此重复,直至得到一个长度为n 的有序序列为止

Page 37: 第 10 章  排序

例 初始关键字: [49] [38] [65] [97] [76] [13] [27]

一趟归并后: [38 49] [65 97] [13 76] [27]

二趟归并后: [38 49 65 97] [13 27 76]

三趟归并后: [13 27 38 49 65 76 97]

Page 38: 第 10 章  排序

算法描述 10.12

Void Merge(RcdType SR[]. RcdType &TR[], int i, int m, int n)

{ // SR[i..m] 和 SR[m+1..n] 分别有序 , 归并到 TR[i..n]

for (j = m+1, k=i; i<=m && j<=n; ++k) {

if LQ(SR[i].key, SR[j].key) TR[k] = SR[i++];

else TR[k] = SR[j++];

} // 将 SR 中记录由小到大地并入 TR

if (i <=m) TR[k..n] = SR[i..m];

if (j <=n ) TR[k..n] = SR[j..n];

}

Page 39: 第 10 章  排序

算法 10.13

void MSort( RcdType SR[], RcdType &TR1[], int s, int t)

{

if ( s==t) TR1[s] = SR[s];

else {

m=(s+t)/2; // 将 SR[s..t]平分为 SR[s..m] 和 SR[m+1..t]

MSort(SR, TR2, s, m); // 递归 MSort(SR, TR2, m+1, t); // 递归 Merge(TR2, TR1, s, m, t); // 归并 }

}

算法 10.14

void MergeSort(SqList &L)

{

MSort(L.r, L.r, 1, L.length);

}

• 算法评价– 时间复杂度: T(n)

=O(nlog2n)

– 空间复杂度: S(n)=O(n)

Page 40: 第 10 章  排序

各种内部排序方法的比较和选择 简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。

影响排序效果的因素

 因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法应综合考虑下列因素:  ①待排序的记录数目n;  ②记录的大小 ( 规模 );  ③关键字的结构及其初始状态;  ④对稳定性的要求;  ⑤语言工具的条件;  ⑥存储结构;  ⑦时间和辅助空间复杂度等。

Page 41: 第 10 章  排序

不同条件下,排序方法的选择

(1)若 n较小 ( 如 n≤50),可采用直接插入或直接选择排序。

 当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。

(2)若文件初始状态基本有序 (指正序 ),则应选用直接插人、冒泡或随机的快速排序为宜;

(3)若 n较大,则应采用时间复杂度为 O(nlgn)的排序方法:快速排序、堆排序或归并排序。

 

Page 42: 第 10 章  排序

快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;

堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序和选择排序、希尔排序都是不稳定的。

 若要求排序稳定,则可选用归并排序、冒泡排序。但本章介绍的从单个记录起进行两两归并的排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定的,所以改进后的归并排序仍是稳定的。

Page 43: 第 10 章  排序

43

作业

10.1

10.2

10.3

10.4