【C++】十大排序算法代码

基于洛谷P1177【模板】排序,整理了十大排序算法及其部分变体的多种代码写法(包括一些STL写法),一些写法来源于网络,并根据自己的理解做了注释。当然,各排序算法写法都非常多,不可能覆盖所有写法,欢迎补充。而且个人能力有限,如有错误欢迎指出。

以下代码包括:

STL直接排序

选择排序

冒泡排序

插入排序

希尔排序

快速排序(3种写法)

三路快速排序(2种写法)

桶排序(2种写法,其中第一种等价于计数排序)

计数排序(等价于桶排序1)

基数排序

堆排序(手打堆、STL堆、STL优先队列)

归并排序

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <iterator>
using namespace std;

/*# 洛谷P1177 【模板】排序
## 题目描述:将读入的 N 个数从小到大排序后输出。
## 输入格式:第一行为一个正整数 N。第二行包含 N 个空格隔开的正整数 a_i,为你需要进行排序的数。
## 输出格式:将给定的 N 个数从小到大输出,数之间空格隔开,行末换行且无空格。
## 样例 #1
### 样例输入 #1
```
5
4 2 4 5 1
```
### 样例输出 #1
```
1 2 4 4 5
```
## 提示:
对于 20% 的数据,有 1<=N<=10^3;
对于 100% 的数据,有 1<=N<=10^5,1<=a_i<=10^9。*/

/*注意:由于内存和时间限制,以下各排序方法不能保证通过P1177,具体如下:
(在C++20编译下,未开O2优化)
STL直接排序:AC (100分)
选择排序:#1 AC, #2~5 TLE (20分)
冒泡排序:#1 AC, #2~5 TLE (20分)
插入排序:#1、3、5 AC, #2、4 TLE (60分)
希尔排序:AC (100分)
快速排序:(1)#1、2 AC, #3、4、5 TLE (40分)
        (2)#1、2 AC, #3、4、5 TLE (40分)
        (3)AC (100分)
三路快速排序:(1)新开数组:AC (100分)
             (2)不新开数组:#1、2、3、5 AC, #4 TLE (80分)
桶排序: (1)单一键值桶:#1、2 RE, #3、4、5 AC (60分)
        (2)区间桶(链表实现):#1、4、5 RE, #2、3 TLE (0分)
计数排序:#1、2 RE, #3、4、5 AC (60分) (等价于桶排序1)
基数排序:(1)十进制:AC (100分)
        (2)二进制:AC (100分)
堆排序:(1)手打堆:AC (100分)
        (2)STL堆:AC (100分)
        (3)STL优先队列:AC (100分)
归并排序:AC (100分)
*/

int N;
int a[100000];
int heap[100001];   //堆排序的堆
int b[100000];  //归并排序用的临时数组、计数排序用的临时数组
int bucket[1000000];    //桶排序用的桶、计数排序用的临时数组
int lt[100000], eq[100000], gt[100000]; //三路快速排序中新开三个数组保存小于、等于、大于pivot的数

//选择排序
void selection_sort(){  
    for (int i=0; i<N; i++){    //枚举当前第i小的位置
        for (int j=i+1; j<N; j++){  //选出无序序列中最小的与当前位置i交换
            if (a[i]>a[j]){ //如果当前值比选出来的最小值(位于第i位)小,则交换
                int temp;
                temp = a[i];
                a[i] = a[j];
                a[j] = temp;
            }
        }
    }
}

//冒泡排序
void bubble_sort(){ 
    for (int i=0; i<N-1; i++){    //排序轮数,每轮将一个最大数“沉底”,最后一个数字不用操作
        for (int j=0; j<N-1-i; j++){    //此时,后i个数字已经有序,只需要在前面n-i个数中操作即可,下标从0到n-i-1.
            if (a[j]>a[j+1]){   //一旦发现前一个比后一个大,就立刻交换,让后一个比前一个大
                int temp;
                temp = a[j];
                a[j] = a[j+1];
                a[j+1] = temp;
            }
        }
    }
}

//插入排序
void insertion_sort(){ 
    for (int i=0; i<N; i++){    //遍历每一个数,第i位之前为有序数列
        int temp = a[i];    //临时储存第i个数
        int j = i-1;    //从第i-1个数开始向前遍历
        while(j>=0 && a[j]>temp){
            a[j+1] = a[j];  //比temp大的向右移,给temp腾位置
            j--;
        }
        a[j+1] = temp;  //将temp插入在第一个小于temp的数的后面
    }

}

