十大排序对比
1. 排序思想
2. 十大排序算法对比
in-place 占用常数内存,不占用额外内存
out-place 占用额外内存
稳定:原数组元素的相对次序不变
不稳定:原数组元素的相对次序改变
1 冒泡排序
/* 冒泡排序
* 思想:不断将最大的冒泡到最后一位,下一次冒泡的对象不包括已经冒泡的对象。
* 注意点:要设置一个 is_swap 标记是否已经有序,节省时间
* 时:O(n²); 最坏:O(n²) 最好:O(n); 空:O(1); in_place; 稳定;
*/
#include <iostream>
#include <vector>
using namespace std;
void BubbleSort(vector<int> &q) {
int n = q.size();
if (n <= 1) return;
bool is_swap; // 标记是否发生交换
for (int i = 1; i < n; ++i) { // 换 n - 1 轮
is_swap = false;
for (int j = 1; j < n - i + 1; ++j) { // 每次换到已经换好的前一个为止
if (q[j - 1] > q[j]) {
swap(q[j - 1], q[j]);
is_swap = true;
}
}
if (!is_swap)
return; // 如果这一轮都没有发生交换,则证明已经有序,直接返回
}
}
int main() {
vector<int> q = {1, 5, 2, 4, 3, 7, 6, 8, 9, 4};
BubbleSort(q);
for (auto it : q) {
cout << it << " ";
}
}
2 选择排序
/* 选择排序
* 思想:分为排序数组和未排序数组,不断在未排序数组中找到最小值,添加到排序数组的最后。
* 时:O(n²) 最坏:O(n²) 最好:O(n²); 空:O(1); in_place; 不稳定;
*/
#include <iostream>
#include <vector>
using namespace std;
void SelectSort(vector<int> &q) {
int n = q.size();
if (n <= 1) return;
int min; // 未排序中的最小值
for (int i = 0; i < n; ++i) {
min = i;
for (int j = i + 1; j < n; ++j) {
if (q[j] < q[min]) min = j; // 在未排序数组中找到最小值
}
swap(q[min], q[i]);
}
}
int main() {
vector<int> q = {1, 5, 2, 4, 3, 7, 6, 8, 9, 4};
SelectSort(q);
for (auto it : q) {
cout << it << " ";
}
}
3 插入排序
/* 插入排序
* 思想:分为排序数组和未排序数组,不断将未排序数组插入到排序数组中合适的位置。
* 时:O(n²); 最坏:O(n²) 最好:O(n); 空:O(1); in_place; 稳定;
*/
#include <iostream>
#include <vector>
using namespace std;
void InsertSort(vector<int> &q) {
int n = q.size();
if (n <= 1) return;
for (int i = 1; i < n; ++i) { // 一个一个插入未排序数组的数到排序数组中
for (int j = i; j > 0 && q[j] < q[j - 1]; --j) { // 找到正确位置插入
swap(q[j], q[j - 1]);
}
}
}
int main() {
vector<int> q{9, 8, 7, 6, 4, 4, 5, 3, 2, 1};
InsertSort(q);
for (auto it : q) {
cout << it << " ";
}
return 0;
}
4 希尔排序
/* 希尔排序
* 思想:先设置不同的间隔给数组分组,然后根据不同的间隔进行插入排序,不断缩小间隔,
* 使得元素一次性可以朝最终位置前进一大步。(分组 + 插入排序)
* 时:O(nlog(n)); 最坏:O(nlog²n) 最好:O(n); 空:O(1); in_place; 稳定;
* 最坏时间复杂度与设置的分组大小有关
*/
#include <iostream>
#include <vector>
using namespace std;
void ShellSort(vector<int>& q) {
int n = q.size();
if (n <= 1) return;
for (int gap = n / 2; gap > 0; gap /= 2) { // 分组
for (int i = gap; i < n; ++i) { // 插入排序
for (int j = i; j - gap >= 0 && q[j] < q[j - 1]; j -= gap) {
swap(q[j], q[j - 1]);
}
}
}
}
int main() {
vector<int> q = {1, 5, 2, 4, 3, 7, 6, 8, 9, 4};
ShellSort(q);
for (auto it : q) {
cout << it << " ";
}
}
5 归并排序
/* 归并排序
* 思想:标准的分治思想,将整个数组的排序问题划分为左右两段数组的排序问题,递归处理每段的排序,
* 最终再合并子问题。
* 时:O(nlog²(n));最坏:O(nlog²(n))最好:O(nlog²(n));空:O(n);out_place;稳定;
*/
#include <iostream>
#include <vector>
using namespace std;
void MergeSort(vector<int> &q, int l, int r) {
if (l >= r) return;
// 划分子问题
int mid = l + r >> 1;
MergeSort(q, l, mid), MergeSort(q, mid + 1, r); // 递归处理子问题
// 合并子问题
int i = l, j = mid + 1, k = 0;
vector<int> tmp(r - l + 1, 0);
while (i <= mid && j <= r) {
if (q[i] < q[j])
tmp[k++] = q[i++];
else
tmp[k++] = q[j++];
}
while (i <= mid) tmp[k++] = q[i++];
while (j <= r) tmp[k++] = q[j++];
for (int i = l, k = 0; i <= r; i++, k++) {
q[i] = tmp[k];
}
}
int main() {
vector<int> q{9, 8, 7, 6, 4, 4, 5, 3, 2, 1};
MergeSort(q, 0, q.size() - 1);
for (auto it : q) {
cout << it << " ";
}
return 0;
}
6 快速排序
/* 快速排序
* 思想:找到一个枢纽,比它小的放在左边,比它大的放在右边,然后左右两部分递归下去,直到全部有序。
* 注意点:虽然是随便找一个值,但是取中间超时的概率会小一些。
* 注意划分的边界值,可能会无限递归的问题。
* 时:O(nlog²(n));最坏:O(n²)最好:O(nlog²(n));空:O(log²(n));in_place;不稳定;
*/
#include <iostream>
#include <vector>
using namespace std;
void QuickSort(vector<int> &q, int l, int r) { // 双指针实现
// 终止条件
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l + r >> 1]; // 找到枢纽
while (i < j) { // 不能是 i <= j,不然会无限递归
do ++i;
while (q[i] < x);
do --j;
while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
// 递归处理子问题
QuickSort(q, l, j), QuickSort(q, j + 1, r);
}
int main() {
vector<int> q{9, 8, 7, 6, 4, 4, 5, 3, 2, 1};
QuickSort(q, 0, q.size() - 1);
for (auto it : q) {
cout << it << " ";
}
return 0;
}
7 堆排序
/* 堆排序
* 思想:利⽤堆这种数据结构所设计的⼀种排序算法。建堆,然后
* 不断将堆顶元素与最后元素交换,再调整堆。
* 时:O(nlog²(n));最坏:O(nlog²(n))最好:O(nlog²(n));空:O(1);out_place;不稳定;
*/
#include <iostream>
#include <vector>
using namespace std;
// 堆下沉
void down(vector<int> &q, int len, int x) {
int lc = 2 * x + 1, rc = 2 * x + 2; // 左、右子节点
int max = x;
if (lc < len && q[lc] > q[max]) max = lc; // 跟左子结点比较
if (rc < len && q[rc] > q[max]) max = rc; // 跟右子结点比较
if (max != x) {
swap(q[max], q[x]); // 交换为最大的
down(q, len, max); // 调整堆,继续下沉
}
}
void HeapSort(vector<int> &q) {
int n = q.size();
if (n <= 1) return;
// 建堆
for (int i = n / 2 - 1; i >= 0; --i) down(q, n, i);
// 不断将堆顶元素与最后元素交换,并调整堆
for (int i = n - 1; i >= 1; --i) {
swap(q[0], q[i]);
down(q, i, 0);
}
}
int main() {
vector<int> q{9, 8, 7, 6, 4, 4, 5, 3, 2, 1};
HeapSort(q);
for (auto it : q) {
cout << it << " ";
}
return 0;
}
8 计数排序
/* 计数排序
* 思想:对于每个元素x,找到比x小的元素的个数,就可以确定出x的位置。
* 特点:排序数量小的时候最快,能达到O(n + m), m 为排序的元素个数,用空间换时间
* 当 m > nlog(n) 后就不是最快了
* 局限:小数、负数等不能排序
* 时:O(n + k); 最坏:O(n + k) 最好:O(n + k); 空:O(n + k); out_place;稳定;
*/
#include <iostream>
#include <vector>
using namespace std;
void CountSort(vector<int>& q, int n) {
vector<int> cnt(n, 0); // 计数数组
// 对数组中的数字计数
for (auto it : q) {
cnt[it]++;
}
// 求前缀和,可以算出相应数字的排名
for (int i = 1; i < n; ++i) {
cnt[i] += cnt[i - 1];
}
// 复制一个原数组用来遍历,按顺序更新原数组
vector<int> tmp(q);
for (int i = 0; i < q.size(); ++i) {
q[cnt[tmp[i]] - 1] = tmp[i]; // 根据排名放入原数组对应位置
cnt[tmp[i]]--; // 放入原数组后,该数字数量-1,不然相同数字会在同一地方放入
}
}
int main() {
vector<int> q = {1, 5, 2, 4, 3, 7, 6, 8, 9, 4};
int max_len = 10; // 数据的最大范围
CountSort(q, max_len);
for (auto it : q) {
cout << it << " ";
}
return 0;
}
9 桶排序
/* 桶排序
* 思想:将数组分到有限数量的桶⾥,每个桶再个别排序。
* 注意点:这里的实现时将桶数量设置为最大元素和最小元素的之差,因为当桶的数量接近于元素数量时,
* 可以达到O(n)的时间复杂度。
* 时:O(n + k); 最坏:O(n²) 最好:O(n); 空:O(n + k); out_place; 稳定;
*/
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
void BucketSort(vector<int> &q) {
if (q.size() <= 1) return;
int low = *min_element(q.begin(), q.end());
int high = *max_element(q.begin(), q.end());
int n = high - low + 1; // 划分桶的数量为数组元素的范围
// 将所有元素放入对应的桶中
vector<int> buckets(n);
for (auto it : q) ++buckets[it - low];
int idx = 0;
for (int i = 0; i < n; ++i) { // 将桶中元素按序放入数组中
for (int j = 0; j < buckets[i]; ++j) { // 放入相同元素
q[idx++] = i + low;
}
}
}
int main() {
vector<int> q = {1, 5, 2, 4, 3, 7, 6, 8, 9, 4};
BucketSort(q);
for (auto it : q) {
cout << it << " ";
}
}
10 基数排序
/* 基数排序
* 思想:求出数据的最高位,从最低位开始排序,没次只排当前位的顺序,排到最高位时,
* 则整个数组有序。排序方式跟基数排序类似,也是求每个桶的前缀和,再一次放入临时数组,
* 不过基数排序只需要分10个桶,而计数排序分的桶的数量为数据的最大值。
* 时:O(n * k); 最坏:O(n * k) 最好:O(n * k); 空:O(n + k); out_place; 稳定;
*/
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
// 求数据的最高位,决定排序的次数
int MaxBit(vector<int> &q) {
int b = 1; // 最大位数
int t = 10; // 当前数据范围
for (int i = 0; i < q.size(); ++i) {
if (q[i] > t) {
b++; // 扩大位数
t *= 10; // 扩大范围
}
}
return b;
}
// 基数排序
void RadixSort(vector<int> &q) {
int n = q.size();
if (n <= 1) return;
int d = MaxBit(q); // 排序次数
int buckets[10]; // 分10个桶
int radix = 1; // 基数
int tmp[n]; // 临时数组存储每次排好的顺序
int i, j, k;
for (i = 1; i <= d; ++i) { // 进行d次排序
memset(buckets, 0, sizeof buckets); // 每次分配前清空桶
for (j = 0; j < n; ++j) { // 计算j号桶要放多少数
k = (q[j] / radix) % 10;
buckets[k]++;
}
// 跟计数排序类似
for (j = 1; j < 10; ++j) {
buckets[j] += buckets[j - 1]; // 求前缀和,可以算出相应数字的排名
}
for (j = n - 1; j >= 0; --j) {
k = (q[j] / radix) % 10;
tmp[buckets[k] - 1] = q[j]; // 根据排名放入原数组对应位置
buckets[k]--; // 放入数组后,该数字数量-1,不然相同数字会在同一地方
}
for (int j = 0; j < n; ++j) {
q[j] = tmp[j]; // 更新原数组顺序
}
radix *= 10; // 比较下一位数
}
}
int main() {
vector<int> q = {1, 5, 2, 4, 3, 7, 6, 8, 9, 4};
RadixSort(q);
for (auto it : q) {
cout << it << " ";
}
}