跳到主要内容数据结构常见排序算法汇总:插入、交换、选择与归并 | 极客日志C++算法
数据结构常见排序算法汇总:插入、交换、选择与归并
综述由AI生成系统梳理了数据结构中的排序算法,涵盖内部排序与外部排序的分类。详细介绍了直接插入、折半插入、希尔、冒泡、快速(含栈模拟与枢轴优化)、简单选择、树形选择、堆排序及二路归并排序的核心思想与 C++ 代码实现,旨在提供完整的算法参考。
城市逃兵28 浏览 分类
按待排序记录所在位置
- 内部排序:待排序记录存放在内存
- 外部排序:排序过程中需对外存进行访问的排序
按排序依据原则
- 插入排序:直接插入排序、折半插入排序、希尔排序
- 交换排序:冒泡排序、快速排序
- 选择排序:简单选择排序、堆排序
- 归并排序:2-路归并排序
存储结构
#include <iostream>
#include <stack>
#include <string>
#include <random>
using namespace std;
#define MAXSIZE 20
typedef int KeyType;
typedef string InfoType;
typedef struct {
KeyType key;
InfoType otherinfo;
} RedType;
typedef struct {
RedType r[MAXSIZE + 1];
int length;
} SqList;
void PrintList(const SqList& L) {
cout << "length = " << L.length << endl;
for (int i = 1; i <= L.length; ++i) {
cout << L.r[i].key << ;
}
cout << endl;
}
{
(n > MAXSIZE) n = MAXSIZE;
L.length = n;
;
;
( i = ; i <= n; ++i) {
L.r[i].key = (gen);
L.r[i].otherinfo = + (i);
}
}
", "
void InitRandomList(SqList& L, int n)
if
static std::mt19937 gen(std::random_device{}())
std::uniform_int_distribution<int> dist(0, 99)
for
int
1
dist
"info_"
to_string
插入排序
直接插入排序
思路
当处理到第 i 个数的时候,[1, i-1] 是有序的,我们希望找到一个 j,使得 [1, j] <= a[i] < [j+1, i-1]。
代码
void InsertSort(SqList& L) {
for (int i = 2; i <= L.length; ++i) {
if (L.r[i - 1].key > L.r[i].key) {
L.r[0] = L.r[i];
int j;
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];
}
}
}
折半查找插入排序
思路
优化插入排序的查找方法,使用折半查找。当处理到第 i 个数的时候,[1, i-1] 是有序的,符合折半查找的前提条件。采用左闭右闭写法,有效区间是 low <= high。
代码
void BInsertSort(SqList& L) {
for (int i = 2; i <= L.length; ++i) {
if (L.r[i].key < L.r[i - 1].key) {
int low = 1, high = i - 1;
L.r[0] = L.r[i];
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (L.r[mid].key > L.r[0].key) {
high = mid - 1;
} else {
low = mid + 1;
}
}
int j;
for (j = i - 1; j >= low; --j) {
L.r[j + 1] = L.r[j];
}
L.r[j + 1] = L.r[0];
}
}
}
希尔排序
思路
希尔排序是插入排序的优化版本,通过缩小增量进行分组插入。
代码
void ShellSort(SqList& L) {
for (int d = L.length / 2; d > 0; d /= 2) {
for (int i = 1 + d; i <= L.length; i++) {
if (L.r[i].key < L.r[i - d].key) {
L.r[0] = L.r[i];
int j;
for (j = i - d; j > 0 && L.r[0].key < L.r[j].key; j -= d) {
L.r[j + d] = L.r[j];
}
L.r[j + d] = L.r[0];
}
}
}
}
交换排序
冒泡排序
思路
相邻元素两两比较,将较大的元素逐步移动到序列末尾。
代码
void BubbleSort(SqList& L) {
auto& r = L.r;
bool change;
for (int i = L.length, change = true; i > 1 && change; --i) {
change = false;
for (int j = 1; j < i; ++j) {
if (r[j].key > r[j + 1].key) {
swap(r[j], r[j + 1]);
change = true;
}
}
}
}
快速排序
思路
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。
严蔚敏数据结构版本
int Partition(SqList& L, int low, int high) {
auto& r = L.r;
r[0] = r[low];
while (low < high) {
while (low < high && r[high].key >= r[0].key) --high;
r[low] = r[high];
while (low < high && r[0].key >= r[low].key) ++low;
r[high] = r[low];
}
r[low] = r[0];
return low;
}
void QuickSort(SqList& L, int low, int high) {
if (low >= high) return;
int p = Partition(L, low, high);
QuickSort(L, low, p - 1);
QuickSort(L, p + 1, high);
}
优化 1:用栈模拟递归
void QuickSort_Stack(SqList& L) {
stack<pair<int, int>> S;
auto& r = L.r;
S.push({1, L.length});
while (S.size()) {
auto e = S.top();
S.pop();
int low = e.first;
int high = e.second;
r[0] = r[low];
while (low < high) {
while (low < high && r[high].key >= r[0].key) --high;
r[low] = r[high];
while (low < high && r[low].key <= r[0].key) ++low;
r[high] = r[low];
}
r[low] = r[0];
int leftLen = (low - 1) - e.first;
int rightLen = e.second - (low + 1);
if (leftLen > rightLen) {
if (leftLen > 0) S.push({e.first, low - 1});
if (rightLen > 0) S.push({low + 1, e.second});
} else {
if (rightLen > 0) S.push({low + 1, e.second});
if (leftLen > 0) S.push({e.first, low - 1});
}
}
}
优化 2:优化取枢轴 随机取数
void SelectPivotRandom(SqList& L, int low, int high) {
int pivotPos = low + rand() % (high - low + 1);
std::swap(L.r[low], L.r[pivotPos]);
}
int Partition_Random(SqList& L, int low, int high) {
SelectPivotRandom(L, low, high);
auto& r = L.r;
r[0] = r[low];
while (low < high) {
while (low < high && r[high].key >= r[0].key) --high;
r[low] = r[high];
while (low < high && r[low].key <= r[0].key) ++low;
r[high] = r[low];
}
r[low] = r[0];
return low;
}
void QuickSort_Random(SqList& L, int low, int high) {
if (low >= high) return;
int p = Partition(L, low, high);
QuickSort(L, low, p - 1);
QuickSort(L, p + 1, high);
}
优化 3:优化取枢轴 三数取中
void SelectPivot_MedianOfThree(SqList& L, int low, int high) {
int mid = low + (high - low) / 2;
auto& r = L.r;
if (r[low].key > r[mid].key) std::swap(r[low], r[mid]);
if (r[low].key > r[high].key) std::swap(r[low], r[high]);
if (r[mid].key > r[high].key) std::swap(r[mid], r[high]);
std::swap(r[low], r[mid]);
}
int Partition_MedianOfThree(SqList& L, int low, int high) {
SelectPivot_MedianOfThree(L, low, high);
auto& r = L.r;
r[0] = r[low];
while (low < high) {
while (low < high && r[high].key >= r[0].key) --high;
r[low] = r[high];
while (low < high && r[low].key <= r[0].key) ++low;
r[high] = r[low];
}
r[low] = r[0];
return low;
}
void QuickSort_MedianOfThree(SqList& L, int low, int high) {
if (low >= high) return;
int p = Partition_MedianOfThree(L, low, high);
QuickSort_MedianOfThree(L, low, p - 1);
QuickSort_MedianOfThree(L, p + 1, high);
}
选择排序
简单选择排序
思路
- 把 1 到 n 的最小值放到 1
- 把 2 到 n 的最小值放到 2
- ……
- 把 n-1 到 n 的最小值放到 n-1
- n 到 n 只有一个数据,不用理了
- 所以每次都是找第 i 到 n 的最小值,放到第 i 个位置里
- 如果 i 本身就是最小值,就不需要交换了
代码
void SelectSort(SqList& L) {
auto& r = L.r;
for (int i = 1; i < L.length; ++i) {
int minIdx = i;
for (int j = i + 1; j <= L.length; ++j) {
if (r[j].key < r[minIdx].key) minIdx = j;
}
if (minIdx != i) {
swap(r[i], r[minIdx]);
}
}
}
树形选择排序
思路
代码
#include <algorithm>
#include <climits>
#include <cmath>
#include <vector>
void TreeSelectionSort(SqList& L) {
int n = L.length;
int leafSize = 1;
while (leafSize < n) leafSize *= 2;
int treeSize = 2 * leafSize;
vector<int> tree(treeSize);
for (int i = 0; i < leafSize; ++i) {
if (i < n) {
tree[leafSize + i] = i + 1;
} else {
tree[leafSize + i] = -1;
}
}
for (int i = leafSize - 1; i >= 1; --i) {
int left = tree[2 * i];
int right = tree[2 * i + 1];
if (left != -1 && right != -1) {
if (L.r[left].key <= L.r[right].key) tree[i] = left;
else tree[i] = right;
} else if (left != -1) tree[i] = left;
else tree[i] = right;
}
SqList tempL;
tempL.length = n;
for (int i = 1; i <= n; ++i) {
int minIdx = tree[1];
tempL.r[i] = L.r[minIdx];
L.r[minIdx].key = INT_MAX;
int idx = leafSize + (minIdx - 1);
while (idx > 1) {
int parent = idx / 2;
int left = tree[2 * parent];
int right = tree[2 * parent + 1];
if (left != -1 && right != -1) {
if (L.r[left].key <= L.r[right].key) tree[parent] = left;
else tree[parent] = right;
} else if (left != -1) tree[parent] = left;
else tree[parent] = right;
idx = parent;
}
}
L = tempL;
}
堆排序
思路
利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
vector 版本
static void siftDown(vector<int>& a, int n, int i) {
while (true) {
int l = 2 * i + 1;
int r = 2 * i + 2;
int maxIdx = i;
if (l < n && a[maxIdx] < a[l]) maxIdx = l;
if (r < n && a[maxIdx] < a[r]) maxIdx = r;
if (maxIdx == i) break;
std::swap(a[i], a[maxIdx]);
i = maxIdx;
}
}
void my_heap_sort(vector<int>& a) {
int n = (int)a.size();
for (int i = n / 2 - 1; i >= 0; --i) {
siftDown(a, n, i);
}
for (int i = n - 1; i > 0; --i) {
std::swap(a[i], a[0]);
siftDown(a, i, 0);
}
}
严蔚敏数据结构版本
对于 HeapAdjust:我们可以把它想象成一个'空位下沉'的过程:
- 挖坑(备份):
rc = H.r[s]。此时,下标 s 的物理空间还在,但在逻辑上,我们把它看作一个'坑',因为它原来的值已经被保护在 rc 里了。
- 填坑(上移):
H.r[s] = H.r[j]。我们找到了一个大孩子 j。我们把 j 的值搬上来填补 s 的坑。关键点:此时下标 j 的值虽然还在,但逻辑上 j 变成了新的'坑'。
- 落坑(归位):
H.r[s] = rc。当 rc 比所有的孩子都大(或者到底了),循环结束。此时 s 指向最终的空位,我们将 rc 放进去。
| 维度 | swap 交换法 ) | rc 覆盖法 (严蔚敏版/当前代码) |
|---|
| 操作本质 | 真的在内存里交换两个数 | 单向赋值,只移动数据,不交换 |
| 赋值次数 | 每次下沉需 3 次 赋值 | 每次下沉需 1 次 赋值 |
void HeapAdjust(SqList& H, int s, int m) {
auto rc = H.r[s];
for (int j = 2 * s; j <= m; j *= 2) {
if (j < m && H.r[j].key < H.r[j + 1].key) ++j;
if (!(rc.key < H.r[j].key)) break;
H.r[s] = H.r[j];
s = j;
}
H.r[s] = rc;
}
void HeapSort(SqList& H) {
for (int i = H.length / 2; i > 0; --i) {
HeapAdjust(H, i, H.length);
}
for (int i = H.length; i > 1; --i) {
swap(H.r[1], H.r[i]);
HeapAdjust(H, 1, i - 1);
}
}
归并排序
2-路归并排序
思路
严蔚敏数据结构伪代码
void Msort(RedType SR[], RedType TR1[], int s, int t) {
if (s == t) TR1[s] = SR[s];
else {
int m = (s + t) / 2;
Msort(SR, TR2, s, m);
Msort(SR, TR2, m + 1, t);
Merge(TR2, TR1, s, m, t);
}
}
void Merge(RecordType r1[], int low, int mid, int high, RecordType r[]) {
int i = low;
int j = mid + 1;
int k = low;
while ((i <= mid) && (j <= high)) {
if (r1[i].key <= r1[j].key) {
r[k] = r1[i];
++i;
} else {
r[k] = r1[j];
++j;
}
++k;
}
while (i <= mid) {
r[k] = r1[i];
k++;
i++;
}
while (j <= high) {
r[k] = r1[i];
k++;
j++;
}
}
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online