//希尔排序(又名分组插入排序/缩小增量排序)
void shell_sort(){
    int gap;    //间隔:每gap个数分为一组,如a[i], a[i+gap], a[i+2*gap], ...分为一组
    for (gap = N>>1; gap > 0; gap >>= 1){   //gap从N/2开始,每轮折半,最后一轮必有gap=1
        for (int group = 0; group < gap; group++){  //遍历每一组,共有gap组
            for (int i=group; i<N; i+=gap){ //各组内进行插入排序,该组内i之前为有序数列
                int temp = a[i];    //待插元素
                int j = i-gap;  //j从组内的前一个数位置(i-gap)开始向前遍历,步长为gap
                while (j>=0 && temp<a[j]){
                    a[j+gap] = a[j];    //比temp大的向右移,给temp腾位置
                    j -= gap;
                }
                a[j+gap] = temp;    //将temp插入在组内第一个小于temp的数的后面
            }
        }
    }
}

//快速排序
void quick_sort(int start, int end){    
    int left = start;   //左指针
    int right = end;    //右指针
    int pivot = left;   //先取最左边的作为轴(pivot)
    int temp = a[pivot];
    if (left>=right){   //当长度<=1时,无需排序,直接返回
        return;
    }
    while (left<right){
        while (a[right]>=temp && left<right){
            right--;    //在右半边找到第一个小于pivot值的
        }
        if (left<right){
            a[left] = a[right]; //小于的放在左边
            left++; //左指针向右拨
        }
        while (a[left]<=temp && left<right){
            left++; //在左半边找到第一个大于pivot值的
        }
        if (left<right){
            a[right] = a[left]; //大于的放在右边
            right--;
        }
    }   //一旦离开该while循环,必有left==right,将该位置作为pivot的插入位置
    pivot = left;   //如上所述更新pivot
    a[pivot] = temp; //把之前取出来的pivot的值填回到中间
    //递归,对左边和右边分别用快速排序
    quick_sort(start, pivot-1);
    quick_sort(pivot+1, end);
}

//快速排序,简化的写法
void quick_sort2(int start, int end){   
    int left = start;
    int right = end;
    int pivot = left;
    int temp = a[pivot];
    if (left>=right){   //当长度<=1时,无需排序,直接返回
        return;
    }
    while (left<right){
        while(left<right && a[right]>=temp){
            right--;
        }
        a[left] = a[right];
        while(left<right && a[left]<=temp){
            left++;
        }
        a[right] = a[left];
    }
    pivot = left;
    a[pivot] = temp;
    quick_sort2(start, pivot-1);
    quick_sort2(pivot+1, end);
}

//快速排序,用swap的写法
void quick_sort3(int start, int end){   
    int left = start;
    int right = end;
    int pivot = (left+right)/2;
    int temp = a[pivot];
    do{
        while(a[right]>temp){
            right--;
        }
        while(a[left]<temp){
            left++;
        }
        if (left<=right){
            swap(a[left], a[right]);
            left++;
            right--;
        }
    }while (left<=right);

    if (start<right){quick_sort3(start, right);}
    if (left<end){quick_sort3(left, end);}
}

//三路快速排序:另开数组的写法
void quick_sort_3ways(int start, int end){
    if (start >= end){return;}  //若长度为0或1就返回
    int pivot = rand()%(end-start+1) + start; //生成[l,r]之间的随机数作为pivot
    int idx1=0, idx2=0, idx3=0;
    for (int i=start; i<=end; i++){
        if (a[i]<a[pivot]){
            lt[idx1++] = a[i];
        }else if (a[i] == a[pivot]){
            eq[idx2++] = a[i];
        }else{
            gt[idx3++] = a[i];
        }
    }
    //将三个数组重新放回a
    for (int i=0; i<idx1; i++){
        a[start+i] = lt[i];
    }
    for (int i=0; i<idx2; i++){
        a[start+idx1+i] = eq[i];
    }
    for (int i=0; i<idx3; i++){
        a[start+idx1+idx2+i] = gt[i];
    }
    //递归处理原lt和gt数组
    quick_sort_3ways(start, start+idx1-1);
    quick_sort_3ways(start+idx1+idx2, end);
}

