数据结构-排序算法全解析

126 阅读5分钟

一、排序基础概念

  • 排序码:数据元素中用于排序的依据属性(如数值大小)。
  • 有序表与无序表:元素按排序码有序排列的表称为有序表,否则为无序表。
  • 稳定性:若排序后相同元素的相对位置不变,则算法是稳定的,否则不稳定。
  • 时间复杂度:衡量算法执行效率的核心指标,常用大O表示法描述。
  • 内排序与外排序:内排序在内存中完成,外排序需借助外部存储。
  • 排序方法的稳定性是指在排序过程中是否改变相同元素的相对位置,若不改变则稳定,否则不稳定

二、插入排序

在有序序列中插入一个元素,保持序列有序,类似摸牌,每摸一张牌都插入到合适的位置,保持手中的牌有序

2.1 直接插入排序(稳定)

  • 原理:将待排序元素插入已排序序列的合适位置。
  • 步骤
    1. 设置哨兵位(下标0),存放待插入元素。
    2. 从后向前扫描已排序序列,若元素大于哨兵,则向后移动。
    3. 找到插入位置后,放入哨兵元素。
  • 特点:适合小规模数据,时间复杂度为 O(n²)
void InsertSort( SqList &L){
    int i,j;
    for (i=2;i<=L.length;++i){
        if(L.r[i].key < L.r[i-1].key){ // 若"<",需将L.r[i]插入有序子表  
        L.r[O]=L.r[i];// 复制为哨兵
        for (j=i-1; L.r[0].key<L.r[j].key; --j){
            L.r[j+ 1]=L.r[j];// 记录后移
         }
         L.r[j+1]=L.r[0];// 插入到正确位置
       }
     }
 }

2.2 折半插入排序(稳定)

  • 优化点:使用二分查找确定插入位置,减少比较次数。
  • 时间复杂度:仍为 O(n²),但比较次数降低。

2.3 希尔排序(不稳定)

  • 原理:按增量序列分组进行插入排序,逐步缩小增量。
  • 增量序列示例5, 3, 1(最终增量必须为1)。
  • 特点:时间复杂度 O(n^(1.3~2)),适合中等规模数据。 采用折半查找法查找插入位置,减少了比较次数,但移动次数没有改变

首先定义一个增量序列dk,比如5,3,1;循环增量序列,按每个增量值给表分块,间隔做插入排序,增量序列必须是递减的,最后一个值一定是1;

image.png

三、交换排序

3.1 冒泡排序(稳定)

  • 原理:通过相邻元素交换,将最小元素“冒泡”到序列前端。
  • 步骤
    1. 从后向前遍历,若相邻元素逆序则交换。
    2. 每轮遍历确定一个最小元素的位置。
  • 时间复杂度O(n²),适合教学或小规模数据。

3.2 快速排序(不稳定)

  • 原理:分治法思想,通过基准值将序列划分为左右子序列。

  • 步骤

    1. 选择一个中间数(假设是下标1)放在数组的0下标(此时中间值原本的下标1就空了),两个指针low high分别指向1和最后一个下标值
    2. 从high开始往前找到一个比中间值小的,把值放在low的位置,然后从low开始往后找,找到一个比中间值大的,则放在high的位置
    3. low和high相等时,一趟排序结束,把0下标中间值放到此时high的位置
    4. 对第一趟排序分出来的两边再递归执行过程2 3,最终排序完成
  • 特点:平均时间复杂度 O(n log n),数据越乱效率越高。


四、选择排序

4.1 直接选择排序(不稳定)

  • 原理:每次从剩余元素中选择最小(或最大)元素放入已排序序列末尾。
  • 步骤
    1. 遍历未排序序列,找到最小值。
    2. 将最小值与未排序序列首元素交换。
  • 时间复杂度O(n²),简单但效率低。

4.2 堆排序(不稳定)

  • 堆的定义:完全二叉树,满足父节点值均大于(大根堆)或小于(小根堆)子节点。

image.png

  • 步骤
    1. 建堆
      1. 用数组存放二叉树结点
      2. 从最后一个非叶子结点即(n/2)的位置开始,一直到第一个结点依次调整
      3. 将以该元素为根的二叉树调整为堆
    2. 排序:以小根堆为例
      1. 输出堆顶元素,以最后一个元素代替
      2. 根与左右孩子比较,并与其中最小者交换位置,直到到达叶子结点
      3. 循环步骤1,2直到剩余两个元素,直接比较
  • 时间复杂度O(n log n),适合动态数据流。

五、归并排序(稳定)

5.1 二路归并排序

  • 原理:分治法思想,将序列递归拆分为子序列,合并时排序。
  • 步骤
    1. 将序列均分为两部分,递归排序子序列。
    2. 合并两个有序子序列,依次比较元素并填充结果。
  • 时间复杂度O(n log n),适合外部排序。

六、算法对比与总结

算法时间复杂度(平均)稳定性适用场景
直接插入排序O(n²)稳定小规模或部分有序数据
快速排序O(n log n)不稳定大规模乱序数据
堆排序O(n log n)不稳定动态数据或内存受限场景
归并排序O(n log n)稳定外部排序或链表排序

七、总结

排序算法是计算机科学的核心基础,不同场景需选择合适的算法。例如:

  • 实时性要求高:优先选择快速排序。
  • 数据规模小:直接插入排序或冒泡排序更简单。
  • 稳定性必需:归并排序或直接插入排序。