【算法总结】十大排序对比及实现(C++)

十大排序对比

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 << " ";
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值