//三路快速排序:无需另开数组的写法
void quick_sort_3ways_2(int start, int end){
    if (start >= end){return;}
    int pivot = start; //直接用第一个数作为pivot
    int p_lt, p_gt, i;
    p_lt = start;   //a[start+1...p_lt]保存小于a[pivot]的值(后面回换回a[start...p_lt-1]),注意开始时要让start+1到p_lt长度为0,应把初值设为start
    p_gt = end+1;   //a[p_gt...end]保存大于a[pivot]的值,注意开始时要让p_gt到end长度为0,应把初值设为end+1
    i = start+1;    //i为当前要处理的元素下标
    while(i<p_gt){
        if (a[i] == a[pivot]){
            //如果当前元素k==pivot的值v,将该元素放到中间等于v的部分a[p_lt+1...i-1]
            i++;    //i右移,将当前元素纳入等于部分,并前往下一元素
        }else if (a[i] < a[pivot]){
            //如果当前元素k<pivot的值v,将该元素放到左边小于v的部分a[start+1...p_lt]
            //只需将k与等于v的第一个元素交换,再将p_lt向右移动一位
            swap(a[i], a[p_lt+1]);
            p_lt++; //当前元素被纳入小于部分
            i++;    //i前往下一元素
        }else{
            //如果当前元素k>pivot的值v,将该元素放到右边大于v的部分a[p_gt...end]
            //只需将k与gt-1位置的元素交换,再将p_gt向左移动一位
            swap(a[i], a[p_gt-1]);
            p_gt--; //当前元素被纳入大于部分,注意这里i不需要移动
        }
    }
    swap(a[pivot], a[p_lt]);    //交换位于start(pivot)处的元素和p_lt处的元素

    //此时a[start...p_lt-1]<v,a[p_lt...p_gt-1]==v,a[p_gt...end]>v。(其中v为本次pivot处的值)
    quick_sort_3ways_2(start, p_lt-1);  //递归小于的部分
    quick_sort_3ways_2(p_gt, end);  //递归大于的部分
}

//桶排序(注意事实上每个桶都可以是一个范围,不一定是一个整数值,实际上这个桶排序等价于计数排序)
void bucket_sort(){
    memset(bucket, 0, 1000000); //可支持的数据最大值为999999
    int max_idx = 0;
    for (int  i=0; i<N; i++){
        bucket[a[i]]++; //入桶
        if (a[i]>max_idx){
            max_idx = a[i]; //记录桶下标的最大值
        }
    }
    int k = 0;  //输出用的下标
    for (int i=0; i<max_idx+1; i++){    //枚举桶
        if (bucket[i]>0){
            for (int j=0; j<bucket[i]; j++){
                a[k] = i;   //输出
                k++;
            }
        }
    }
}

//桶排序(桶内链表实现)
int bucket_num;
struct ListNode{
    ListNode* mNext;    //链接下一个节点
    int mData;  //当前节点数据
    explicit ListNode(int i=0): mData(i), mNext(NULL){}//构造函数和成员初始化表
};

ListNode* insert(ListNode* head, int val){  //链表插入(用于桶内插入排序)
    ListNode dummyNode; //哑节点
    ListNode *newNode = new ListNode(val);  //构造一个值为val的新节点
    ListNode *pre, *curr;   //用于遍历的指针,分别指向前一节点和当前节点
    dummyNode.mNext = head;
    pre = &dummyNode;
    curr = head;
    while (NULL!= curr && curr->mData <= val){
        pre = curr;
        curr = curr->mNext;
    }   //如果当前节点值比要插入的值小,则继续向后走,跳出循环后即找到待插入位置
    newNode->mNext = curr;  //此时待插入值val<curr的值,所以将新节点链入到curr之前
    pre->mNext = newNode;
    return dummyNode.mNext; //返回新的head(head可能不变也可能已经改变)
}

ListNode* Merge(ListNode *head1, ListNode *head2){  //链表合并,用于桶间合并
    ListNode dummyNode; //合并后新链表的哑节点
    ListNode *dummy = &dummyNode; //指向哑节点的指针
    while (NULL!=head1 && NULL!=head2){
        if (head1->mData <= head2->mData){  //较大的head链入
            dummy->mNext = head1;
            head1 = head1->mNext;
        }else{
            dummy->mNext = head2;
            head2 = head2->mNext;
        }
        dummy = dummy->mNext;   //dummy始终代表新链表尾,以便新元素链入
    }
    //其中一个链表全部链入之后,将另一个链表剩下的元素全部链入
    if (NULL != head1) {dummy->mNext = head1;}
    if (NULL != head2) {dummy->mNext = head2;}
    return dummyNode.mNext; //返回新的头节点head
}

