排序

定义

  • 若待排序表中有两个元素 \(R_i\) 和 \(R_j\),其对应的关键字相同即 \(key_i=key_j\),且在排序前 \(R_i\)\(R_j\) 的前面,若使用某一排序算法后,\(R_i\) 仍在 \(R_j\) 前面,则称这个排序算法稳定的,否则则不稳定

!算法是否稳定不能衡量一个算法的优劣

插入排序

直接插入排序

  • 排序过程
    1. 查找出元素 L(i) 在有序子序列 L[1...i-1] 中的位置 k
    2. 将 L[1...i-1] 中所有元素依次往后移一个位置
    3. 将 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];		// 插入操作
    }
}

希尔排序

  • 排序过程
    1. 取一个小于 n 的步长 d1,把表中全部记录分为 d1 组,所有距离为 d1 倍数的记录放在同一组,在各组内进行直接插入排序
    2. 然后取第二个步长 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)\)
  • 特点
    • 该算法是稳定的
    • 待排序序列越有序,算法效率越高
    • 冒泡排序每趟生成了一个有序子序列并且确定一个元素的最终位置(冒大泡确定最后,冒小泡确定最前)

快速排序

  • 排序过程(基于分治法)
    • 第一趟:
      1. 第一个元素作为枢轴从后往前遍历排序表,找到比枢轴的元素,将其替换
      2. 以枢轴元素作为中间点,将排序表分为两部分 \(A_1\) 与 \(A_2\)从前往后遍历前半部分 \(A_1\),找到比枢轴的元素将其替换
      3. 再次以枢轴元素作中间点,将上一步前半部分 \(A_1\) 的排序表分为两部分 \(B_1\) 与 \(B_2\)从后往前遍历 \(B_2\),找到比枢轴的元素将其替换
      4. 将 ii, iii 两步交替进行,直到前半部分没有比枢轴大,后半部分没有比枢轴小的元素为止
    • 第二趟:
      1. 以第一趟确定的枢轴为中间点,划分出了前后两个相对于枢轴而言有序的两部分
      2. 分别将这两部分的第一个元素确立为新的两个枢轴,再次使用第一趟的方法进行排序
  • 时空复杂度
    • 空间复杂度为 \(O(log_2n)\)
    • 时间复杂度为 \(O(nlog_2n)\),最坏情况下为 \(O(n^2)\)
  • 特点
    • 该算法是不稳定的,但其为内部排序中的最优排序,并且可以并行处理
    • 待排序序列越无序,算法效率越高
    • 快速排序每趟确定一个元素的最终位置(确定了指定枢轴元素的位置),但不会产生一个有序的子序列

选择排序

简单选择排序

  • 排序过程
    1. 给排序表第一个元素的下标设立一个标志 min,从前往后遍历排序表
    2. 若有比 min 元素更小的元素,则将标志 min 更新到该元素下标上
    3. 当遍历结束后,交换第一个元素和带有 min 标志的元素
  • 时空复杂度
    • 空间复杂度为 \(O(1)\)
    • 时间复杂度为 \(O(n^2)\)
  • 特点
    • 该算法是不稳定的
    • 该算法效率与待排序的初始序列的顺序无关
    • 每趟排序确定一个元素的最终位置(确定了第一个元素,为最小)

堆排序

  • 定义
    • 堆是完全二叉树
    • 大顶堆(大根堆):所有非根结点的元素值均小于根结点元素值
    • 小顶堆(小根堆):所有非根节点的元素值均大于根节点元素值
  • 排序过程
    • 第一趟
      1. 二叉树由下至上由右至左(数组下标从大到小)检查每个元素是否满足大顶堆(小顶堆),不满足则调整
      2. 由于交换了堆上层部分,导致下层部分结构混乱,则再次进行 i 操作调整结构,直至该堆所有子树部分均满足大顶堆(小顶堆)
    • 第二趟
      3. 将第一趟所得的根节点与末尾元素(从上至下从左至右的最后一个元素)进行交换,删除该节点(已为有序序列,暂时不看)
      3. 重复第一趟过程,再次删除末尾元素,以此类推
    • 堆排序图解
    • 重建堆图解
  • 时空复杂度
    • 空间复杂度为 \(O(1)\),因为仅用了常数个辅助存储单元
    • 时间复杂度为 \(O(nlog_2n)\)
  • 特点
    • 该算法是不稳定的
    • 每趟可确定一个元素被放在其最终位置上

归并、基数排序

归并排序

  • 排序过程
    1. 将关键字两两合并为 \(\lceil\frac{n}{2}\rceil\) 组排序序列,对每组内进行排序
    2. 将相邻两组两两合并,对这些组内的 4 个元素(或小于 4)进行排序,以此类推
  • 时空复杂度
    • 空间复杂度为 \(O(n)\)
    • 时间复杂度为 \(O(nlog_2n)\)
  • 特点
    • 该算法是稳定的

基数排序桶排序

  • 排序过程
    • 最低位优先
      1. 第一趟从左往右依次将排序表的每个元素的个位与序列下标相比较,相同的则采用类似邻接表的方式挂在该下标位置,依次链接挂上,并从每组链表的头至尾,从第一组链表到最后一组链表依次填入排序表
      2. 第二趟再将每个元素的十位与序列下标相比较,相同则用 i 的方式同样挂上并按 i 的方式填入排序表
      3. 以此类推
    • 最高位优先:用该排序表元素的最高位与序列下标相比较,剩余操作与上述操作相同
  • 时空复杂度
    • 空间复杂度为 \(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)\)
  • 不稳定的排序算法:
    • 一冒 (冒泡排序) 二插 (直接插入,折半插入) 两特殊 (归并,基数排序)
  • 冒泡和直接插入排序算法序列越有序效率越高,快速排序算法序列越无序,效率越高。基本有序的序列选择直接插入排序算法效率最高,基本无序的序列选择快速排序算法效率最高
  • 每趟排序后可以确定某一元素在其最终位置上:
    • 冒泡排序:可确定其最大元素或最小元素在其两端
    • 快速排序:每一趟可至少确定一个元素在其最终位置上
    • 选择排序:每趟可确定一个最小元素在最左端
    • 堆排序:大顶堆或小顶堆可确定其元素在两端
  • 快速排序是所有内部排序中性能最优的算法,并且可以并行处理
  • 初始状态的无关性:
    • 算法复杂程度与初始状态无关:一堆 (堆排序) 海归 (归并排序) 选 (选择排序) 基 (基数排序) 友
    • 总排序趟数与初始状态无关:除了快排与优化冒泡排序
    • 元素总比较次数与初始状态无关:简单选择排序,基数排序
    • 元素总移动次数与初始状态无关:归并排序,基数排序
posted @ 2021-12-23 19:01  絵守辛玥  阅读(27)  评论(0)    收藏  举报