C/C++ 数据结构(十五)排序算法

本篇核心知识点 :算法时间复杂度分级、稳定 / 不稳定排序定义、快速排序堆排序归并排序原理与完整代码、八大排序分类

一、算法时间复杂度分级

1. 概念

时间复杂度用来衡量算法运行步数随数据量n的增长趋势,采用大 O 记法,只保留最高次项,忽略常数与低次项,是选择算法、数据结构核心依据。

2. 各类复杂度概念 + 特性

  1. O (1) 常数级

    概念:无循环,执行步骤固定,与数据量无关。

    特性:数组下标随机访问、哈希表单点查找。

  2. O (logn) 对数级

    概念:每次处理后数据规模折半,循环条件成倍变化。

    特性:平衡二叉搜索树、二分查找;每次排除一半数据。

  3. O (n) 线性级

    概念:单层循环,遍历全部数据一次。

    特性:链表完整遍历、普通顺序查找。

  4. O (nlogn) 线性对数级

    概念:一层线性循环嵌套一层对数循环。

    特性:快速排序、堆排序、归并排序平均复杂度。

  5. O (n²) 平方级

    概念:两层嵌套循环,数据越大效率暴跌。

    特性:冒泡、选择、插入基础排序。

拓展对比

数据量大时优先级:O (1) > O (logn) > O (nlogn) > O (n) > O (n²)

二、排序基础核心概念

2.1 稳定排序

概念

待排序序列中值相等元素,排序后相对先后顺序保持不变

稳定排序集合:冒泡、插入、归并、基数排序

2.2 不稳定排序

概念:

等值元素排序后原有前后位置可能颠倒

不稳定排序集合:选择、快速、堆排序、希尔

拓展

业务场景要求保留原始同等数据顺序时,必须选用稳定排序。

三、快速排序

1. 概念

分治思想:选取基准值(pivot),一轮遍历将序列划分为「小于基准」左区间、「大于基准」右区间,再递归处理左右子区间,直到区间长度为 1 天然有序。

2. 特性

  1. 平均时间复杂度 O (nlogn),最坏有序数组 O (n²);

  2. 不稳定排序;

  3. 原地交换,无需额外数组,空间开销小;

  4. 基准一般选区间首元素,也可随机基准优化最坏情况。

3. 完整代码实现

复制代码
#include <iostream>
using namespace std;
​
// 快速排序 左闭右闭区间 [l, r]
void QuickSort(int arr[], int l, int r)
{
    if (l >= r) return; // 区间仅一个元素,终止递归
    int pivot = arr[l]; // 基准取最左侧元素
    int i = l, j = r;
    while (i < j)
    {
        // 右指针向左找小于基准的值
        while (i < j && arr[j] >= pivot)
            j--;
        arr[i] = arr[j];
        // 左指针向右找大于基准的值
        while (i < j && arr[i] <= pivot)
            i++;
        arr[j] = arr[i];
    }
    arr[i] = pivot; // 基准归位,i为分割点
    QuickSort(arr, l, i - 1);  // 递归处理左区间
    QuickSort(arr, i + 1, r);  // 递归处理右区间
}
​
int main()
{
    int arr[] = {3,9,4,7,12,5};
    int len = sizeof(arr)/sizeof(int);
    QuickSort(arr,0,len-1);
    for(int i=0;i<len;i++)
        cout << arr[i] << " ";
    return 0;
}

拓展优化

随机选取基准、三数取中法,规避有序数组最坏时间复杂度。

四、堆排序

1. 概念

基于**完全二叉树(堆)**实现排序,分为大顶堆、小顶堆;升序排序使用大顶堆:堆顶为最大值,每次交换堆顶与末尾元素,再重新调整堆,逐步完成有序序列。

大顶堆规则:任意父节点值 ≥ 左右子节点值

2. 堆下标数学公式(数组存储完全二叉树)

当前下标i

左孩子:2*i + 1

