排序---常用排序算法汇总

排序算法是计算机科学中最基础也最核心的算法之一,根据排序原理可分为比较类排序 (通过比较元素大小确定顺序)和非比较类排序(利用数值特性排序)。

一、比较类排序算法

比较类排序通过比较元素间的大小关系调整顺序,时间复杂度下限为O(nlogn)(基于决策树模型证明)。

1. 冒泡排序(Bubble Sort)

核心思想:重复遍历数组,每次比较相邻元素,逆序则交换,使最大元素逐步"浮"到数组末端。

步骤

  • 从数组头部开始,依次比较相邻元素,逆序则交换;
  • 每轮遍历后,最大元素移至未排序区间末尾,缩小未排序范围;
  • 若某轮无交换,说明数组有序,提前终止。

特性

  • 时间复杂度:最好O(n)(优化后),最坏/平均O(n²);
  • 空间复杂度:O(1)(原地排序);
  • 稳定性:稳定(相等元素不交换)。

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void bubbleSort(vector<int>& arr) {
    int n = arr.size();
    for (int i = 0; i < n - 1; ++i) {
        bool swapped = false; // 优化:记录是否交换
        for (int j = 0; j < n - 1 - i; ++j) { // 未排序区间[0, n-1-i]
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
                swapped = true;
            }
        }
        if (!swapped) break; // 无交换则数组有序
    }
}

int main() {
    vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    bubbleSort(arr);
    for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
    return 0;
}
2. 选择排序(Selection Sort)

核心思想:每轮从待排序区间找到最小元素,与区间首元素交换,逐步扩大已排序区间。

步骤

  • 初始未排序区间为整个数组;
  • 遍历未排序区间,找到最小元素,与区间首元素交换;
  • 已排序区间扩大1个元素,重复至排序完成。

特性

  • 时间复杂度:最好/最坏/平均均为O(n²)(必须遍历找最小值);
  • 空间复杂度:O(1);
  • 稳定性:不稳定(如[2, 2, 1],交换后破坏相对顺序)。

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void selectionSort(vector<int>& arr) {
    int n = arr.size();
    for (int i = 0; i < n - 1; ++i) {
        int minIndex = i; // 记录未排序区间最小值索引
        for (int j = i + 1; j < n; ++j) {
            if (arr[j] < arr[minIndex]) minIndex = j;
        }
        swap(arr[i], arr[minIndex]); // 交换到已排序区间
    }
}

int main() {
    vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    selectionSort(arr);
    for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
    return 0;
}
3. 插入排序(Insertion Sort)

核心思想:将数组分为"已排序"和"未排序"两部分,每次从无序部分取元素,插入到有序部分的正确位置(类似整理扑克牌)。

步骤

  • 初始已排序区间为第一个元素;
  • 从第二个元素开始,与有序区间元素从后往前比较,找到插入位置;
  • 移动元素腾出位置,插入当前元素,扩大有序区间。

特性

  • 时间复杂度:最好O(n)(已排序),最坏/平均O(n²);
  • 空间复杂度:O(1);
  • 稳定性:稳定(相等元素插入后方)。

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void insertionSort(vector<int>& arr) {
    int n = arr.size();
    for (int i = 1; i < n; ++i) {
        int key = arr[i]; // 待插入元素
        int j = i - 1; // 已排序区间末尾
        // 移动元素为key腾位置
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key; // 插入key
    }
}

int main() {
    vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    insertionSort(arr);
    for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
    return 0;
}
4. 希尔排序(Shell Sort)

核心思想 :插入排序的改进版,通过"增量分组"减少元素移动次数。按增量gap分组,每组插入排序;逐步减小gap至1,最终整体有序。

步骤

  • 初始增量gap = n/2,后续gap = gap/2
  • gap分组,每组插入排序;
  • 减小gap重复,直至gap = 1(完整插入排序)。

