基于洛谷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;
}