排序
定义
- 若待排序表中有两个元素 \(R_i\) 和 \(R_j\),其对应的关键字相同即 \(key_i=key_j\),且在排序前 \(R_i\) 在 \(R_j\) 的前面,若使用某一排序算法后,\(R_i\) 仍在 \(R_j\) 前面,则称这个排序算法稳定的,否则则不稳定
!算法是否稳定不能衡量一个算法的优劣
插入排序
直接插入排序
- 排序过程
- 查找出元素 L(i) 在有序子序列 L[1...i-1] 中的位置 k
- 将 L[1...i-1] 中所有元素依次往后移一个位置
- 将 L(i) 复制到 L(k)
- 时空复杂度
- 空间复杂度为 \(O(1)\),因为有“哨兵”
- 时间复杂度为 \(O(n^2)\)
- 特点
- 该排序算法是稳定的
- 待排序序列越有序,算法效率越高
void InsertSort(ElemType A[], int n) {
int i, j;
for (i = 2; i <= n; i++) // 依次将A[2]~A[n]插入到前面已排序序列,第1个元素不需要排序从第2个开始
if (A[i] < A[i-1]) { // 若A[i]关键码小于其前驱,将A[i]插入有序表
A[0] = A[i]; // 复制为哨兵,A[0]不存放元素
for (j = i - 1; A[0] < A[j]; --j) // 从后往前查找待插入位置
A[j+1] = A[j] // 向后挪位
A[j+1] = A[0] // 复制到插入位置
}
}
折半插入排序
- 排序过程:折半插入排序相当于先折半查找,再直接插入排序
- 时空复杂度
- 空间复杂度为 \(O(1)\)
- 时间复杂度为 \(O(n^2)\)
- 特点
- 该算法是稳定的
void InsertSort(ElemType A[], int n) {
int i, j, low, high, mid;
for (i = 2; i <= n; i++) { // 依次将A[2]~A[n]插入到前面已排序序列,第1个元素不需要排序从第2个开始
A[0] = A[i]; // 将A[i]暂存到A[0]
low = 1;
high = n - 1; // 设置折半查找范围
while (low <= high) {
mid = (low + high) / 2; // 取中间点
if (A[mid] > A[0])
high = mid - 1; // 查找左半子表
else
low = mid + 1; // 查找右半子表
}
for (j = i - 1; j > high + 1; --j)
A[j+1] = A[j]; // 统一后移元素,空出插入位置
A[high+1] = A[0]; // 插入操作
}
}
希尔排序
- 排序过程
- 取一个小于 n 的步长 d1,把表中全部记录分为 d1 组,所有距离为 d1 倍数的记录放在同一组,在各组内进行直接插入排序
- 然后取第二个步长 d2 < d1,重复上述过程,直到所取到的 d1=1,即所有记录放在同一组中,在进行直接插入排序
- 时空复杂度
- 空间复杂度为 \(O(1)\),因为仅用了常数个辅助存储单元
- 时间复杂度为 \(O(n^2)\)
- 特点
- 该算法是不稳定的
void ShellSort(ElemType A[], int n) {
for (dk = n / 2; dk >= 1; dk = dk / 2) // 步长变化
for (i = dk + 1; i <= n; ++i)
if (A[i] < A[i-dk]) { // 需将A[i]插入有序增量子表
A[0] = A[i]; // 暂存在暂存单元A[0]中
for (j = i - dk; j > 0 && A[0] < A[j]; j -= dk)
A[j+dk] = A[j]; // 记录后移,查找插入的位置
A[j+dk] = A[0]; // 插入
}
}
交换排序
冒泡排序起泡排序
- 排序过程:从后往前(从前往后)两两比较相邻元素值,若为逆序,则交换它们
- 时空复杂度
- 空间复杂度为 \(O(1)\),因为仅用了常数个辅助存储单元
- 时间复杂度最好情况下为 \(O(n)\)(序列有序),平均为 \(O(n^2)\)
- 特点
- 该算法是稳定的
- 待排序序列越有序,算法效率越高
- 冒泡排序每趟生成了一个有序子序列并且确定一个元素的最终位置(冒大泡确定最后,冒小泡确定最前)
快速排序
- 排序过程(基于分治法)
- 第一趟:
- 将第一个元素作为枢轴,从后往前遍历排序表,找到比枢轴小的元素,将其替换
- 以枢轴元素作为中间点,将排序表分为两部分 \(A_1\) 与 \(A_2\),从前往后遍历前半部分 \(A_1\),找到比枢轴大的元素将其替换
- 再次以枢轴元素作中间点,将上一步前半部分 \(A_1\) 的排序表分为两部分 \(B_1\) 与 \(B_2\),从后往前遍历 \(B_2\),找到比枢轴小的元素将其替换
- 将 ii, iii 两步交替进行,直到前半部分没有比枢轴大,后半部分没有比枢轴小的元素为止
- 第二趟:
- 以第一趟确定的枢轴为中间点,划分出了前后两个相对于枢轴而言有序的两部分
- 分别将这两部分的第一个元素确立为新的两个枢轴,再次使用第一趟的方法进行排序
- 第一趟:
- 时空复杂度
- 空间复杂度为 \(O(log_2n)\)
- 时间复杂度为 \(O(nlog_2n)\),最坏情况下为 \(O(n^2)\)
- 特点
- 该算法是不稳定的,但其为内部排序中的最优排序,并且可以并行处理
- 待排序序列越无序,算法效率越高
- 快速排序每趟确定一个元素的最终位置(确定了指定枢轴元素的位置),但不会产生一个有序的子序列
选择排序
简单选择排序
- 排序过程
- 给排序表第一个元素的下标设立一个标志 min,从前往后遍历排序表
- 若有比 min 元素更小的元素,则将标志 min 更新到该元素下标上
- 当遍历结束后,交换第一个元素和带有 min 标志的元素
- 时空复杂度
- 空间复杂度为 \(O(1)\)
- 时间复杂度为 \(O(n^2)\)
- 特点
- 该算法是不稳定的
- 该算法效率与待排序的初始序列的顺序无关
- 每趟排序确定一个元素的最终位置(确定了第一个元素,为最小)
堆排序
- 定义
- 堆是完全二叉树
- 大顶堆(大根堆):所有非根结点的元素值均小于根结点元素值
- 小顶堆(小根堆):所有非根节点的元素值均大于根节点元素值
- 排序过程
- 时空复杂度
- 空间复杂度为 \(O(1)\),因为仅用了常数个辅助存储单元
- 时间复杂度为 \(O(nlog_2n)\)
- 特点
- 该算法是不稳定的
- 每趟可确定一个元素被放在其最终位置上
归并、基数排序
归并排序
- 排序过程
- 将关键字两两合并为 \(\lceil\frac{n}{2}\rceil\) 组排序序列,对每组内进行排序
- 将相邻两组两两合并,对这些组内的 4 个元素(或小于 4)进行排序,以此类推
- 时空复杂度
- 空间复杂度为 \(O(n)\)
- 时间复杂度为 \(O(nlog_2n)\)
- 特点
- 该算法是稳定的
基数排序桶排序
- 排序过程
- 最低位优先
- 第一趟从左往右依次将排序表的每个元素的个位与序列下标相比较,相同的则采用类似邻接表的方式挂在该下标位置,依次链接挂上,并从每组链表的头至尾,从第一组链表到最后一组链表依次填入排序表
- 第二趟再将每个元素的十位与序列下标相比较,相同则用 i 的方式同样挂上并按 i 的方式填入排序表
- 以此类推
- 最高位优先:用该排序表元素的最高位与序列下标相比较,剩余操作与上述操作相同
- 最低位优先
- 时空复杂度
- 空间复杂度为 \(O(r)\),r 为该排序表中数的进制数
- 时间复杂度为 \(O(d(n+r))\),d 为最终排序完成所需趟数
- 特点
- 该算法是稳定的
- 该算法效率与待排序的初始序列的顺序无关
总结
- 空间复杂度:
- 快速排序为 \(O(log_2n)\),归并排序为 \(O(n)\),基数排序为 \(O(r)\),其余均为 \(O(1)\)
- 时间复杂度:
- 快速排序,堆排序,归并排序均为 \(O(nlog_2n)\),基数排序为\(O(d(n+r))\),其余均为 \(O(n^2)\)
- 不稳定的排序算法:
- 一冒 (冒泡排序) 二插 (直接插入,折半插入) 两特殊 (归并,基数排序)
- 冒泡和直接插入排序算法序列越有序效率越高,快速排序算法序列越无序,效率越高。基本有序的序列选择直接插入排序算法效率最高,基本无序的序列选择快速排序算法效率最高
- 每趟排序后可以确定某一元素在其最终位置上:
- 冒泡排序:可确定其最大元素或最小元素在其两端
- 快速排序:每一趟可至少确定一个元素在其最终位置上
- 选择排序:每趟可确定一个最小元素在最左端
- 堆排序:大顶堆或小顶堆可确定其元素在两端
- 快速排序是所有内部排序中性能最优的算法,并且可以并行处理
- 初始状态的无关性:
- 算法复杂程度与初始状态无关:一堆 (堆排序) 海归 (归并排序) 选 (选择排序) 基 (基数排序) 友
- 总排序趟数与初始状态无关:除了快排与优化冒泡排序
- 元素总比较次数与初始状态无关:简单选择排序,基数排序
- 元素总移动次数与初始状态无关:归并排序,基数排序

浙公网安备 33010602011771号