特性

  • 时间复杂度:取决于增量,平均约O(n^1.3),最坏O(n²);
  • 空间复杂度:O(1);
  • 稳定性:不稳定(分组排序破坏相对顺序)。

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void shellSort(vector<int>& arr) {
    int n = arr.size();
    // 按增量分组排序
    for (int gap = n / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < n; ++i) {
            int key = arr[i];
            int j = i;
            // 组内插入排序(步长gap)
            while (j >= gap && arr[j - gap] > key) {
                arr[j] = arr[j - gap];
                j -= gap;
            }
            arr[j] = key;
        }
    }
}

int main() {
    vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    shellSort(arr);
    for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
    return 0;
}
5. 归并排序(Merge Sort)

核心思想:基于分治法,递归拆分数组为两个子数组,分别排序后合并为有序数组。

步骤

  • 拆分:数组从中间分为两部分,递归拆分至子数组长度为1;
  • 合并:创建临时数组,比较两子数组元素,按序放入临时数组,再复制回原数组。

特性

  • 时间复杂度:最好/最坏/平均均为O(nlogn);
  • 空间复杂度:O(n)(需临时数组);
  • 稳定性:稳定(合并时保持相等元素顺序)。

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

// 合并[left, mid]和[mid+1, right]两个有序子数组
void merge(vector<int>& arr, int left, int mid, int right) {
    int n1 = mid - left + 1, n2 = right - mid;
    vector<int> L(n1), R(n2); // 临时数组
    for (int i = 0; i < n1; ++i) L[i] = arr[left + i];
    for (int i = 0; i < n2; ++i) R[i] = arr[mid + 1 + i];
    
    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++];
}

// 归并排序主函数
void mergeSort(vector<int>& arr, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2; // 避免溢出
        mergeSort(arr, left, mid); // 排序左半
        mergeSort(arr, mid + 1, right); // 排序右半
        merge(arr, left, mid, right); // 合并
    }
}

int main() {
    vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    mergeSort(arr, 0, arr.size() - 1);
    for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
    return 0;
}
6. 快速排序(Quick Sort)

核心思想:分治法的典型应用,选择"基准元素"将数组分为两部分(左≤基准≤右),递归排序两部分。

步骤

  • 选择基准(如随机元素);
  • 分区:小于基准放左,大于基准放右,基准位置固定;
  • 递归排序左右两部分。

特性

  • 时间复杂度:平均O(nlogn),最坏O(n²)(可通过基准优化避免);
  • 空间复杂度:O(logn)(递归栈);
  • 稳定性:不稳定(分区交换破坏顺序)。

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace std;

// 分区操作(随机基准)
int partition(vector<int>& arr, int left, int right) {
    srand(time(0));
    int pivotIndex = left + rand() % (right - left + 1); // 随机基准
    swap(arr[left], arr[pivotIndex]);
    int pivot = arr[left];
    
    int i = left, j = right;
    while (i < j) {
        while (i < j && arr[j] >= pivot) j--; // 右指针找小于基准的元素
        while (i < j && arr[i] <= pivot) i++; // 左指针找大于基准的元素
        if (i < j) swap(arr[i], arr[j]);
    }
    swap(arr[left], arr[i]); // 基准放到最终位置
    return i;
}

// 快速排序主函数
void quickSort(vector<int>& arr, int left, int right) {
    if (left < right) {
        int pivotPos = partition(arr, left, right);
        quickSort(arr, left, pivotPos - 1); // 左子数组
        quickSort(arr, pivotPos + 1, right); // 右子数组
    }
}

int main() {
    vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    quickSort(arr, 0, arr.size() - 1);
    for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
    return 0;
}
7. 堆排序(Heap Sort)

核心思想:利用大顶堆(父节点≥子节点)特性,每次提取堆顶(最大值)放到数组末尾,再调整剩余元素为大顶堆,重复至排序完成。

步骤

  • 构建大顶堆:从最后一个非叶子节点开始向下调整;
  • 排序:堆顶与末尾元素交换,缩小堆范围,调整新堆顶,重复至堆为空。

特性

  • 时间复杂度:最好/最坏/平均均为O(nlogn);
  • 空间复杂度:O(1)(原地排序);
  • 稳定性:不稳定(交换破坏顺序)。

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

