- bitworld 的博客
各种排序算法剖析
- 2025-8-18 18:27:55 @
各种排序算法剖析
一、排序算法基础
1. 核心概念
排序 (Sorting) 排序是将一组杂乱无章的数据元素,按照某个关键字(例如数值大小、字典顺序)调整为有序序列的过程。排序的目标是让数据更易于查找和处理。
稳定性 (Stability) 稳定性是指在待排序的序列中,若存在多个具有相同关键字的元素,经过排序后,这些元素的原有相对顺序保持不变。
例如,序列中有两个元素 和 ,它们的关键字相同 (),且 在 前面。如果排序后 仍然在 的前面,则称该排序算法是稳定的;反之,如果它们的相对位置可能发生改变,则该算法是不稳定的。判断题中经常会考察各种算法的稳定性。
时间复杂度 (Time Complexity) 时间复杂度是衡量算法执行效率的度量,它描述了算法的执行时间随输入数据规模 增大的变化趋势。通常使用大O表示法()来表示。在笔试中,时间复杂度的分析至关重要。
- 最坏情况 (Worst Case): 算法在任何输入下,执行时间最长的情况。
- 平均情况 (Average Case): 算法在所有可能输入下,执行时间的期望值。
- 最好情况 (Best Case): 算法在特定输入下,执行时间最短的情况。
空间复杂度 (Space Complexity) 空间复杂度衡量算法在运行过程中临时占用的存储空间大小。如果算法只需要常数级别的额外空间(即不随数据规模 变化),则其空间复杂度为 ,也称为原地排序 (in-place sorting)。
2. 排序分类
排序算法可以分为两大类:
- 比较类排序:通过比较元素之间的关键字大小来决定元素的相对次序。其时间复杂度的理论下限是 。常见的比较类排序有冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序和堆排序。
- 非比较类排序:不通过比较来确定元素顺序,而是利用元素自身的特性(如数值范围、位数等)来进行排序。它们可以突破比较排序的时间复杂度下限,达到线性时间 。常见的非比较类排序有计数排序、基数排序和桶排序。
二、比较类排序算法
1. 冒泡排序 (Bubble Sort)
核心思想 重复地遍历待排序序列,每次比较相邻的两个元素,如果顺序错误就交换它们。这个过程就像气泡一样,每一轮都会将当前未排序部分的最大(或最小)元素“冒泡”到序列的末尾。
步骤
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
复杂度与稳定性
- 时间复杂度:
- 平均情况:
- 最坏情况: (序列逆序)
- 最好情况: (序列已基本有序,只需一轮遍历确认)
- 空间复杂度: ,是原地排序算法。
- 稳定性: 稳定。因为只有当相邻元素的顺序错误时才交换,相等的元素不会触发交换,它们的相对位置不变。
代码示例
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// 题意概括: 对一个整型数组进行升序排序。
void bub(int a[], int n) {
bool flg; // 交换标志
for (int i = 0; i < n - 1; i++) {
flg = false;
// 每一轮将一个最大值冒泡到末尾
for (int j = 0; j < n - 1 - i; j++) {
if (a[j] > a[j + 1]) {
swap(a[j], a[j + 1]);
flg = true; // 发生交换
}
}
if (!flg) break; // 若一轮没有发生交换,说明已有序
}
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int a[] = {5, 2, 8, 1, 9, 4};
int n = sizeof(a) / sizeof(a[0]);
bub(a, n);
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}
// 时间复杂度: O(n^2)
// 空间复杂度: O(1)
2. 选择排序 (Selection Sort)
核心思想 每一轮在未排序的序列中找到最小(或最大)的元素,然后将其放置到已排序序列的末尾。
步骤
- 在未排序序列 中找到最小的元素,将其与 交换。
- 在未排序序列 中找到最小的元素,将其与 交换。
- 以此类推,直到所有元素都排序完毕。
复杂度与稳定性
- 时间复杂度: 。选择排序的比较次数与初始序列的排列无关,始终是 。
- 空间复杂度: ,是原地排序算法。
- 稳定性: 不稳定。 考虑序列
{5, 8, 5, 2, 9}
,第一轮找到最小的2,与第一个5交换,序列变为{2, 8, 5, 5, 9}
。两个5的相对位置发生了改变。
代码示例
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// 题意概括: 对一个整型数组进行升序排序。
void sel(int a[], int n) {
for (int i = 0; i < n - 1; i++) {
int min = i; // 记录最小元素的索引
for (int j = i + 1; j < n; j++) {
if (a[j] < a[min]) {
min = j;
}
}
if (min != i) {
swap(a[i], a[min]);
}
}
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int a[] = {5, 2, 8, 1, 9, 4};
int n = sizeof(a) / sizeof(a[0]);
sel(a, n);
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}
// 时间复杂度: O(n^2)
// 空间复杂度: O(1)
3. 插入排序 (Insertion Sort)
核心思想 将待排序序列分为“已排序”和“未排序”两部分。每次从未排序部分取出一个元素,插入到已排序部分的正确位置,直到所有元素都插入完毕。
步骤
- 将第一个元素视为已排序序列。
- 从第二个元素开始,作为待插入元素。
- 将待插入元素与已排序序列中的元素从后向前比较。
- 如果已排序元素大于待插入元素,则将该已排序元素向后移动一位。
- 重复步骤4,直到找到一个小于或等于待插入元素的已排序元素,或已到达序列头部。
- 将待插入元素插入该位置。
- 重复上述过程,直到处理完所有元素。
复杂度与稳定性
- 时间复杂度:
- 平均情况:
- 最坏情况: (序列逆序)
- 最好情况: (序列已基本有序)
- 空间复杂度: ,是原地排序算法。
- 稳定性: 稳定。因为插入时,只有当已排序元素严格大于待插入元素时才移动,遇到相等的元素会停止移动并插入其后,不会改变相等元素的相对顺序。
代码示例
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// 题意概括: 对一个整型数组进行升序排序。
void ins(int a[], int n) {
for (int i = 1; i < n; i++) {
int cur = a[i]; // 当前待插入元素
int j = i - 1;
// 在已排序部分 a[0...i-1] 中寻找插入位置
while (j >= 0 && a[j] > cur) {
a[j + 1] = a[j]; // 元素后移
j--;
}
a[j + 1] = cur; // 插入
}
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int a[] = {5, 2, 8, 1, 9, 4};
int n = sizeof(a) / sizeof(a[0]);
ins(a, n);
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}
// 时间复杂度: O(n^2)
// 空间复杂度: O(1)
4. 希尔排序 (Shell Sort)
核心思想
希尔排序是插入排序的一种更高效的改进版本。它通过将待排序序列按某个“增量”gap
分成若干个子序列,对每个子序列分别进行插入排序。然后逐步减小增量 gap
,重复此过程,直到增量为1。当增量为1时,整个序列基本有序,此时再进行一次插入排序效率会非常高。
步骤
- 选择一个增量序列,例如
n/2
,n/4
, ...,1
。 - 对于第一个增量
gap
,将序列分为gap
个子序列,每个子序列的元素索引相差gap
(例如,a[0], a[gap], a[2*gap], ...
构成一个子序列)。 - 对这
gap
个子序列分别进行插入排序。 - 缩小增量
gap
,重复步骤2和3。 - 直到增量
gap = 1
,此时对整个序列进行最后一次插入排序。
复杂度与稳定性
- 时间复杂度: 依赖于增量序列的选择,不是一个固定的值。
- 平均情况: 通常认为在 到 之间。
- 最坏情况: (例如使用
n/2, n/4, ...
的增量序列)
- 空间复杂度: ,是原地排序算法。
- 稳定性: 不稳定。在分子序列排序时,相同关键字的元素可能被分到不同的子序列中,它们的相对顺序可能会改变。
代码示例
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// 题意概括: 对一个整型数组进行升序排序。
void shl(int a[], int n) {
// 增量 gap 从 n/2 开始,每次减半
for (int gap = n / 2; gap > 0; gap /= 2) {
// 对每个子序列进行插入排序
for (int i = gap; i < n; i++) {
int cur = a[i];
int j = i - gap;
while (j >= 0 && a[j] > cur) {
a[j + gap] = a[j];
j -= gap;
}
a[j + gap] = cur;
}
}
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int a[] = {5, 2, 8, 1, 9, 4, 3, 7, 6};
int n = sizeof(a) / sizeof(a[0]);
shl(a, n);
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}
// 时间复杂度: O(n log n) ~ O(n^2),取决于增量序列
// 空间复杂度: O(1)
5. 归并排序 (Merge Sort)
核心思想 归并排序是分治法 (Divide and Conquer) 的一个典型应用。 它将一个大问题分解为多个小问题来解决。其核心在于“先递归分解,再合并结果”。
步骤
- 分解 (Divide): 将当前序列递归地从中间对半切分,直到每个子序列只包含一个元素。单个元素的序列自然是有序的。
- 合并 (Combine): 将相邻的两个有序子序列合并成一个更大的有序序列。合并时,使用两个指针分别指向两个子序列的开头,比较指针所指元素的大小,将较小的元素放入一个临时数组中,并移动相应的指针。重复此过程,直到一个子序列遍历完,再将另一个子序列剩余的元素全部复制到临时数组末尾。最后将临时数组中的结果拷回原数组的对应位置。
复杂度与稳定性
- 时间复杂度: 。分解过程是对数级的 (),每层合并过程是线性的 ()。其性能不受输入数据初始顺序的影响。
- 空间复杂度: 。合并操作需要一个与当前待合并序列等长的临时数组。
- 稳定性: 稳定。 在合并过程中,如果两个元素相等,可以规定先将被比较的第一个序列中的元素放入临时数组,这样就能保证它们的相对顺序不变。
代码示例
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// 题意概括: 对一个整型数组的指定区间 [l, r] 进行升序排序。
void mrg(int a[], int l, int m, int r) {
int n1 = m - l + 1;
int n2 = r - m;
vector<int> L(n1), R(n2); // 创建临时数组
for (int i = 0; i < n1; i++) L[i] = a[l + i];
for (int j = 0; j < n2; j++) R[j] = a[m + 1 + j];
int i = 0, j = 0, k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
a[k++] = L[i++];
} else {
a[k++] = R[j++];
}
}
while (i < n1) a[k++] = L[i++];
while (j < n2) a[k++] = R[j++];
}
void srt(int a[], int l, int r) {
if (l >= r) return;
int m = l + (r - l) / 2; // 分解
srt(a, l, m);
srt(a, m + 1, r);
mrg(a, l, m, r); // 合并
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int a[] = {5, 2, 8, 1, 9, 4};
int n = sizeof(a) / sizeof(a[0]);
srt(a, 0, n - 1);
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}
// 时间复杂度: O(n log n)
// 空间复杂度: O(n)
6. 快速排序 (Quick Sort)
核心思想 快速排序同样是基于分治法的排序算法。 它通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
步骤
- 选择基准 (Pivot): 从序列中选择一个元素作为“基准”。
- 分区 (Partition): 重新排列序列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准的后面。分区结束后,基准元素就位于其最终的排序位置。
- 递归 (Recurse): 递归地对基准左右两边的子序列重复步骤1和2。
复杂度与稳定性
- 时间复杂度:
- 平均情况: 。
- 最坏情况: 。当每次选择的基准都是当前序列的最大或最小元素时发生(例如,对一个已经有序的序列进行排序)。
- 最好情况: 。当每次划分都尽可能均匀时。
- 空间复杂度: (平均情况) 到 (最坏情况),主要取决于递归深度。
- 稳定性: 不稳定。 在分区过程中,与基准相等的元素的相对位置可能会因为交换操作而改变。
代码示例
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// 题意概括: 对一个整型数组的指定区间 [l, r] 进行升序排序。
// par 函数用于分区,并返回基准元素的最终位置
int par(int a[], int l, int r) {
int piv = a[r]; // 选择最后一个元素作为基准
int i = l - 1;
for (int j = l; j < r; j++) {
if (a[j] < piv) {
i++;
swap(a[i], a[j]);
}
}
swap(a[i + 1], a[r]);
return i + 1;
}
void srt(int a[], int l, int r) {
if (l >= r) return;
int p = par(a, l, r); // 分区
srt(a, l, p - 1); // 递归排序左子序列
srt(a, p + 1, r); // 递归排序右子序列
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int a[] = {5, 2, 8, 1, 9, 4};
int n = sizeof(a) / sizeof(a[0]);
srt(a, 0, n - 1);
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}
// 时间复杂度: 平均 O(n log n),最坏 O(n^2)
// 空间复杂度: 平均 O(log n),最坏 O(n)
7. 堆排序 (Heap Sort)
核心思想 堆排序利用了“堆”这种数据结构的特性。堆是一棵完全二叉树,且满足堆性质:父节点的值总是大于等于(大顶堆)或小于等于(小顶堆)其子节点的值。 堆排序就是通过构建大顶堆(用于升序排序)或小顶堆(用于降序排序)来实现的。
步骤 (以升序排序为例,使用大顶堆)
- 建堆 (Build Heap): 将无序的初始序列构建成一个大顶堆。此时,堆顶元素(根节点)是整个序列的最大值。
- 排序 (Sort): a. 将堆顶元素与序列的最后一个元素交换。此时,最大值被放到了正确的位置。 b. 将序列的长度减1,然后对堆顶元素进行一次“下沉” (heapify) 操作,以维护剩余 个元素的大顶堆性质。 c. 重复步骤a和b,直到序列中只剩一个元素。
复杂度与稳定性
- 时间复杂度: 。建堆的时间复杂度是 ,之后进行 次调整,每次调整的时间复杂度是 。
- 空间复杂度: ,是原地排序算法。
- 稳定性: 不稳定。 在建堆和调整堆的过程中,元素的交换可能会改变相等元素的相对顺序。
代码示例
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// 题意概括: 对一个整型数组进行升序排序。
// hpf 函数用于调整以 i 为根的子树,使其成为大顶堆
void hpf(int a[], int n, int i) {
int lrg = i; // 初始化最大值为根节点
int l = 2 * i + 1; // 左孩子
int r = 2 * i + 2; // 右孩子
if (l < n && a[l] > a[lrg]) lrg = l;
if (r < n && a[r] > a[lrg]) lrg = r;
if (lrg != i) {
swap(a[i], a[lrg]);
hpf(a, n, lrg); // 递归调整受影响的子树
}
}
void srt(int a[], int n) {
// 1. 构建大顶堆
for (int i = n / 2 - 1; i >= 0; i--) {
hpf(a, n, i);
}
// 2. 逐个提取元素
for (int i = n - 1; i > 0; i--) {
swap(a[0], a[i]); // 将当前最大值移到末尾
hpf(a, i, 0); // 重新调整堆
}
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int a[] = {5, 2, 8, 1, 9, 4};
int n = sizeof(a) / sizeof(a[0]);
srt(a, 0, n - 1); // 修正:应传入数组和大小
srt(a, n);
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}
// 时间复杂度: O(n log n)
// 空间复杂度: O(1)
三、非比较类排序算法
这类算法通常对输入数据有特定要求,例如必须是整数,或分布在有限范围内。
1. 计数排序 (Counting Sort)
核心思想 计数排序是一种非比较排序,它通过统计每个不同整数出现的次数,来确定每个元素在输出序列中的位置。它适用于待排序数据是整数且范围不大(例如,对学生的百分制成绩进行排序)的情况。
步骤
- 找出范围: 找到待排序数组中的最大值
max
和最小值min
。 - 计数: 创建一个大小为
max - min + 1
的计数数组C
。遍历原数组,统计每个元素出现的次数。例如,元素v
出现了k
次,则C[v - min] = k
。 - 累加: 修改计数数组
C
,使其每一项C[i]
记录的是小于等于i + min
的元素的总个数。这通过从C
的第二项开始,每一项都加上前一项的值来实现。 - 排序: 创建一个临时数组
B
用于存放排序结果。反向遍历原数组A
,对于每个元素A[i]
,它在排序后数组中的位置是C[A[i] - min] - 1
。将A[i]
放入B
的这个位置,然后将C[A[i] - min]
的值减1。反向遍历是确保算法稳定性的关键。
复杂度与稳定性
- 时间复杂度: ,其中 是待排序元素的数量, 是元素的范围 ()。
- 空间复杂度: ,需要一个额外的计数数组。
- 稳定性: 稳定。通过从后向前遍历原数组并将元素放入正确位置,可以保证相等元素的原始相对顺序。
代码示例
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// 题意概括: 对一个非负整数数组进行升序排序。
void cnt(int a[], int n) {
if (n == 0) return;
int mx = a[0];
for (int i = 1; i < n; i++) mx = max(mx, a[i]);
vector<int> c(mx + 1, 0); // 计数数组
vector<int> b(n);
// 1. 统计频率
for (int i = 0; i < n; i++) c[a[i]]++;
// 2. 累加频率
for (int i = 1; i <= mx; i++) c[i] += c[i - 1];
// 3. 放置到临时数组 b,保证稳定性
for (int i = n - 1; i >= 0; i--) {
b[c[a[i]] - 1] = a[i];
c[a[i]]--;
}
// 4. 将结果拷回原数组
for (int i = 0; i < n; i++) a[i] = b[i];
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int a[] = {5, 2, 8, 1, 9, 2, 4};
int n = sizeof(a) / sizeof(a[0]);
cnt(a, n);
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}
// 时间复杂度: O(n + k)
// 空间复杂度: O(k)
2. 桶排序 (Bucket Sort)
核心思想 桶排序是计数排序的升级版。它将待排序数据根据一定的映射规则,均匀地分配到有限数量的“桶”中,然后对每个桶内的数据分别进行排序(可以使用其他排序算法,如插入排序),最后按顺序连接所有桶中的数据得到有序序列。
步骤
- 建桶: 根据待排序数据的范围和分布,设置一定数量的空桶。
- 入桶: 遍历原序列,将每个元素通过映射函数分配到对应的桶中。
- 桶内排序: 对每个非空的桶中的元素进行排序。
- 合并: 按顺序将所有桶中的元素依次取出,放回原序列。
复杂度与稳定性
- 时间复杂度:
- 平均情况: ,其中 是桶的数量。当数据均匀分布时,每个桶期望有 个元素,若桶内排序使用 的算法(如插入排序在元素少时近似线性),则总时间为 。
- 最坏情况: 。当所有数据都落入同一个桶中时,桶排序退化为桶内排序算法的复杂度。
- 空间复杂度: ,需要存储桶和桶内元素。
- 稳定性: 取决于桶内排序算法的稳定性。如果桶内排序是稳定的(如插入排序),那么桶排序也是稳定的。
代码示例
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// 题意概括: 对一个浮点数数组进行升序排序。
void bkt(float a[], int n) {
// 1. 创建桶
vector<float> b[n];
// 2. 将元素放入不同的桶
for (int i = 0; i < n; i++) {
int bi = n * a[i]; // 映射函数
b[bi].push_back(a[i]);
}
// 3. 对每个桶进行排序
for (int i = 0; i < n; i++) {
sort(b[i].begin(), b[i].end());
}
// 4. 合并所有桶
int idx = 0;
for (int i = 0; i < n; i++) {
for (float x : b[i]) {
a[idx++] = x;
}
}
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
float a[] = {0.8, 0.2, 0.5, 0.1, 0.9, 0.4};
int n = sizeof(a) / sizeof(a[0]);
bkt(a, n);
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}
// 时间复杂度: 平均 O(n+k),最坏 O(n^2)
// 空间复杂度: O(n+k)
3. 基数排序 (Radix Sort)
核心思想 基数排序将整数按位数切割成不同的数字,然后从最低位到最高位(LSD, Least Significant Digit first)或从最高位到最低位(MSD, Most Significant Digit first),依次对每一位进行排序。每一位的排序通常使用一种稳定的排序算法,如计数排序。
步骤 (以LSD为例)
- 确定最大位数: 找到序列中最大数的位数
d
。 - 按位排序: 从最低位(个位)开始,对所有元素的当前位进行一次稳定的排序。
- 重复步骤2,依次对十位、百位...直到第
d
位进行排序。 - 完成所有位的排序后,整个序列就有序了。
复杂度与稳定性
- 时间复杂度: ,其中
d
是最大数的位数,n
是元素个数,k
是每一位的取值范围(对于十进制数,k=10
)。 - 空间复杂度: ,取决于内部使用的稳定排序算法(如计数排序)所需的空间。
- 稳定性: 稳定。基数排序的正确性依赖于其内部排序算法的稳定性。
代码示例
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// 题意概括: 对一个非负整数数组进行升序排序。
// 使用计数排序对数组按指定位 exp 进行排序
void cSort(int a[], int n, int exp) {
vector<int> out(n);
int cnt[10] = {0};
for (int i = 0; i < n; i++) cnt[(a[i] / exp) % 10]++;
for (int i = 1; i < 10; i++) cnt[i] += cnt[i - 1];
for (int i = n - 1; i >= 0; i--) {
out[cnt[(a[i] / exp) % 10] - 1] = a[i];
cnt[(a[i] / exp) % 10]--;
}
for (int i = 0; i < n; i++) a[i] = out[i];
}
void rdx(int a[], int n) {
if (n == 0) return;
int mx = a[0];
for (int i = 1; i < n; i++) mx = max(mx, a[i]);
// 从个位开始,对每一位进行计数排序
for (int exp = 1; mx / exp > 0; exp *= 10) {
cSort(a, n, exp);
}
}
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int a[] = {170, 45, 75, 90, 802, 24, 2, 66};
int n = sizeof(a) / sizeof(a[0]);
rdx(a, n);
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}
// 时间复杂度: O(d(n+k))
// 空间复杂度: O(n+k)
四、算法总结与比较
下表总结了本篇讨论的各种排序算法的关键特性,这对于解答选择题和判断题非常有帮助。
排序算法 (Sorting Algorithm) | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 (Bubble Sort) | 稳定 | ||||
选择排序 (Selection Sort) | 不稳定 | ||||
插入排序 (Insertion Sort) | 稳定 | ||||
希尔排序 (Shell Sort) | 不稳定 | ||||
归并排序 (Merge Sort) | 稳定 | ||||
快速排序 (Quick Sort) | 不稳定 | ||||
堆排序 (Heap Sort) | |||||
计数排序 (Counting Sort) | 稳定 | ||||
桶排序 (Bucket Sort) | |||||
基数排序 (Radix Sort) |
(表中 为数据规模, 为数据范围或桶的数量, 为最大值的位数)