数据结构经典八大排序算法全解析

1. 冒泡排序 (Bubble Sort)

思想:
重复遍历数列,比较相邻元素,若逆序则交换,每一趟将未排序部分的最大值“浮”到末尾。若某一趟未发生交换,则提前结束。

复杂度与稳定性:

时间:最好 O(n),平均 O(n²),最坏 O(n²)

空间:O(1)

稳定性:稳定

void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int swapped = 0;
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
                swapped = 1;
            }
        }
        if (!swapped) break; // 已经有序
    }
}

代码思路

外层循环控制趟数,最多 n-1 趟。

内层循环比较相邻元素,每一趟的终点随 i 减少(后面已排好)。

使用 swapped 标志,若内层没有交换说明已有序,提前结束。

交换时用临时变量确保稳定(相等不交换,相对顺序不变)。

2. 选择排序 (Selection Sort)

思想
每一轮从未排序部分选出最小元素,与未排序部分的第一个元素交换,将最小元素归位。

复杂度与稳定性

  • 时间:最好/平均/最坏均为 O(n²)

  • 空间:O(1)

  • 稳定性:不稳定

适用场景
实现简单,不要求稳定性且数据规模较小时可用。

void selectionSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int minIdx = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIdx])
                minIdx = j;
        }
        // 交换
        int tmp = arr[i];
        arr[i] = arr[minIdx];
        arr[minIdx] = tmp;
    }
}

代码思路

 i 作为已排序部分的边界。

    内层循环在 [i+1, n-1] 中寻找真正的最小值索引。

    交换 arr[i] 与 arr[minIdx],将当前最小元素放到正确位置。

    交换操作可能跨越中间元素,导致相同元素的前后顺序改变,故不稳定。


3. 插入排序 (Insertion Sort)

思想
将数组分为有序区和无序区,每次取无序区第一个元素,在有序区中从后向前扫描,找到合适位置插入(将较大元素右移)。

复杂度与稳定性

时间:最好 O(n),平均 O(n²),最坏 O(n²)

空间:O(1)

稳定性:稳定

适用场景
数据量小且基本有序时效率高,常作为高级排序的底层优化。

void insertionSort(int arr[], int n) {
    for (int i = 1; i < n; i++) {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

代码思路

i=1 开始,认为第一个元素有序。

取出 arr[i] 暂存为 key,然后向前扫描有序区。

只要 arr[j] > key,就将 arr[j] 右移一位。

最终将 key 插入到 j+1 位置。由于只在 > key 时才移动,相等元素不会跨越,保持稳定。


4. 希尔排序 (Shell Sort)

思想
插入排序的改进,先按较大增量分组进行插入排序,使数组逐步有序,最后增量缩小至1,完成最终插入排序。

复杂度与稳定性

时间:依赖增量序列,平均 O(n^1.3) ~ O(n²)

空间:O(1)

稳定性:不稳定

适用场景
中等规模数据,对插入排序的直接优化,实际效率优于简单插入排序。

void shellSort(int arr[], int n) {
    for (int gap = n / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < n; i++) {
            int tmp = arr[i];
            int j = i;
            while (j >= gap && arr[j - gap] > tmp) {
                arr[j] = arr[j - gap];
                j -= gap;
            }
            arr[j] = tmp;
        }
    }
}

代码思路

外层循环控制增量 gap,通常从 n/2 开始,每次折半。

内层对每个子序列进行插入排序,子序列元素间隔为 gap

对于每个 i,在组内向前比较并移动元素,类似于插入排序,但步长为 gap

由于分组插入可能打乱相同元素在不同组中的顺序,故不稳定。


5. 归并排序 (Merge Sort)

思想
采用分治法,递归地将数组二分至单个元素,再将有序子数组合并成更大的有序数组。

复杂度与稳定性

时间:最好/平均/最坏均为 O(n log n)

空间:O(n)(临时数组)

稳定性:稳定

适用场景
数据量大且要求稳定排序,也常用于外部排序。

void merge(int arr[], int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;
    int* L = (int*)malloc(n1 * sizeof(int));
    int* R = (int*)malloc(n2 * sizeof(int));
    for (int i = 0; i < n1; i++) L[i] = arr[left + i];
    for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j];

    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) arr[k++] = L[i++];
        else              arr[k++] = R[j++];
    }
    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];

    free(L); free(R);
}

void mergeSort(int arr[], int left, int right) {
    if (left >= right) return;
    int mid = left + (right - left) / 2;
    mergeSort(arr, left, mid);
    mergeSort(arr, mid + 1, right);
    merge(arr, left, mid, right);
}