// 向下调整堆(保证大顶堆特性)
void heapify(vector<int>& arr, int n, int root) {
    int largest = root;
    int left = 2 * root + 1; // 左子节点
    int right = 2 * root + 2; // 右子节点
    
    if (left < n && arr[left] > arr[largest]) largest = left;
    if (right < n && arr[right] > arr[largest]) largest = right;
    
    if (largest != root) {
        swap(arr[root], arr[largest]);
        heapify(arr, n, largest); // 递归调整
    }
}

// 堆排序主函数
void heapSort(vector<int>& arr) {
    int n = arr.size();
    // 构建大顶堆(从最后一个非叶子节点开始)
    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); // 调整剩余i个元素
    }
}

int main() {
    vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    heapSort(arr);
    for (int num : arr) cout << num << " "; // 11 12 22 25 34 64 90
    return 0;
}

二、非比较类排序算法

非比较类排序不依赖元素比较,利用数值特性实现排序,时间复杂度可达O(n),但适用场景有限。

1. 计数排序(Counting Sort)

核心思想 :假设元素值在[min, max]范围内,统计每个值的出现次数,再根据次数重构有序数组。

步骤

  • 确定值范围[min, max],创建计数数组;
  • 统计每个元素出现次数,计算前缀和(确定元素位置);
  • 从后往前遍历原数组,按计数数组放置元素(保证稳定性)。

特性

  • 时间复杂度:O(n + k)(n为元素数,k为值范围);
  • 空间复杂度:O(n + k);
  • 稳定性:稳定。

适用场景 :元素为整数且值范围小(如k ≈ n)。

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void countingSort(vector<int>& arr) {
    if (arr.empty()) return;
    int minVal = *min_element(arr.begin(), arr.end());
    int maxVal = *max_element(arr.begin(), arr.end());
    int range = maxVal - minVal + 1;
    
    vector<int> count(range, 0), output(arr.size());
    // 统计次数
    for (int num : arr) count[num - minVal]++;
    // 计算前缀和
    for (int i = 1; i < range; ++i) count[i] += count[i - 1];
    // 放置元素(从后往前保证稳定)
    for (int i = arr.size() - 1; i >= 0; --i) {
        output[count[arr[i] - minVal] - 1] = arr[i];
        count[arr[i] - minVal]--;
    }
    arr = output;
}

int main() {
    vector<int> arr = {4, 2, 2, 8, 3, 3, 1};
    countingSort(arr);
    for (int num : arr) cout << num << " "; // 1 2 2 3 3 4 8
    return 0;
}
2. 桶排序(Bucket Sort)

核心思想:将元素分到若干"桶"中,桶内用其他排序(如插入排序),最后按桶顺序合并。

步骤

  • 确定桶数量和范围,将元素分配到对应桶;
  • 桶内排序;
  • 合并所有桶元素。

特性

  • 时间复杂度:O(n + nlog(n/m))(m为桶数);
  • 空间复杂度:O(n + m);
  • 稳定性:取决于桶内排序算法。

适用场景:元素分布均匀(如0-1之间的浮点数)。

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void bucketSort(vector<float>& arr) {
    int n = arr.size();
    if (n == 0) return;
    
    vector<vector<float>> buckets(n); // 创建n个桶
    // 分配元素到桶(假设元素在[0,1))
    for (float num : arr) {
        int idx = n * num;
        buckets[idx].push_back(num);
    }
    // 桶内排序
    for (auto& bucket : buckets) sort(bucket.begin(), bucket.end());
    // 合并桶
    int idx = 0;
    for (auto& bucket : buckets) {
        for (float num : bucket) arr[idx++] = num;
    }
}

int main() {
    vector<float> arr = {0.897, 0.565, 0.656, 0.1234, 0.665, 0.3434};
    bucketSort(arr);
    for (float num : arr) cout << num << " "; // 0.1234 0.3434 0.565 0.656 0.665 0.897
    return 0;
}
3. 基数排序(Radix Sort)

核心思想:按元素"位数"从低到高排序,每轮对当前位用计数排序(保证稳定性)。

