一些排序
(写这篇文章的目的–方便自己复习,打个标记,如果能帮到大家那就更好了,受众是和我一样的初学者)
1.冒泡排序
(想象冒泡场景,两两比较,然后不断实现最右边就是当前范围中的最值)
/*
----------------------------------
改良版的冒泡排序 (参考信息学奥赛一本通)
加入了bool变量判断 如果在扫描一遍没有交换操作
说明已经冒泡完毕
----------------------------------
*/
#include <iostream>
using namespace std;
const int N=10010;
int a[N];
int main(void)
{
bool ok;
int n;
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n-1;i++)
{
ok=true;
for(int j=0;j<n-i-1;j++)
if(a[j]>a[j+1])
{
swap(a[j],a[j+1]);
ok=false;
}
if(ok==true) break;
}
for (int i = 0; i < n; i++)
cout << a[i] << ' ';
//升序排序
//5个元素 只需要冒泡4个剩下一个自然排好了所以是n-1次
//然后每次排序需要交换的次数 比如第一遍是将5个元素冒泡 就要交换4次
//如果一遍下来ok还是true说明没有交换 排序就已经完成了
return 0;
}
2.选择排序
(每一次选出范围中的最值当作当前元素实现排序)
#include <iostream>
using namespace std;
const int N = 10010;
int a[N];
int main(void)
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 0; i < n - 1; i++)
{
for (int j = i + 1; j < n; j++)
{
if (a[i] > a[j])
swap(a[i], a[j]);
//不断交换让a[i]就是最值 因为每次都是两两中选出最值 最后得到的就是最值
}
}
for (int i = 0; i < n; i++)
cout << a[i] << ' ';
}
3.桶排序
(通过值的范围存桶,注意:值的范围如果有负数需要先偏移后面再调整,然后按顺序输出非空桶)
#include <iostream>
using namespace std;
const int N = 101, M = 10010;
//数值范围是-50到50,桶就开到0-100(因为桶存不了负)
//下面是样例 n=7( -45 -49 -30 20 6 45 49)
int ton[N], a[M];
int main(void)
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 0; i < n; i++)
ton[a[i] + 50]++; //扫一遍入桶
for (int i = 0; i < N; i++)
while (ton[i] > 0)
{
cout << i - 50 << ' ';
ton[i]--;
}
return 0;
}
4.插入排序
(想象你那打斗地主插扑克牌的样子)
#include <iostream>
using namespace std;
const int N = 10010;
int a[N];
int main(void)
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 1; i < n; i++)//从第二张牌开始因为只有一张牌的话本身就不用排序
{
int j = i - 1, temp = a[i];//存下待插入值 并且j指向待插入值前一个位置从后往前遍历
while (j >= 0 && a[j] > temp)
{
a[j + 1] = a[j]; //如果a[j]大于待插入值 待插入值原本位置空出来 将大值往后挪 直到找到第一个小值 可以想象你抓牌的时候 拿到一张牌怎么插入
j--;
}
a[j + 1] = temp; //j+1值最后就是插入的位置 要么是j减到了从后往前第一个比temp小的数的位置 要么就是j到-1
}
for (int i = 0; i < n; i++)
cout << a[i] << ' ';
return 0;
}
5.快速排序
(板子来源:acwing)
(每次调整区间 让标准值左边小于等于标准值,标准值右边大于等于标准值,然后递归处理,核心思想:分治)
#include <iostream>
using namespace std;
const int N=100010;
int a[N];
void quick_sort(int a[],int l,int r)
{
if(l>=r) return;//或者l==r 只有一个元素就弹出 如果没有元素就是l>r
int i=l-1,j=r+1,x=a[(l+r+1)>>1];//①确定标准值
while(i<j)//②调整区间
{
while(a[++i]<x);//让x的左边都小于等于x
while(a[--j]>x);//让x的右边都大于等于x
if(i<j)swap(a[i],a[j]);
}
quick_sort(a,l,i-1),quick_sort(a,i,r);//③递归排序
}
int main(void)
{
int n;
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
quick_sort(a,0,n-1);
for(int i=0;i<n;i++)cout<<a[i]<<' ';
return 0;
}
快排应用 acwing.786 第k个数
给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k个数。
输入格式
第一行包含两个整数 n和 k。
第二行包含 n个整数(所有整数均在 1∼109范围内),表示整数数列。
输出格式
输出一个整数,表示数列的第 k小数。
数据范围
1≤n≤100000,
1≤k≤n
输入样例:
5 3
2 4 1 5 3
输出样例:
3
#include <iostream>
using namespace std;
const int N=100010;
int a[N];
int quick_pick(int a[],int l,int r,int k)
{
if(l==r)return a[l];//如果区间只有一个数那就是它了
int i=l-1,j=r+1,x=a[(l+r+1)>>1];
while(i<j)
{
while(a[++i]<x);
while(a[--j]>x);
if(i<j)swap(a[i],a[j]);
}
int sl=i-l;//j-l+1;左边的长度
if(sl>=k) return quick_pick(a,l,i-1,k);
else return quick_pick(a,i,r,k-sl);
/*
-------------------------
我的理解就是边排序边找
找的是排序后的第k个数
然后不断缩小范围
例如首先是左边的第k个
然后递归分治的第k'个数...
就是这个过程
-------------------------
*/
}
int main(void)
{
int n,k;
cin>>n>>k;
for(int i=0;i<n;i++)cin>>a[i];
cout<<quick_pick(a,0,n-1,k);
return 0;
}
6.归并排序
(板子来源:acwing)
(快排调区间分治,归并分治合并区间)
#include <iostream>
using namespace std;
const int N=100010;
int a[N],b[N];
void merge_sort(int a[],int l,int r)
{
if(l==r)return;//如果区间只有一个元素就不用归并了因为一个数没有顺序可言
int mid=l+r>>1;
merge_sort(a,l,mid),merge_sort(a,mid+1,r);
int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r)
{
if(a[i]<=a[j])b[k++]=a[i++];
else
b[k++]=a[j++];
}
//扫尾
while(i<=mid) b[k++]=a[i++];
while(j<=r) b[k++]=a[j++];
//归主
for(int i=l,k=0;i<=r;i++,k++)
a[i]=b[k];
}
/*
----
31245 (全是个位数)
312 |45
31` 2| 4 `5
3` 1 `2 |45
13 `2 |45
123 |45
12345
----
*/
int main(void)
{
int n;
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
merge_sort(a,0,n-1);
for(int i=0;i<n;i++)cout<<a[i]<<' ';
return 0;
}
归并运用 acwing.788 逆序对的数量
给定一个长度为 n的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 i个和第 j 个元素,如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。
输入格式
第一行包含整数 n,表示数列的长度。
第二行包含 n个整数,表示整个数列。
输出格式
输出一个整数,表示逆序对的个数。
数据范围
1≤n≤100000,
数列中的元素的取值范围 [1,109]。
输入样例:
6
2 3 4 5 6 1
输出样例:
5
#include <iostream>
using namespace std;
const int N=100010;
int a[N],b[N];
long long merge_sort(int a[],int l,int r)
{
if(l==r)return 0;
int mid=l+r>>1;
long long res=merge_sort(a,l,mid)+merge_sort(a,mid+1,r);//统计左边的逆序对和右边的逆序对
int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r)
{
if(a[i]<=a[j])b[k++]=a[i++];
else
{
b[k++]=a[j++];
res+=mid-i+1;//统计脚踏两只船的逆序对 一定是有序的 所以i后面的都比j的大
/*
----
//画图就好理解了
// 234561
// 234 561
// 23 4 56 1
// 2 3 4 5 6 1
23 4 56 1 这里的1在这产生2对 (56)
//234 156 然后这里的1 产生3对 (234)
// 1 2 3 4 5 6
----
*/
}
}
while(i<=mid)b[k++]=a[i++];
while(j<=r)b[k++]=a[j++];//扫尾 已经有序
for(int i=l,k=0;i<=r;i++,k++)a[i]=b[k];
return res;
}
int main(void)
{
int n;
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
cout<<merge_sort(a,0,n-1);
return 0;
}
7.堆排序(板子来源:acwing)
(小根堆 数组存 父节点永远比两个子节点小)
#include <iostream>
using namespace std;
const int N=100010;
int h[N],cnt;//数组h 模拟堆 cnt当前节点指针
void down(int u)
{
int t=u;
//三个数 找到最小的那个
if(2*u<=cnt&&h[t]>h[2*u])t=2*u;
if(2*u+1<=cnt&&h[t]>h[2*u+1])t=2*u+1;
if(t!=u)
{
swap(h[t],h[u]);//交换使得父节点最小
down(t);//递归处理 t是最大的 往下放
}
}
int main(void)
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
h[++cnt]=-x;
}//最小值 变成大根堆的骚操作
for(int i=n/2;i;i--)down(i);//优化 down一遍有序 从n/2开始往下down
while(n--)
{
cout<<-h[1]<<' ';
swap(h[1],h[cnt]);
cnt--;
down(1);
}
return 0;
}
输入:
5
4 5 1 3 2
输出结果:
5 4 3 2 1