//桶排序(链表实现)主函数:每个bucket链表覆盖一个范围
void bucket_sort_linkedlist(int bn=10){ //bucket_num可作为参数输入,默认为10个bucket
    bucket_num = bn;
    int max = a[0];
    int min = a[0];
    for (int i=0; i<N; i++){
        if (a[i]>max) {max = a[i];}
        if (a[i]<min) {min = a[i];}
    }
    int bucket_len = (max-min)/bucket_num + 1;  //每个bucket的区间长度
    vector<ListNode*> buckets(bucket_num, (ListNode*)(0));
    for (int i=0; i<N; i++){
        int index = a[i]/bucket_len;
        ListNode *head = buckets.at(index);
        buckets.at(index) = insert(head, a[i]);
    }
    ListNode *head = buckets.at(0);
    for (int i=1; i<bucket_num; i++){   //链接所有链表
        head = Merge(head, buckets.at(i));
    }
    for (int i=0; i<N; i++){
        a[i] = head->mData;
        head = head->mNext;
    }   //列表转回数组
}

//计数排序(每个桶对应一个键值)
void counting_sort(){
    memset(bucket, 0, 1000000); //可支持的数据最大值为999999
    int max_idx = 0;
    for (int  i=0; i<N; i++){
        bucket[a[i]]++; //入桶
        if (a[i]>max_idx){
            max_idx = a[i]; //记录桶下标的最大值
        }
    }
    for (int i=1; i<=max_idx; i++){
        bucket[i] += bucket[i-1];   //bucket[i]记录a中<=i的元素个数
    }
    for (int i=N-1; i>=0; i--){ //从后向前遍历a,将排序后的数组暂存于b中
        b[bucket[a[i]] - 1] = a[i]; //逐个将a中元素填入b中
        bucket[a[i]]--; //已经填入了一个元素,则将对应的计数减去1
    }
    for (int i=0; i<N; i++){
        a[i] = b[i];    //将b复制回a
    }
}

//基数排序

//基数排序辅助函数,求a中数据的最大位数(k进制,默认位十进制)
int maxdigit(int k=10){
    int max_data = a[0];
    for (int i=1; i<N; i++){
        if (max_data<a[i]){
            max_data = a[i];
        }
    }
    int digit = 1;
    while (max_data >= k){
        max_data /= k;
        digit++;
    }
    return digit;
}

//基数排序主函数
void radix_sort(int k=10){ //k为进制数,默认为十进制
    int digit = maxdigit(k);
    int *tmp = new int[N];
    int *count = new int[k];   //计数器(“桶”)
    int radix = 1;
    for (int i=0; i<digit; i++){    //每一位数进行一次排序
        for (int j=0; j<k; j++){
            count[j] = 0;   //每次分配前清空计数器
        }
        for (int j=0; j<N; j++){
            int c = (a[j]/radix) % k;   //统计从低到高第(i+1)位数的出现次数(入桶)
            count[c]++;
        }
        for (int j=1; j<k; j++){
            count[j] += count[j-1]; //同计数排序的处理,count[j]存储该位上数码小于等于j的数的个数
        }
        for (int j=N-1; j>=0; j--){ //同计数排序,从后向前遍历
            int c = (a[j]/radix) % k;
            tmp[count[c]-1] = a[j];
            count[c]--;
        }
        for (int j=0; j<N; j++){
            a[j] = tmp[j];
        }   //按照从低到高第(i+1)位排序完成
        radix *= k; //基数向前一位
    }
    delete []tmp;
    delete []count;
}

//堆排序:升序排序采用大根堆

//入堆并上浮
void shiftup(int idx){   
    if (idx==1 || heap[idx/2]>=heap[idx]){
        return; //若已经在堆顶或者其父节点已经比自己大,则返回
    }else{
        swap(heap[idx/2], heap[idx]); //否则(即父节点小于自己),交换父子
        shiftup(idx/2);    //递归上浮
    }
}
//删除根节点(将根节点移到最后)后将新的堆顶向下移
void shiftdown(int idx, int heap_len){  
    int child = idx;
    if (idx*2<=heap_len){ //左子节点存在
        if (heap[idx*2]>heap[idx]){ //如果左子节点比父节点大,则交换
            child = idx*2;
        }
        if (idx*2+1<=heap_len){ //右子节点存在
            if (heap[idx*2+1]>heap[child]){
            //如果右子节点比父节点和左子节点都大,则交换,更新child
                child = idx*2+1;
            }
        }
        if(child!=idx){     
            swap(heap[child], heap[idx]);   //交换下移
            shiftdown(child, heap_len); //向下递归下沉
        }else{  //若child==idx,则说明没有满足可交换条件的子节点,下移结束
            return;
        }
    }else{  //左子节点不存在,说明该节点是叶子节点,下移结束
        return;
    }    
}
//堆排序外层函数
void heap_sort(){
    int heap_len=0;
    for (int i=0; i<N; i++){    //建立大根堆
        heap[i+1] = a[i];   //为了方便起见,堆的编号从1开始,到N
        heap_len++;
        shiftup(heap_len);
    }

    //将堆顶(最大)和堆底交换(出堆)
    while(heap_len>0){
        swap(heap[1], heap[heap_len]);
        heap_len--;     //出堆之后堆长度减一
        // cout << "heap_len="<<heap_len << endl;
        shiftdown(1, heap_len); //将堆顶下移(维护大根堆)
    }
    for (int i=0; i<N; i++){
        a[i] = heap[i+1];
    }
}