步骤

  • 确定最大元素的位数d
  • 从最低位到最高位,每轮对当前位执行计数排序;
  • 最终整体有序。

特性

  • 时间复杂度:O(d*(n + k))(d为位数,k为每一位范围);
  • 空间复杂度:O(n + k);
  • 稳定性:稳定。

适用场景:整数或字符串(位数固定)。

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 获取x的第exp位数字(如exp=1取个位)
int getDigit(int x, int exp) {
    return (x / exp) % 10;
}

// 对第exp位计数排序(基数排序的一轮)
void countSortRadix(vector<int>& arr, int exp) {
    int n = arr.size();
    vector<int> output(n), count(10, 0);
    
    // 统计当前位数字出现次数
    for (int num : arr) count[getDigit(num, exp)]++;
    // 计算前缀和
    for (int i = 1; i < 10; ++i) count[i] += count[i - 1];
    // 放置元素(从后往前保证稳定)
    for (int i = n - 1; i >= 0; --i) {
        int digit = getDigit(arr[i], exp);
        output[count[digit] - 1] = arr[i];
        count[digit]--;
    }
    arr = output;
}

// 基数排序主函数
void radixSort(vector<int>& arr) {
    if (arr.empty()) return;
    int maxVal = *max_element(arr.begin(), arr.end());
    // 按每位排序(从个位到最高位)
    for (int exp = 1; maxVal / exp > 0; exp *= 10) {
        countSortRadix(arr, exp);
    }
}

int main() {
    vector<int> arr = {170, 45, 75, 90, 802, 24, 2, 66};
    radixSort(arr);
    for (int num : arr) cout << num << " "; // 2 24 45 66 75 90 170 802
    return 0;
}

三、排序算法对比与选择

算法 平均时间 最坏时间 空间 稳定性 适用场景
冒泡排序 O(n²) O(n²) O(1) 稳定 小规模、近有序数据
选择排序 O(n²) O(n²) O(1) 不稳定 小规模、空间受限
插入排序 O(n²) O(n²) O(1) 稳定 小规模、部分有序数据
希尔排序 O(n^1.3) O(n²) O(1) 不稳定 中等规模数据
归并排序 O(nlogn) O(nlogn) O(n) 稳定 大规模、需稳定排序
快速排序 O(nlogn) O(n²) O(logn) 不稳定 大规模、平均性能最优
堆排序 O(nlogn) O(nlogn) O(1) 不稳定 大规模、空间受限
计数排序 O(n+k) O(n+k) O(n+k) 稳定 整数、值范围小
桶排序 O(n+nlog(n/m)) O(n²) O(n+m) 稳定 元素分布均匀
基数排序 O(d*(n+k)) O(d*(n+k)) O(n+k) 稳定 整数或字符串、位数固定

选择建议

  • 小规模数据(n≤100):插入排序或冒泡排序(实现简单);
  • 中大规模数据:快速排序(平均最优)、归并排序(稳定)或堆排序(空间优);
  • 特殊场景:值范围小用计数排序,分布均匀用桶排序,整数/字符串用基数排序。
相关推荐
AndrewHZ1 小时前
【遥感图像入门】DEM数据处理核心算法与Python实操指南
图像处理·python·算法·dem·高程数据·遥感图像·差值算法
CoderYanger1 小时前
动态规划算法-子序列问题(数组中不连续的一段):28.摆动序列
java·算法·leetcode·动态规划·1024程序员节
有时间要学习2 小时前
面试150——第二周
数据结构·算法·leetcode
freedom_1024_2 小时前
红黑树底层原理拆解
开发语言·数据结构·b树
liu****2 小时前
3.链表讲解
c语言·开发语言·数据结构·算法·链表
minji...2 小时前
Linux 基础IO(一) (C语言文件接口、系统调用文件调用接口open,write,close、文件fd)
linux·运维·服务器·网络·数据结构·c++
第二只羽毛2 小时前
C++ 高性能编程要点
大数据·开发语言·c++·算法
CQ_YM3 小时前
数据结构之栈
数据结构·算法·
爱学习的梵高先生3 小时前
C++:基础知识
开发语言·c++·算法