代码思路

mergeSort 递归拆分:mid 取中间点,先对左半和右半分别排序,再调用 merge 合并。

merge 合并时,将左右子数组复制到临时数组 L 和 R,然后用双指针比较归并。

归并时 if (L[i] <= R[j]) 保证相等元素优先取左半,从而保持稳定性。

最终需要 free 临时数组,避免内存泄漏。


6. 快速排序 (Quick Sort)

思想
选取基准元素,通过分区将数组分为小于基准和大于基准的两部分,再递归排序两部分。

复杂度与稳定性

时间:最好/平均 O(n log n),最坏 O(n²)

空间:O(log n)(递归栈)

稳定性:不稳定

适用场景
实际应用中最常用的内排序,需注意基准选取避免退化。

void swap(int* a, int* b) {
    int t = *a;
    *a = *b;
    *b = t;
}

int partition(int arr[], int low, int high) {
    int pivot = arr[high];
    int i = low - 1; // i 指向小于基准的最后一个元素
    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return i + 1;
}

void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

代码思路

partition 选择最右元素为基准 pivot

i 维护小于等于 pivot 的元素边界,j 扫描整个区间,遇到小于等于基准的元素就与 i+1 位置交换。

最后将基准放到 i+1 位置,返回该索引。

递归排序左右子区间。由于交换会打乱相同元素的相对位置,故不稳定。


7. 堆排序 (Heap Sort)

思想
利用大顶堆进行选择排序:先建堆,然后反复交换堆顶(最大值)与末尾元素,再调整堆。

复杂度与稳定性

时间:最好/平均/最坏均为 O(n log n)

空间:O(1)

稳定性:不稳定

适用场景
原地排序且保证 O(n log n) 时间,适合对空间要求严格的场合。

void heapify(int arr[], int n, int i) {
    int largest = i;
    int left = 2 * i + 1;
    int right = 2 * i + 2;

    if (left < n && arr[left] > arr[largest])
        largest = left;
    if (right < n && arr[right] > arr[largest])
        largest = right;

    if (largest != i) {
        swap(&arr[i], &arr[largest]);
        heapify(arr, n, largest);
    }
}

void heapSort(int arr[], int n) {
    // 建堆
    for (int i = n / 2 - 1; i >= 0; i--)
        heapify(arr, n, i);

    // 一个个提取最大元素
    for (int i = n - 1; i > 0; i--) {
        swap(&arr[0], &arr[i]);
        heapify(arr, i, 0);
    }
}

代码思路

heapify 是下沉调整,确保以 i 为根的子树满足大顶堆性质。比较父节点与左右子节点,若子节点更大则交换并递归调整。

建堆从最后一个非叶节点 n/2-1 开始向前调用 heapify,使得整个数组变成大顶堆。

排序循环中,每次将堆顶(最大值)与当前末尾元素交换,然后缩小堆大小并对新堆顶执行下沉调整。

由于堆调整和交换可能打乱相同元素的顺序,故不稳定。


8. 计数排序 (Counting Sort)

思想
非比较排序,统计每个元素的出现次数,再按次序覆盖原数组,适用于整数且范围有限的场景。

复杂度与稳定性

  • 时间:O(n + k),k 为值域范围

  • 空间:O(k)

  • 稳定性:可稳定(取决于写回方式,简单写回不稳定)

适用场景
数据范围集中、整数排序,当 k 不大时速度极快。

#include <stdlib.h>
#include <string.h>

void countingSort(int arr[], int n) {
    if (n <= 0) return;

    int min = arr[0], max = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] < min) min = arr[i];
        if (arr[i] > max) max = arr[i];
    }
    int range = max - min + 1;

    int* count = (int*)malloc(range * sizeof(int));
    memset(count, 0, range * sizeof(int));

    // 计数
    for (int i = 0; i < n; i++)
        count[arr[i] - min]++;

    // 写回原数组(简单写回,不稳定)
    int idx = 0;
    for (int i = 0; i < range; i++) {
        while (count[i]--) {
            arr[idx++] = i + min;
        }
    }

    free(count);
}

代码思路

先找到最大值和最小值,确定值域范围 range

分配计数数组并初始化为0,统计每个值出现的次数。

按值从小到大遍历计数数组,将元素按出现次数写回原数组。

写回时用 idx 递增覆盖,此方式不保留原顺序,故不稳定。若需稳定,可采用从后向前遍历原数组,借助前缀和定位填入位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值