欢迎来到数据结构的世界
博客主页:卿云阁欢迎关注点赞收藏⭐️留言
本文由卿云阁原创!
作者水平很有限,如果发现错误,请留言轰炸哦!万分感谢!
目录
排序的基本概念
插入排序
希尔排序(ShellSort)
冒泡排序
快速排序
简单选择排序
堆排序
堆的插入和删除操作
归并排序
基数排序
外部排序
败者树
置换-选择排序
最佳归并树
排序的基本概念
排序的概念:让关键字有序(关键字是可以重复的)
时间复杂度,空间复杂度,算法的稳定性
(相同的关键字的相对位置是否发生变化,如下图的紫色的3和红色的3,稳定的排序算法不一定比不稳定的好,要看实际的需求)
排序算法的分类
内部排序:数据全部放在内存(时间、空间复杂度)
外部排序:数据太多,无法全部放在内存(读写磁盘的次数)
(比如我们自己定义的数组,就是放在内存中的,对于内部排序来说,排序都是在内存中进行的,内存是一个高速的设备,所以我们更关注时间,空间复杂度,对于外部排序我们还需要考虑读写磁盘的次数)
插入排序
算法思想:将待排记录(按照关键字的大小)插入到有序序列。
- 49是当前的有序序列,38为待查元素,其余为无序序列
- 处理38这个元素,把38与有序序列进行对比,比38大的元素都向后移动
- 处理65这个元素,65>49,把65放回原来的位置。
- 处理97这个元素,97>65,把97放回原来的位置。
- 处理76这个元素,76<97,97后移,76>65,所以76插入到65的后面。
- 处理13这个元素,13<97, 97后移,13<76,76后移,13<65,65后移,13<49,49后移,13<38,38后移。
- 处理27这个元素,27<97, 97后移,27<76, 76后移,27<65,65后移,27<49,49后移,27<38,38后移。
- 处理49这个元素,比它大的元素全部右移,但是和它相等的元素不移动,这样做可以保证算法的稳定性。
代码实现
假设此时我们需要排序的是A[ ],有n个数据元素,变量i表示待排序的元素。
如果当前处理的元素A[i]小于A[i-1],需要移动前面的元素,用中间变量temp保存当前待排元素的质A[i],从i的左边j(i-1),这个元素开始,检查它前面的元素A[j],是否比temp大,所有比它大的元素都需要向右移动,当j小于0,或者在有序序列中A[j]找到第一个小于temp的值,此时的A[j+1]就是temp的位置。
#include
//函数声明 void InsertSort(int A[], int n); void print(int m, int A[], int n); //直接插入排序 void InsertSort(int A[], int n) { int i, j, temp; for (i = 1; i < n; i++) { print(i, A, n); if (A[i] < A[i - 1]) { temp = A[i]; for (j = i - 1; j >= 0 && A[j] > temp; j--) { A[j + 1] = A[j]; } A[j + 1] = temp; } } } //输出每次排序的结果 void print(int m, int A[], int n) { int i; printf("第%d轮:", m); for (i = 0; i < n; i++) { printf("%d ", A[i]); } printf("\n"); } int main() { int A[8] = {49, 38, 65, 97, 76, 13, 27, 49}; int i; printf("初始 :"); for (i=0; i<8; i++) { printf("%d ", A[i]); } printf("\n"); InsertSort(A, 8); return 0; } 算法实现(带哨兵)
实际存储的位置是从1开始的,0号位置会作为哨兵,和之前不一样的是我们会把待插入元素放入到哨兵的位置。
优点:不需要每次循环都判断j>=0。
//直接插入排序(带哨兵) void InsertSort(int A[], int n) { int i, j, temp; for (i = 2; i < n; i++) { if (A[i] < A[i - 1]) { A[0] = A[i]; for (j = i - 1; A[j] > A[0]; j--) { A[j + 1] = A[j]; } A[j + 1] = A[0]; } } }
算法效率分析
空间复杂度:O(1)
辅助变量i,j,temp(A[0])都是常数级的和n无关。
时间间复杂度:
在进行插入排序的时候,我们都是从第2个元素开始依次往后处理,每个元素都需要进行依次for循环,我们总共需要n-1次处理,每趟都需要进行关键字的对比,可能需要进行元素的移动。
平均时间复杂度=1/2(最好时间复杂度+最坏时间复杂度)
优化--折半插入排序
之前,当我们在处理一个元素的时候,都是采用顺序查找的方式找到它的位置,现在我们可以使用折半查找到相应的位置,再移动元素。
当前处理55,A[0]=55,mid指向的值50<55,55应该插入50的右边,70>55,55应该插入70的左边,60>55,55应该插入60的左边。
当low>high,将[low,i-1]右移,把A[0]的位置放到low所指向的位置。
处理60这个元素,50<60, 右边,60<=60,元素相等,为了保证稳定性,继续在右边查找,
70>60, 左边,low>high停止折半查找。,将[low,i-1]右移,把A[0]的位置放到low所指向的位置。
比起直接插入排序,折半插入排序减少了关键字的比较次数,但是移动元素的次数不变,时间复杂度O(n2)。
代码实现:
#include
//函数声明 void InsertSort(int A[], int n); void print(int m, int A[], int n); //折半插入排序(带哨兵) void InsertSort(int A[], int n) { int i, j, low,high,mid; for (i = 2; i <= n; i++) { low=1;high=i-1; A[0]=A[i]; while(low<=high) { mid=(low+high)/2; if(A[mid]>A[0]) high=mid-1; else low=mid+1; } for(j=i-1;j>=low;j--) A[j+1]=A[j]; A[low]=A[0]; print(i-1, A, n); } } //输出每次排序的结果 void print(int m, int A[], int n) { int i; printf("第%d轮:", m); for (i = 1; i <=n; i++) { printf("%d ", A[i]); } printf("\n"); } int main() { int A[12] = {999,20, 30, 40, 50, 60, 70, 80, 55, 60, 90, 10}; int i; printf("初始 :"); for (i=1; i<12; i++) { printf("%d ", A[i]); } printf("\n"); InsertSort(A, 11); return 0; } 对链表进行插入排序
当我们要移动一个元素的时候,只要移动几个指针就可以了,但是关键字的对比仍然是O(n2)这样一个数量级。
希尔排序(ShellSort)
(代码考察频率不高)
通过上面的分析我们可以发现基本有序的时候,直接插入排序的效率较高。
希尔排序的思想:先追求表中元素的部分有序,再逐渐逼近全局有序。
我们会把表分割成很多的子表,在每一趟的处理中,我们都会设置一个增量d,把表分成类似各个子表,对子表进行直接插入排序,直到d=1为止。
第一趟:我们把距离为d=4的元素看成是一个子表,对各个子表进行直接插入排序,
第二趟:d2=d1/2=2(希尔本人建议将增量减小一半)
子表1,直接插入排序
子表2,直接插入排序
第三趟:d3=d2/2=1
子表1,直接插入排序
代码实现
变量d表示每一次的增量,初始状态下它的值等于d=(n/2),最后的状态是d=1,d的变化是(d=d/2)
变量i表示每个子表的最后元素的位置,初始状态下i=d+1,最后的状态是i=n,i的变化是(i++,处理下一个子表)
第一趟:d=4
子表1:i=d+1指向76,76(A[i])>49(A[i-d]),不需要进行移动。
子表2:i++指向13,13(A[i])<38(A[i-d]),把13A[i]放入A[0],j=i-d(j表示该表倒数第二个元素的位置),38A[j]后移到j+d的位置,j=j-d(检查子表前面是否还有元素,j=j-d=-1),此时j<0,跳出for循环。
子表3:类似,当i=n是进行最后一次处理。
代码实现
#include
//函数声明 void InsertSort(int A[], int n); void print(int m, int A[], int n); //希尔排序 void ShellSort(int A[], int n) { int d,i,j; for(d=n/2;d>=1;d=d/2) { for(i=d+1;i<=n;i++) { if(A[i]0&&A[0] 算法性能
空间间复杂度:O(1)
最坏时间复杂度:O(n2)
稳定性:不稳定的
适用性:仅用于顺序表,
冒泡排序
算法思想
算法流程:(从前开始)
第一趟:
49<38,交换,49<65,不需要交换,65<97,不需要交换,97>76,交换,97>13,交换,97>27,交换,97>49,交换。
97这个最大的元素放到了后面
代码实现:
j和j+1分别指向当前需要对比的两个元素,if(A[j]>A[j+1],交换位置。
第1趟:
j的值应该是从 [0-------n-2]
第2趟:
j的值应该是从 [0-------n-3]
-----
每一趟都会确定一个最大值,n个元素要进行n-1轮的对比,我们用变量i来表示是第i趟。
i的取值应该是从 [1-------n-1]
j的取值应该是从 [0-------n-i-1]
#include
#include //函数声明 void InsertSort(int A[], int n); void print(int m, int A[], int n); void Swap(int &a,int &b); //冒泡排序 void BubbleSort(int A[], int n) { int i,j; for(i=1;i<=n-1;i++) { bool flag=false; for(j=0;j<=n-i-1;j++) if(A[j]>A[j+1]) { flag=true; Swap(A[j],A[j+1]); } if(flag==false) return ; print(i, A, n); } } //定义交换函数 void Swap(int &a,int &b) { int temp; temp=a; a=b; b=temp; } //输出每次排序的结果 void print(int m, int A[], int n) { int i; printf("第%d趟:", m); for (i = 0; i 实际上:第5趟就可以保证有序了。
算法性能分析
空间复杂度是:O(1)
时间复杂度:
最好情况(有序):
我们进行第一趟冒泡排序,从第一个位置开始检查,发现所有的元素都不需要交换位置,算法直接结束。比较次数是n-1,交换次数是0次,时间复杂度O(n)。
最坏情况(逆序):(每次交换都需要移动元素3次)
比较次数是=交换次数,时间复杂度O(n2)
平均情况:O(n2)
稳定性:稳定的
适用性:顺序表和链表都可以
快速排序
(考察代码频率高)
一次划分只能确定一个元素的最终位置,但是一趟排序,可以确定多个元素的最终位置。
比如下图给了8个元素的表。L[1----n]
第一趟:选取首元素49作为基准,划分2部分(左小右大)
L[1----k-1] L[k] L[k+1----n] 用i和j分别指向这个表的头和尾的位置,选择49作为基准元素,让i和j指针开始向中间移动,保证i的右边大于49,j的左边小于49。
接下来对左右子表再进行划分
对于左表(0-2),此时左右两边只剩下一个元素,不需要再继续划分。
对于右表(4-7),此时左右两边只剩下一个元素,不需要再继续划分。
对于第二层左表(4-5)继续划分。
最后还剩下65和47这两个元素,显然此时最终位置也确定了。
代码实现:
我们定义了一个数组A[ ]
此时需要排序的范围是[0-----7],QuickSort(A,0,7)
low小于high,进入if函数的处理。
int p=Partition(A,low,high),这个函数实现的事情是进行一次划分。系统还会记录执行到了第几行,当前是96行(Partition函数返回之后,让我们知道接下来执行的是第几行)
进入Partition函数处理 Partition(A,0,7)
int pivot=A[low] 把第一个元素49作为基准值。
while(low
(比较)检查low
=pvot,high-- 这个while循环要做的是,找到一个比基准值更小的元素,跳出该循环。 (移动)A[low]=A[high] 把high指向的值放到low指向的位置 把27放到0号位置
(比较)检查low
(移动)A[high]=A[low] 把low指向的值放到high指向的位置 把65放到6号位置
low
直到low大于high,不满足low
A[low]=pivot 把基准位置放到,low的位置,此时我们就实现了一次划分。返回low的值。
第一层的Partition函数执行结束,回到QuickSort函数的位置,最终以3为基准划分成左右两个部分,接下来让函数继续执行,
接下来我们先处理左子表 QuickSort(A,low,p-1) QuickSort(A,0,2),进入第二层的QuickSort函数。第一层的QuickSort执行到97行,调用Partition(A,0,2),
Partition函数执行结束,返回1,第2层的QuickSort执行到97行,对左子表进行划分,第3层QuickSort(A,0,0),由于此时low=high,说明只剩下了一个元素,不满足low
下面的执行类似
算法效率分析
时间复杂度O(n*递归深度)
空间复杂度O(n*递归深度)
二叉树的层数就是递归的层数,
最好的情况是划分的区域是均匀的,所以如果初始序列有序或者逆序,则效率最差。
稳定性:不稳定
#include
//函数声明 void InsertSort(int A[], int n); void print(int m, int A[], int n); void QuickSort(int A[],int low,int high); int Partition(int A[],int low,int high); //快速排序 void QuickSort(int A[],int low,int high) { if(low =p) high--; A[low]=A[high]; while(low
简单选择排序
每一趟排序选择关键字最小的元素,加入有序序列。
对于n个元素,需要进行n-1次处理
代码实现:
#include
//函数声明 void SelectSort(int A[],int n); void Swap(int &a,int &b); void print(int m, int A[], int n); //简单选择排序 void SelectSort(int A[],int n) { int i,j,min; for(i=0;i
堆排序
(考察频率很高)
这种排序算法的实现基于一种叫堆的数据结构。
大根堆:根>=左右
小根堆:左右<=根
对于一个初始序列,如何把它建立成大根堆?
我们要保证所有根节点的值大于左右孩子-----检查所有非终端结点,不满足则进行调整(i<=n/2向下取整)。
下面这个例子中有8个节点,i<=4的节点是非终端结点,1,2,3,4。
从后往前处理这些结点,第一个被处理的是4号结点,根据它的下标找到左孩子(2i=8),右孩子(2i+1=9),32>9,与更大的孩子互换。
处理3号结点,87>78,互换。
处理2号结点,17<32,17<45,17与更大的孩子45互换。
处理1号结点,53<87,53与更大的孩子87互换。
53发生下坠,相同方法继续调整。(小元素不断下坠)
代码实现:
BuildMaxHeap(int A[ ],int len)
我们需要从n/2开始依次往后处理,把所有的分支结点,都调整为大根堆HeapAust。
for(i=len/2;i>0;i--) HeapAdjust(A,i,len)
HeapAdjust(int A[ ],int k,int len)
现在处理53这个结点
第一步:当前处理结点的值保存在A[0]---A[0]=A[k]
第二步:i指向左右孩子较大的元素
左孩子的位置:i=2*k 右孩子的位置:i+1
比较左右孩子的大小(A[i] A[i+1])) 如果右孩子更大,让i++,此时i指向的是更大的孩子。只有当i
第三步:
如果当前结点A[0]的值大于A[i],说明当前的结点满足(根大于左右的),说明这个结点不需要处理跳出整个for循环。(k是有可能插入53的位置)
如果当前结点A[0]的值小于A[i],说明当前的结点需要调整,
A[k]=A[i] 把最大的孩子放到双亲结点
k=i 待确定是什么元素的位置
i=i*2 更新i的值,指向k的左孩子
第一个if语句 让i指向最大的孩子,A[i]>A[0],A[k]=A[i],k=i
i=i*2>len,说明这个位置没有左右孩子了,A[k]=A[0]
#include
//函数声明 void BuildHeap(int A[],int len); void HeapAdust(int A[],int k,int len); //建立一个大顶堆 void BuildHeap(int A[],int len) { int i; for(i=len/2;i>0;i--) HeapAdust(A,i,len); } void HeapAdust(int A[],int k,int len) { A[0]=A[k]; int i; for(i=2*k;i<=len;i=i*2) { if(i A[i]) i++; if(A[0]>=A[i]) break; else { A[k]=A[i]; k=i; } } A[k]=A[0]; } int main() { int A[9] = {999, 53, 17, 78, 9, 45, 65, 87,32}; int i; printf("初始 :"); for (i=1; i<9; i++) { printf("%d ", A[i]); } printf("\n"); BuildHeap(A,8); printf("建立大顶堆 :"); for (i=1; i<8; i++) { printf("%d ", A[i]); } return 0; } 基于大根堆进行排序
每一趟将堆顶元素加入到有序子序列(与待排序列最后一个元素交换)
第一趟:
- 把最大的元素移动到末尾
- 再次调整为大根堆(小元素不断的下坠,len-1)
代码实现:
BuildHeap(A,len) 建立一个大根堆
刚开始i的值=len, 指向我们的堆底的元素。
Swap(A[i],A[1]) 把堆顶元素和堆底元素进行交换
HeapAdjust(A,1,i-1) 调整成大顶堆
#include
//函数声明 void BuildHeap(int A[],int len); void HeapAdjust(int A[],int k,int len); void HeapSort(int A[], int len); void Swap(int &a,int &b); //建立一个大顶堆 void BuildHeap(int A[],int len) { int i; for(i=len/2;i>0;i--) HeapAdjust(A,i,len); } void HeapAdjust(int A[],int k,int len) { A[0]=A[k]; int i; for(i=2*k;i<=len;i=i*2) { if(i A[i]) i++; if(A[0]>=A[i]) break; else { A[k]=A[i]; k=i; } } A[k]=A[0]; } void HeapSort(int A[], int len) { BuildHeap(A, len); int i; for(i = len; i > 1; i--) { Swap(A[i], A[1]); HeapAdjust(A, 1, i-1); } } void Swap(int &a,int &b) { int temp=a; a=b; b=temp; } int main() { int A[9] = {999, 53, 17, 78, 9, 45, 65, 87,32}; int i; printf("初始 :"); for (i=1; i<9; i++) { printf("%d ", A[i]); } printf("\n"); BuildHeap(A,8); printf("建立大顶堆 :"); for (i=1; i<8; i++) { printf("%d ", A[i]); } HeapSort(A,8); printf("\n"); printf("堆排序之后 :"); for (i=1; i<8; i++) { printf("%d ", A[i]); } return 0; } 算法效率分析
我们需要调整9这个元素
A[i]>A[i+1] 找最大的孩子,进行1次关键字的对比
A[0]>A[i] 进行1次关键字的对比
所以一个结点要有左右孩子的话,需要对比两次关键字,如果只要左孩子只要进行1次关键词的对比。
堆的插入和删除操作
假设我们要插入的值是13,首先把它放到表尾的位置。
找到父节点32,13<32,13上升一层,13<17,上升, 13>9符合要求,总共比较了3次。
如果要插入46这个元素,只需要进行一次关键字的对比。
46不断的下坠
归并排序
把两个或者多个已经有序的序列合成一个
i指向的值
(比较),把7放入k的位置(赋值),j和k的指向向后移(指向后移) 12>10,10放进1号,j和k的指向向后移。
12<21,12放进2号,i和k的指向向后移。
16<21,16放进3号,i和k的指向向后移。
后面的操作类似,直到有边的数组已经遍历完, 直接把剩余的元素加入到总表。
"2"路归并
每次选出一个小元素需要对比1次。
"4"路归并
每次选出一个小元素需要对比3次。
归并排序手算模拟
核心:把数组内两个有序序列归并成一个。
代码实现:目的是和并[low-mid]和[mid+1,high]这两个子序列
定义1个辅助数组B和A的大小是相同的:
int *B=(int *)malloc(n*sizeof(int))
把[low-high]内的元素复制到B数组
for(k=low;k<=high;k++)
B[k]=A[k]
i的取值是【low-mid】
j的取值是【mid+1-high】
for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++)
if(B[i]<=B[j])
A[k]=B[i++]; //如果值相等优先选择i指向
else
A[k]=B[j++];
j超过high所指向的范围,跳for循环,说明第二个数组已经合并完了。
用两个while循环来检查是否合并完。没有合并完就把剩余的元素放到总表中。
while(i<=mid) B[k++]=A[i++];
while(j<=high) B[k++]=A[j++]
完整代码:
对A[ ]进行归并排序,范围是[low,high]
计算mid,拆分成两个子序列[low,mid],[mid+1,high]
int mid=(low+high)/2;
对左半部分和右半部分递归的进行排序
MergSort(A,low,mid)
MergSort(A,mid+1,high)
然后进行归并
Merg(A,low,mid,high)
算法效率分析
树高是h,需要进行h-1趟排序
基数排序
我们要把它排成递减的序列
所有的关键字都可以拆分成3个部分(百位 十位 个位)取值只有可能是(0--9),所以我们建立了10个辅助队列。
第一趟:以个位进行分配,比如520。
第一趟:收集
第二趟:以十位进行分配。
第二趟:收集
第三趟:收集
基数排序的应用
外部排序
外部排序原理
采用归并排序的思想和方法
1.数据初始状态
2.将(36、8、26)(42、9、48)分别存入输入缓冲区1、输入缓冲区2
3.将输入缓冲区1和输入缓冲区2的数据进行递增排序
4.将输入缓冲区1和输入缓冲区2的数据通过输出缓冲区逐一写入外存,形成一个有序归并段
6.一共构造了8个有序的归并段。总共需要进行16次读操作和16次写操作
7.第一次归并:读入归并段1和归并段2中的第一块磁盘(相对最小),进行排序。
8.依次找出这两个输入缓冲区中最小元素,并将其移动到输出缓冲区中,当输出缓冲区满,则写入外存(1、8、9)
9.继续找出这剩余元素中的最小元素,直到某一个缓冲区中空,则读入其所属归并段的后一个内存块的数据,并继续进行上述操作。直到两个缓冲区都空,且归并段1和归并段2中的元素全部读入内存,此时归并段1和归并段2就得到了一个有序的递增序列。
输入缓冲区1空,输入归并段1的第二块内存
10.对剩余的六个归并段进行上述操作,八个归并段→四个归并段
11.第二次归并:继续采用此方法依次取出归并段1和归并段2的各个块进行排序操作,四个归并段→两个归并段 原归并段1、2排序形成归并段1。
原归并段3、4排序形成归并段2
12.第三次归并:继续排序归并段1、2,形成最后的有序递增序列
时间开销分析
上述外部排序中:形成初始归并段→第一次归并(8 ~ 4)→第二次归并(4 ~ 2)→第三次归并(2 ~ 1)(每个过程都需要读和写16次,共32 + 32 * 3 = 128次)
总时间开销 = 内部排序所需时间 + 内部归并所需时间 + 外部读写所需时间
改用四路归并:初始化归并段→第一次归并(8 ~ 2)→第二次归并(2 ~ 1)
需要读写次数:32 + 32 * 2 = 96
但是,与此同时,缓冲区的数量也要变成四个(k路归并需k个缓冲区)
结论:1.对于 r 个初始归并段进行 k 路归并,需要归并趟数 = (向上取整,归并树高度)
2.提升外部排序的速度、减少读写磁盘的速度的方法:提高 k 值,降低 r 值。提高 r 值:增加归并段长度
但是,提高 k 有负面影响:
A.需要的缓存空间升高(k路归并需k个缓冲区)
B.内部归并的所需时间提高(选出最小关键字需要进行k - 1次比较)
降低 r 值
败者树
败者树的构造
如果此时的冠军突然弃赛,突然有别的选手参加,如何找新的冠军?
1.将每个归并段的第一个元素作为叶子结点加入败者树中
2.从左至右、从上往下的更新分支节点的信息:判断其左右子树的大小,除了根节点(最上面那个结点)记录冠军来自哪个归并段外,其余各分支节点记录的是失败者来自哪个归并段
3.取出最小的元素1后,从其所属的归并段中取出下一个元素6,依次与从叶子结点到根节点的各个结点所记录的败者信息进行对比。
引进败者树后,选出最小的关键字,仅需log2k次比较(向上取整)
置换-选择排序
1.初始状态
归并段1:
2.4、6、9依次加入内存工作区中,(4、6、9)选择最小的元素4,输出4并更改MIN = 4
3.加入7,(7、6、9)选择最小元素6 > MIN = 4,输出6并更改MIN = 6
4.加入13,(7、13、9)选择最小元素7 > MIN = 6,输出7并更改MIN = 7
5.加入11,(11、13、9)选择最小元素9 > MIN = 7,输出9并更改MIN = 9
6.加入16,(11、13、16)选择最小元素11 > MIN = 9,输出11并更改MIN = 11
8.加入14,(14、13、16)选择最小元素13 > MIN = 11,输出13并更改MIN = 13
9.加入10,(14、10、16)选择最小元素10 < MIN = 13,标记13为不可输出,选择第二小的元素14 > MIN = 13,输出14并更改MIN = 14
10.加入22,(22、10、16)选择最小元素16 > MIN = 14,输出16并更改MIN = 16
11.加入30,(22、10、30)选择最小元素22 > MIN = 16,输出并更改MIN = 22
12.加入2,(2、10、30)选择最小元素2 < MIN = 22,标记2为不可输出,选择第三小的元素30 > MIN = 22,输出30并更改MIN = 30
13.加入3,(2、10、3)选择最小元素3 < MIN = 30,标记2为不可输出,此时,输出缓冲区中的三个元素都是不可输出元素,则第一个归并区到上一个输出元素为止(4、6、7、9、11、13、14、16、22、30)
归并段2:
14.(2、10、3)选择最小元素2,输出2并更改MIN = 2
15.加入19,(19、10、3)选择最小元素3 > MIN = 2,输出3并更改MIN = 3
16.加入20,(19、10、20)选择最小元素10 > MIN = 3,输出10并更改MIN = 10
17.加入17,(19、17、20)选择最小元素17 > MIN = 10,输出17并更改MIN = 17
18.加入1,(19、1、20)选择最小元素1 < MIN = 17,标记1为不可输出,选择第二小的元素19 > MIN = 17,输出19并更改MIN = 19
19.加入23,(23、1、20)选择最小元素20 > MIN = 19,输出20并更改MIN = 20
20.加入5,(23、1、5)选择最小元素5 < MIN = 20,标记5为不可输出,选择第三小的元素23 > MIN = 23,输出23并更改MIN = 23
21.加入36,(36、1、5)选择最小元素36 > MIN = 36,输出36并更改MIN = 36
22.加入22,(12、1、5)选择最小元素12 < MIN = 36,标记12为不可输出时,输出缓冲区中的三个元素都是不可输出元素,则第二个归并区到上一个输出元素为止(2、3、10、17、19、20、23、36)
第三个归并段:
23.(12、1、5)选择最小元素1,输出1并更改MIN = 1
24.加入18,(12、18、5)选择最小元素5 > MIN = 1,输出5并更改MIN = 5
25.加入21,(12、18、21)选择最小元素12 > MIN = 5,输出12并更改MIN = 12
26.加入39,此时,待排序文件空,将内存工作区中的剩余数据按序输出,即18、21、39,则第三个归并段为(1、5、12、18、21、39)
最佳归并树
1.性质和构造完全相同于哈弗曼树
2.与哈弗曼树的区别:
k叉树,其中k > 2时:需要判断是否能满足构造完全k叉树,若不满足,则需要添加长度为0的“虚段”
①若(初始归并段数量 - 1) % (k - 1) = 0,则能构成完全k叉树
②若(初始归并段数量 - 1) % (k - 1)= u ≠ 0,则说明需要添加(k - 1)- u 个虚段才能构成完全二叉树
假设我们现在要进行8路归并,初始段的数量等于19,18%7=4,7-4=3。