右孩子:2*i + 2

父节点:(i-1)/2

3. 特性

  1. 时间复杂度稳定 O (nlogn);

  2. 不稳定排序;

  3. 原地排序,无额外数组开销;

  4. 适合海量数据 TOP-K 极值筛选。

4. 完整代码

复制代码
#include <iostream>
using namespace std;
​
// 调整以i为根的子树为大顶堆,size为数组有效长度
void HeapAdjust(int arr[], int i, int size)
{
    int temp = arr[i]; // 保存根值
    int left = 2*i + 1;
    while(left < size)
    {
        int maxIdx = left;
        int right = left + 1;
        // 找出左右子节点最大值下标
        if(right < size && arr[right] > arr[left])
            maxIdx = right;
        if(arr[maxIdx] > temp)
        {
            arr[i] = arr[maxIdx];
            i = maxIdx;
            left = 2*i + 1;
        }
        else break;
    }
    arr[i] = temp;
}
​
// 堆排序主函数
void HeapSort(int arr[], int len)
{
    // 1. 初始化大顶堆:从最后一个非叶子节点向前调整
    for(int i = (len-2)/2; i >= 0; i--)
        HeapAdjust(arr,i,len);
    // 2. 交换堆顶最大值到末尾,逐步缩小区间
    for(int i = len-1; i > 0; i--)
    {
        swap(arr[0], arr[i]);
        HeapAdjust(arr,0,i);
    }
}
​
int main()
{
    int arr[] = {2,8,5,3,7,1};
    int len = sizeof(arr)/sizeof(int);
    HeapSort(arr, len);
    for(int x : arr) cout << x << " ";
    return 0;
}

五、归并排序

1. 概念

分治递归思想:先把长数组不断拆分为长度 1 的子数组(天然有序),再两两合并两个有序子数组,逐层向上合并得到完整有序序列。

2. 特性

  1. 稳定排序;

  2. 时间复杂度稳定 O (nlogn);

  3. 需要临时数组存储合并结果,存在额外内存开销;

  4. 链表排序优先选用归并,无数组拷贝开销。

3. 完整代码

复制代码
#include <iostream>
#include <vector>
using namespace std;
​
// 合并两个有序区间 [l,mid] [mid+1, r]
void Merge(int arr[], int l, int mid, int r, int temp[])
{
    int i = l, j = mid+1, k = l;
    while(i <= mid && j <= r)
    {
        if(arr[i] <= arr[j]) temp[k++] = arr[i++];
        else temp[k++] = arr[j++];
    }
    while(i <= mid) temp[k++] = arr[i++];
    while(j <= r) temp[k++] = arr[j++];
    // 临时数组拷贝回原数组
    for(int p=l;p<=r;p++) arr[p] = temp[p];
}
​
void MergeSort(int arr[], int l, int r, int temp[])
{
    if(l >= r) return;
    int mid = (l + r)/2;
    MergeSort(arr, l, mid, temp);    // 拆分左半
    MergeSort(arr, mid+1, r, temp);  // 拆分右半
    Merge(arr, l, mid, r, temp);     // 合并有序区间
}
​
// 封装入口
void MergeSortWrap(int arr[], int len)
{
    int* temp = new int[len];
    MergeSort(arr, 0, len-1, temp);
    delete[] temp;
}
​
int main()
{
    int arr[] = {9,2,7,4,1,6};
    int len = sizeof(arr)/sizeof(int);
    MergeSortWrap(arr, len);
    for(int x : arr) cout << x << " ";
    return 0;
}

六、八大排序整体分类拓展

1. 简单平方级排序 O (n²)

冒泡排序、插入排序、选择排序

稳定:冒泡、插入;不稳定:选择

2. 线性对数级 O (nlogn)

快速排序、堆排序、归并排序

稳定:归并;不稳定:快排、堆排

3. 线性特殊排序 O (n)(数据有范围约束)

基数排序、计数排序(稳定)