排序part

快速排序

  • 快排是一种基于分治的排序方法。
  • 现在我们要把a数组中的元素从小到大排序,其排序流程如下:
    • 假设现在要排序的区间为[l,r]。
      1. 如果区间长度小于等于1则直接退出,否则在区间中选择一个数字x作为比较元素;
      2. 将大于x的数字放在右边,小于x的数字放在左边,等于x的数字想放在哪边放在哪边,将x放在两组数字中间。
      3. 此时x的位置已经确定了,对其左右两边的区间分别进行递归即可。
  • 为了演示方便,接下来选取比较元素x时会选择区间[l,r]中的第一个数字。为了保证复杂度,实际操作市选区的数字要在区间中随机取。
  • 现在我们要对以下数列进行排序:
    在这里插入图片描述
  • 首先,我们选出比较元素x=9,此时我们要排序的区间是整个区间。
  • 在编写代码时,为了让左右两边的数字分开并且不额外使用储存空间,我们一般是这样做的:
    • 使用两个指针i,j,初始时i = l, j = r;
    • j向左找到第一个小于等于x的数字,把它放在位置i里,然后i向右找到第一个大于等于x的数字,把它放到位置j里;一直重复这个过程,直到两个指针指向同一个位置p,最后把位置p上的值设成x。
void quicksort(int l, int r){
    if(l >= r)
        return;
    swap(a[l], a[l + rand() % (r - l + 1)])
    int x = a[l];
    int i = l, j = r;
    while(l < r){
        while(i < j && a[j] > x)
            j--;
        if(i < j)
            a[i++] = a[j];
        while(i < j && a[i] < x)
            i++;
        if(i < j)
            a[j--] = a[i];
    }
    a[i] = x;
    quicksort(l, i - 1);
    quicksort(i + 1, r);
}

多关键字排序

  • 在实际应用的过程中还可能涉及到有多关键字的情况,例如针对二维平面上的点,经常要以横坐标为第一关键字,纵坐标为第二关键字进行排序。
  • 这个时候只需要改写一下数组元素的比较函数即可。
struct Node{
    int x, y;
    bool operator < (const Node &A)const{
        if(x != A.x)
            return x < A.x;
        return y < A.y;
    }
}A[N + 1];
void quicksort(int l, int r){
    if(l >= r)
        return;
    swap(a[l], a[l + rand() % (r - l + 1)])
    Node x = a[l];
    int i = l, j = r;
    while(l < r){
        while(i < j && x < a[j])//因为没有重构大于号,所以用小于号表示
            j--;
        if(i < j)
            a[i++] = a[j];
        while(i < j && a[i] < x)
            i++;
        if(i < j)
            a[j--] = a[i];
    }
    a[i] = x;
    quicksort(l, i - 1);
    quicksort(i + 1, r);
  • 使用快排来寻找第k小数字。

归并排序

  • 归并排序也是一种基于分治的排序方法。
  • 排序流程如下:
    • 假设现在要排序的区间为[l,r]:
      1. 如果区间长度为1则直接退出,否则将区间分为[l, m], [m + 1, r]两个部分,其中m = (l + r) / 2;
      2. 递归对两个子区间进行归并排序;
      3. 将两个已经排好序的子区间合并。
    • 一开始我们只要对区间[1,n]进行排序就好。
inline void mergesort(int l,int r){
    if(l == r)
        return;
    int m = (l + r) / 2;
    mergesort(l, m);
    mergesort(m + 1, r);
    int p1 = l, p2 = m + 1, tot = 0;//两个队列头指针,小的队列出队
    while(p1 <= m && p2 <= r){
        if(a[p1] <= a[p2])
            c[++tot] = a[p1++];
        else
            c[++tot] = a[p2++];
    }
    while(p1 <= m)
        c[++tot] = a[p1++];
    while(p2 <= r)
    c[++tot] = a[p2++];
    for(int i = 1; i <= tot; i++)
        a[l + i - 1] = c[i];
}

计数排序

  • 计数排序适合于值域范围较小的数字排序:
    1. 统计每个数字出现几次;
    2. 统计完每个元素的出现次数后,求一边前缀和,我们就知道了每个数字在排序完后的序列中出现的范围(第几小到第几小的都是这个数字);
    3. 把数字填入对应的位置即可。
  • 如何保证排序的稳定性?
    • 对于相同的数字,越后出现的排在越后面
    • 我们只需要到这确定原本每个位置的数字最后排在第几位就可以了。
inline void countingsort(){
    memset(c, 0, sizeof(c));
    for(int i = 1; i <= n; i++)//统计每个数字出现几次
        ++c[a[i]];
    for(int i = 2; i <= m; i++)//求前缀和
        c[i] += c[i - 1];
    for(int i = 1; i <= n; i++)//从后往前填入数字
        r[i] = c[a[i]]--;
    for(int i = 1; i <= n; i++)
        printf("%d", r[i]);
}

基数排序

  • 基数排序的核心思想是将每个待排序的元素拆分成m个关键字,然后从后往前依次对着m个关键字进行排序(严格来说,每次我们会针对一个后缀进行排序),每次排序是会使用上一次的排序结果
  • 基数排序一般会使用计数排序来完成每次关键字的排序。
  • 基数排序经常被用于进行字符串的排序,比如说后缀数组的核心就是基数排序。
  • 假设现在我们已经排完了第i个及以后的关键字,现在要排第i-1个关键字,这里其实是一个双关键字排序(第一关键字是原来的第i-1个关键字,第二关键字是原来的第i个及以后的关键字的rank)。
  • 只需要把数字按第i个及以后的关键字从小到大的顺序放在数组里,再做一次计数排序就好了。
#include<bits/stdc++.h>

using namespace std;

int n, m, a[100001], sa[100001], v[100001], r[100001],c[10];

inline void countingsort(){
    memset(c, 0, sizeof(c));
    for(int i  = 1; i <= n; i++)
        ++c[v[i]];
    for(int i = 1; i <= 9; i++)
        c[i] += c[i-1];
    for(int i = n; i; --i)
        r[sa[i]] = c[v[sa[i]]]--//上一次排序中的第i个数在当前关键字下应该排第几位
    for(int i = 1; i <= n; i++)
        sa[r[i]] = i;//更新这一次的排序,将原来输入的n个数填入他们应该在的位置
}

inline void radixsort(){
    for(int i = 1; i <= n; i++)
        sa[i] = i;
    int x = 1;
    for(int i = 1; i <= m; i++, x*=10){
        for(int j = 1; j <= n; j++)
            v[j] = a[j]/x%10;
        countingsort();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值