前言 “排序”学习提纲。
代码模板
排序的基本概念 相关概念
关键字可以是主关键字、次关键字和关键字组合等
多关键字可以转换为单关键字
稳定性:排序前后,两个或两个以上相同的关键字,相对位置没有变化,称算法是稳定的,反之是不稳定的
若关键字无重复,则排序结果唯一 若关键字有重复,则排序结果不唯一,需要考虑算法的稳定性
判断方式: 若存在相邻地比较和移动/交换关键字,则是稳定的 若存在跳跃地比较和移动/交换关键字,则是不稳定的
类型(依据思想)
插入类排序
交换类排序
选择类排序
归并类排序
基数类排序
插入类排序:
交换类排序:
选择类排序:
归并类排序:
基数类排序:
类型(依据复杂度)
简单排序:
复杂排序:
类型(依据数据是否完全在内存中)
内部排序:
插入类排序
交换类排序
选择类排序
归并类排序
基数类排序
基本操作
注意:基数类排序基于多关键字比较
时间复杂度->比较和移动的次数
插入类排序 直接插入排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 void insertSort (vector<ELEM_TYPE> &elems) { int i = 0 ; int j = 0 ; int temp = 0 ; for (i = 1 ; i < elems.size (); ++i) { temp = elems[i]; for (j = i - 1 ; j >= 0 ; --j) { if (temp < elems[j]) { elems[j + 1 ] = elems[j]; } else { break ; } } elems[j + 1 ] = temp; } return ; }
折半插入排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 void binarySort (vector<ELEM_TYPE> &elems) { int i = 0 ; int j = 0 ; int temp = 0 ; int left = 0 ; int right = elems.size () - 1 ; int mid = 0 ; for (i = 1 ; i < elems.size (); ++i) { temp = elems[i]; left = 0 ; right = i - 1 ; while (left <= right) { mid = left + (right - left) / 2 ; if (temp == elems[mid]) { left = mid + 1 ; } else if (temp > elems[mid]) { left = mid + 1 ; } else if (temp < elems[mid]) { right = mid - 1 ; } } for (j = i - 1 ; j >= right + 1 ; --j) { elems[j + 1 ] = elems[j]; } elems[right + 1 ] = temp; } return ; }
希尔排序/缩小增量排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 void shellSort (vector<ELEM_TYPE> &elems) { int i = 0 ; int j = 0 ; int temp = 0 ; for (int step = elems.size () / 2 ; step >= 1 ; step /= 2 ) { for (i = step; i < elems.size (); ++i) { temp = elems[i]; for (j = i - step; j >= 0 ; j -= step) { if (temp < elems[j]) { elems[j + step] = elems[j]; } else { break ; } } elems[j + step] = temp; } } return ; }
交换类排序 冒泡排序/起泡排序 简单版 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void bubbleSort1 (vector<ELEM_TYPE> &elems) { int i = 0 ; int j = 0 ; int temp = 0 ; for (i = 0 ; i < elems.size () - 1 ; ++i) { for (j = i + 1 ; j < elems.size (); ++j) { if (elems[i] > elems[j]) { temp = elems[i]; elems[i] = elems[j]; elems[j] = temp; } } } return ; }
冒泡排序 正版 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void bubbleSort2 (vector<ELEM_TYPE> &elems) { int i = 0 ; int j = 0 ; int temp = 0 ; for (i = 0 ; i < elems.size () - 1 ; ++i) { for (j = elems.size () - 1 ; j > i; --j) { if (elems[j - 1 ] > elems[j]) { temp = elems[j - 1 ]; elems[j - 1 ] = elems[j]; elems[j] = temp; } } } return ; }
冒泡排序 改进版 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 void bubbleSort3 (vector<ELEM_TYPE> &elems) { int i = 0 ; int j = 0 ; int temp = 0 ; bool flag = false ; for (i = 0 ; i < elems.size () - 1 ; ++i) { for (j = elems.size () - 1 ; j > i; --j) { if (elems[j - 1 ] > elems[j]) { temp = elems[j - 1 ]; elems[j - 1 ] = elems[j]; elems[j] = temp; flag = true ; } } if (flag == false ) { return ; } } return ; }
快速排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 void quickSort (vector<ELEM_TYPE> &elems, int left, int right) { int i = left; int j = right; if (left < right) { ELEM_TYPE privot = elems[left]; while (i < j) { while (i < j) { if (elems[j] >= privot) { --j; } else { break ; } } elems[i] = elems[j]; while (i < j) { if (elems[i] <= privot) { ++i; } else { break ; } } elems[j] = elems[i]; } elems[i] = privot; quickSort (elems, left, privot - 1 ); quickSort (elems, privot + 1 , right); } return ; }
堆 概念
数据结构中的完全二叉树。满足:任意一个非叶结点 的关键字都不小于(或不大于)其左和右孩子结点的关键字
类型
大顶/根堆:任意一个非叶结点 的关键字都不小于其左和右孩子结点的关键字
小顶/根堆:任意一个非叶结点 的关键字都不大于其左和右孩子结点的关键字
插入过程
在最右下位置插入(满足完全二叉树定义)
调整(满足堆定义)
删除过程
交换最右下位置和删除位置的关键字(满足完全二叉树定义)
从最右下位置删除(满足完全二叉树定义)
调整(满足堆定义)
堆排序的过程 对大顶堆:
无序序列->完全二叉树(依据层序遍历方法)
完全二叉树->大顶堆(依据大顶堆调整方法)
大顶堆->有序序列(依据堆排序方法)
层序遍历方法:
大顶堆调整方法:
从完全二叉树的最后一个非叶子结点开始,从右到左,从下到上,调整结点
交换结点以满足大顶堆定义
堆排序方法:
交换树根结点和最右下位置结点,删除最右下位置结点,输出最右下位置结点(最右下位置结点进入有序序列)
调整(最右下位置结点被交换到树根结点,可能不满足堆定义)
重复1和2步,堆无结点时结束
选择类排序 简单选择排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 void selectSort (vector<ELEM_TYPE> &elems) { int i = 0 ; int j = 0 ; int min = 0 ; ELEM_TYPE temp = 0 ; for (i = 0 ; i < elems.size () - 1 ; ++i) { min = i; for (int j = i + 1 ; j < elems.size (); ++j) { if (elems[min] > elems[j]) { min = j; } } if (min != i) { temp = elems[i]; elems[i] = elems[min]; elems[min] = temp; } } return ; }
堆排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 void heapAdjust (vector<ELEM_TYPE> &elems, int low, int high) { int i = low; ELEM_TYPE temp = elems[low]; for (int j = 2 * i; j <= high; j *= 2 ) { if (j < high && elems[j] < elems[j + 1 ]) { ++j; } if (temp < elems[j]) { elems[i] = elems[j]; i = j; } else { break ; } } elems[i] = temp; return ; } void heapSort (vector<ELEM_TYPE> &elems) { ELEM_TYPE temp = 0 ; for (int i = (elems.size () - 1 ) / 2 ; i >= 1 ; --i) { heapAdjust (elems, i, elems.size () - 1 ); } for (int i = elems.size () - 1 ; i >= 2 ; --i) { temp = elems[1 ]; elems[1 ] = elems[i]; elems[i] = temp; heapAdjust (elems, 1 , i - 1 ); } return ; }
归并类排序 二路归并排序 递归法 分治法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 void merge (vector<ELEM_TYPE> &elems, int low, int mid, int high) { vector<ELEM_TYPE> temp (elems.size(), 0 ) ; for (int i = low; i <= high; ++i) { temp[i] = elems[i]; } int i = low; int j = mid + 1 ; int k = low; while (i <= mid && j <= high) { if (temp[i] <= temp[j]) { elems[k] = temp[i]; ++k; ++i; } else { elems[k] = temp[j]; ++k; ++j; } } while (i <= mid) { elems[k] = temp[i]; ++k; ++i; } while (j <= high) { elems[k] = temp[j]; ++k; ++j; } return ; } void mergeSort (vector<ELEM_TYPE> &elems, int low, int high) { if (low < high) { int mid = low + (high - low) / 2 ; mergeSort (elems, low, mid); mergeSort (elems, mid + 1 , high); merge (elems, low, mid, high); } return ; }
基数类排序 基数排序
总结 插入类排序
名称
时间复杂度
空间复杂度
稳定性
直接插入排序
O(n²)
O(1)
稳定
折半插入排序
O(n²)
O(1)
稳定
希尔排序
O(n的1.3次方)~O(n²)
O(1)
不稳定
交换类排序
名称
时间复杂度
空间复杂度
稳定性
冒泡排序
O(n²)
O(1)
稳定
快速排序
O(nlogn)
O(logn)
不稳定
选择类排序
名称
时间复杂度
空间复杂度
稳定性
简单选择排序
O(n²)
O(1)
不稳定
堆排序
O(nlogn)
O(1)
不稳定
归并类排序
名称
时间复杂度
空间复杂度
稳定性
二路归并排序
O(nlogn)
O(n)
稳定
基数类排序
名称
时间复杂度
空间复杂度
稳定性
基数排序
O(d(n+r))
O(r)
稳定
总表1
名称
时间复杂度
空间复杂度
稳定性
直接插入排序
O(n²)
O(1)
稳定
折半插入排序
O(n²)
O(1)
稳定
希尔排序
O(n的1.3次方)~O(n²)
O(1)
不稳定
冒泡排序
O(n²)
O(1)
稳定
快速排序
O(nlogn)
O(logn)
不稳定
简单选择排序
O(n²)
O(1)
不稳定
堆排序
O(nlogn)
O(1)
不稳定
二路归并排序
O(nlogn)
O(n)
稳定
基数排序
O(d(n+r))
O(r)
稳定
总表2
名称
最好时间复杂度
平均时间复杂度
最坏时间复杂度
空间复杂度
稳定性
直接插入排序
O(n)
O(n²)
O(n²)
O(1)
稳定
折半插入排序
O(nlogn)
O(n²)
O(n²)
O(1)
稳定
希尔排序
O(n的1.3次方)
O(n的1.3次方)~O(n²)
O(n²)
O(1)
不稳定
冒泡排序
O(n)
O(n²)
O(n²)
O(1)
稳定
快速排序
O(nlogn)
O(nlogn)
O(n²)
O(logn)
不稳定
简单选择排序
O(n²)
O(n²)
O(n²)
O(1)
不稳定
堆排序
O(nlogn)
O(nlogn)
O(nlogn)
O(1)
不稳定
二路归并排序
O(nlogn)
O(nlogn)
O(nlogn)
O(n)
稳定
基数排序
O(d(n+r))
O(d(n+r))
O(d(n+r))
O(r)
稳定
记忆 时间复杂度为O(n²):
简单排序:直接插入排序,折半插入排序,冒泡排序,简单选择排序
时间复杂度为O(nlogn):
复杂排序:希尔排序(可能),快速排序,堆排序
归并排序:二路归并排序
时间复杂度特殊:
最好、平均和最坏时间复杂度相同:
最好和平均时间复杂度不同:
最坏和平均时间复杂度不同:
空间复杂度不为O(1):
稳定:
简单排序:直接插入排序,折半插入排序,冒泡排序
归并排序,基数排序:二路归并排序
不稳定:
复杂排序:希尔排序,快速排序
选择类排序:简单选择排序,堆排序
一趟排序,能够确定元素的最终位置:
交换类排序:冒泡排序,快速排序
选择类排序:简单选择排序,堆排序
比较次数和初始序列无关:
折半插入排序
简单选择排序
二路归并排序
基数排序
移动次数和初始序列无关:
排序趟数和初始序列有关:
应用 数据基本有序:最好时间复杂度小
数据规模小(<10000):简单排序
直接插入排序(性能最优;数据规模<=25)
折半插入排序(相对直接插入排序,适用数据规模更大)
冒泡排序
简单选择排序(相对冒泡排序,性能更优)
数据规模小,信息量大(如:数据是数十位的数字->占用空间大->移动时间长):
数据规模大:复杂排序
数据规模<=1000:希尔排序
数据随机分布:快速排序(性能最优;数据基本有序时,性能最差)
空间复杂度小;从大数据规模排序小部分数据:堆排序
稳定:归并排序
多关键字排序:基数排序
数据规模更大:外部排序
数据规模大,数据位数少:
数据规模大,数据位数多:
其他:
堆排序只适用顺序存储方式
堆排序建堆时比较次数多,不适用数据规模小
归并排序尽量用迭代法而不是递归法
基数排序不适用实数类型
混合排序使用
外部排序 概念
内存作为工作/辅助空间,排序外存数据
算法相对复杂
时间=读写外存内存时间+内存排序时间+内存归并时间
时间->访问外存/输入/输出(I/O)次数
优化:减少访问外存/输入/输出(I/O)次数->减少初始归并段数,增多
流程
外存数据分段
段读入内存
段在内存排序,为初始归并段
初始归并段写回外存
初始归并段读入内存
初始归并段在内存归并,为归并段
归并段写回外存
另:
分段
排序
归并
排序方法 常用:
子算法:
置换-选择排序:
作用:排序时 ,生成初始归并段时不受内存限制;减少初始归并段数
外存段中,每记录读入内存
依据排序规则 ,选择一个最值生成初始归并段。不符合排序规则的最值等待 生成下一初始归并段
初始归并段长度不同
最佳归并树:
作用:归并时 ,减少访问外存/输入/输出(I/O)次数;适应长度不同初始归并段的归并
生成哈夫曼树
权:初始归并段的数据规模
路径长度:归并次数
2:读和写
访问外存/输入/输出(I/O)次数 = 带权路径长度(WPL) × 2
败者树:
作用:排序和归并时 ,减少选最值的比较次数/时间复杂度;增多归并路数
对最小值败者树:
叶子结点:值为归并段的段首值,旁为归并段的序号
非叶子结点:值为归并段的序号,旁为归并段的段首值
构造:比较值 ,构造二叉树。败者(值大者)为双亲结点,胜者(值小者)为双亲结点的双亲结点
由树根结点的序号,取值写回外存,取另外值读入内存
调整:同构造过程
选择树 :堆,败者树
时间复杂度:相对复杂
置换-选择排序中,每记录有两次访问外存/输入/输出(I/O)
每一趟归并,每记录有两次访问外存/输入/输出(I/O)
归并趟数:[log以k为底的m]上取整。k为归并路数,m为初始归并段数=外存记录数÷内存能容纳的记录数
k路归并败者树:k个记录/叶子结点的二叉树
k路归并败者树构造的时间复杂度:O(klogk)。k为归并路数
k路归并败者树的树高:[log以2为底的k]上取整+1。k为归并路数(不包括树根结点层)
传统选最值的比较次数:k-1。k为归并路数
k路归并败者树选最值的比较次数:[log以2为底的k]上取整。k为归并路数
k路归并败者树选最值的时间复杂度:O(logk)。k为归并路数
空间复杂度:O(1)。未使用辅助空间
总结 “排序”学习提纲。
参考资料
《2023版数据结构高分笔记》主编:率辉
《2023年计算机组成原理考研复习指导》组编:王道论坛
《大话数据结构》作者:程杰
作者的话
感谢参考资料的作者/博主
作者:夜悊
版权所有,转载请注明出处,谢谢~
如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
文章在认识上有错误的地方, 敬请批评指正
望读者们都能有所收获