//堆排序:运用STL的heap实现
//STL的堆位于<algorithm>头文件中,含四个操作函数make_heap(), push_heap(), pop_heap(), sort_heap()
//注意堆需要一个底层容器,可以用<vector>中的向量vector<T>结构实现
void heap_sort_stl(){
    vector<int> va(a, a+N);
    //也可用va.push_back(???); push_heap(_First, _Last, _Comp);来入堆
    //用pop_heap(_First, _Last, _Comp);来将堆顶推出,置于容器底,再用va.pop_back();将该元素彻底删除
    make_heap(va.begin(), va.end());    //默认建立大根堆,或加入第三个参数less<int>()以建立大根堆
    //可以用make_heap(va.begin(), va.end(), greater<int>());来建立小根堆
    sort_heap(va.begin(), va.end());    //堆排序,默认维护大根堆,排完序后为升序
    int i = 0;
    for (vector<int>::iterator it=va.begin(); it!=va.end(); ++it){
        a[i] = *it;
        i++;
    }
    return;
}

//堆排序:运用STL的优先队列实现
//优先队列底层用堆实现,在<queue>头文件中
//大根堆:priority_queue<int> q; 小根堆:priority_queue<int, vector<int>, greater<int>> q;
//插入:q.push(x); 删除根结点:q.pop(); 访问根节点:q.top();
void heap_sort_stl_pq(){
    priority_queue<int, vector<int>, greater<int>> min_heap;    //定义优先队列(小根堆)
    for (int i=0; i<N; i++){
        min_heap.push(a[i]);    //入堆
    }
    for (int i=0; i<N; i++){
        a[i] = min_heap.top();  //将最小的输出回a
        min_heap.pop();         //将已经输出的最小值从堆中删除
    }
}

//归并排序

//用于合并两个序列
void merge(int left, int mid, int right){   
    if (left >= right){return;} //序列只有<=1个元素,不能再分,则返回
    int i=left;
    int j=mid+1;
    int tot=0; //i是左序列指针,j是右序列指针,tot是b中的总指针
    while (i<=mid && j<=right){
        if (a[i]<=a[j]){    //升序排序,先放小的那一个,在将对应的指针后拨
            b[tot++] = a[i];
            i++;
        }else{
            b[tot++] = a[j];
            j++;
        }
    }
    while (i<=mid){     //如果比较完之后左序列还没放完,就直接将剩下的放进来
        b[tot++] = a[i];
        i++;
    }
    while (j<=right){   //如果比较完之后右序列还没放完,就直接将剩下的放进来
        b[tot++] = a[j];
        j++;
    }
    for (int t=0; t<=right-left; t++){  //从b中将结果赋回a
        a[left+t] = b[t];
    }
    return;
}
//归并排序外层递归函数
void merge_sort(int left, int right){
    if (left >= right){return;} //序列只有<=1个元素,不能再分,则返回
    int mid = (left+right)/2;   //取中间值用于对分
    merge_sort(left, mid);      //左半边递归
    merge_sort(mid+1, right);   //右半边递归
    merge(left, mid, right);    //合并
}

//读入N个数,从小到大排序后输出
int main(){
    cin >> N;
    for (int i=0; i<N; i++){
        cin >> a[i];
    }
    //直接使用STL排序
    sort(a, a+N);

    //选择排序
    // selection_sort();

    //冒泡排序
    // bubble_sort();

    //插入排序
    // insertion_sort();

    //希尔排序
    // shell_sort();

    //桶排序
    // bucket_sort();
    // bucket_sort_linkedlist();

    //计数排序
    // counting_sort();

    //基数排序
    // radix_sort();   //默认以十进制处理
    // radix_sort(2);  //以二进制处理,效果相同

    //快速排序
    // quick_sort(0, N-1);
    // quick_sort2(0, N-1);
    // quick_sort3(0, N-1);

    //三路快速排序
    // quick_sort_3ways(0, N-1);
    // quick_sort_3ways_2(0, N-1);

    //堆排序
    // heap_sort();
    // heap_sort_stl();
    // heap_sort_stl_pq();

    //归并排序
    // merge_sort(0, N-1);

    for (int i=0; i<N-1; i++){
        cout << a[i] << " ";
    }
    cout << a[N-1] << endl;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值