题解:P1271 【深基9.例1】选举学生会
feizhu_QWQ · · 题解
首先,在写这道题之前,我们要先了解排序是个什么东西。
举个例子:给你一个数组
1. 快速排序(系统)
在 C++ 的 stl 库里有这样一个函数:sort()。
我们简称快排,快速排序。
他的实现原理我们下一种方法会将,我们这里只需要知道它的实现方法:sort([数组名]+1,[数组名]+1+n);(省略中括号)。运行之后,数组就会变成从小到大的样子,你就会获得一个从小到大的数组了,他的时间复杂度是
#include<bits/stdc++.h>
using namespace std;
int a[20000005];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>a[i];//输入
}
sort(a+1,a+1+m);//stl排序
for(int i=1;i<=m;i++)
{
cout<<a[i]<<" ";
}
return 0;
}
2. 快速排序(手写)
上个方法我们介绍了排序的 stl,这里我们来介绍的是他的实现方式。\
想一想,我们有一个东西,比他小的数放左边,大的放右边,就可以把他们分类了。\
我们可以每次选择一个基准数,当然,我们一般用随机数,那么我们以这个基准数为准,比他小的放左边,比他大的放右边,就会得到一个较为有序的数组。\
但很明显,这不是完全从小到大的,所以我们又用到分治的思想:\
\
我们生活中有一句话“大事化小,小事化了” 在程序设计中,这也同样可行。\
比如一个数组求最大值的问题,给你一个长度为 1 5 2 6。\
我们把 1 5 拿出来,再把 2 6 拿出来。\
1 5 的最大值很明显我们用 5,2 6 是 6。\
我们再把两个最大值 5 6 比大小,得出来 6。\
这就是分治的典型案例,整个数组无法直接比大小,我们就把他分成小数组求,然后把小数组得来的答案往上传,和另一个小数组比较,循环往复,就得到了最终答案。
因为单纯一次基准数比较无法得出答案,我们就把基准数左边的数组再找一个基准数来排序,右边同理。\
我们可以用函数递归的方法实现,那么最后,数组就排好序了,这样我们有
#include<bits/stdc++.h>
using namespace std;
int a[1000005];
int n,m;
void qiuck_sort(int l,int r)
{
if(l>=r) return;
int mid=rand()%(r-l+1)+l; //随机基准数
int t=a[mid];
int q=l,h=r;
a[mid]=a[l];
while(q<h)
{
while(q<h&&a[h]>=t)//大的放右边
{
h--;
}
a[q]=a[h];
while(q<h&&a[q]<=t)//小的放左边
{
q++;
}
a[h]=a[q];
}
qiuck_sort(l,q-1);//继续递归
qiuck_sort(q+1,r);
return;
}
int main()
{
srand(time(0));//用随机数一定要加这句话
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>a[i];
qiuck_sort(1,m);
for(int i=1;i<=m;i++) cout<<a[i]<<" ";
return 0;
}//此代码正确性无法保证,随机性强
3. 归并排序
快速排序用递归实现,我们也可以用递归分治的思想,创造另一种算法。\
同样是排序,现在给你两个有序的数组,怎么把他们融合在一起,变成一个同样有序的数组?\
这里可以用到指针,我们从左到右,如果当前第一个数组的第一个数,比第二个数组的第一个数小,就先把前者放入一个新数组里,然后把原数组里的这个数删除,这样,我们就能得到新数组了。\
我们来模拟一下这个过程:\
1 4 5 和 2 3 6。\
首先,两个数组的第一个数分别是
接着,两个数组的第一个数分别是
接着,两个数组的第一个数分别是
接着,两个数组的第一个数分别是
接着,两个数组的第一个数分别是
最后只剩下了 1 2 3 4 5 6 这样的一个有序数组。
那么我们怎么得到两个有序的数组呢?\
这就是递归的事了。\
我们每次递归先直接调用函数,等着他把两个有序的数组传上来,我们再履行自己的职责,把这两个有序数组合并。\
归并排序很明显比其他排序复杂,所以他一般被用来求逆序对(我们到时候再说)。\
实现:
#include<bits/stdc++.h>
using namespace std;
int a[1000005],b[1000005];
int n,m;
void merge_sort(int l,int r)
{
int mid=(l+r)/2;
int q=l,h=mid+1,cnt=0;//这里的数组是连在一起的有序数组,所以我们从中间断开
while(q<=mid&&h<=r)
{
if(a[q]<=a[h])//如果第一个数小
{
cnt++;
b[cnt]=a[q];
q++;
}
else//第二个数小
{
cnt++;
b[cnt]=a[h];
h++;
}
}
while(q<=mid)
{
cnt++;
b[cnt]=a[q];
q++;
}
while(h<=r)
{
cnt++;
b[cnt]=a[h];
h++;
}
for(int i=1,j=l;i<=cnt;i++,j++)
{
a[j]=b[i];
}
return;
}
void m_sort(int l,int r)
{
if(l>=r) return;
int mid=(l+r)/2;
m_sort(l,mid);
m_sort(mid+1,r);//先往下递归
merge_sort(l,r);//再履行自己的职责
return;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>a[i];
m_sort(1,m);
for(int i=1;i<=m;i++) cout<<a[i]<<" ";
return 0;
}
4. 桶排序
桶排序的时间复杂度取决于这个数的大小,在有些地方有奇效。\
我们想,C++ 中有什么东西是有序的?\
数组下标!\
数组下标一般都是从
#include<bits/stdc++.h>
using namespace std;
int tong[10000005];
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x;
cin>>x;
tong[x]++;//记录数出现的次数
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=tong[i];j++) cout<<i<<" ";//
出现几次输出几次
}
return 0;
}
5. 堆排序
堆排序纯粹依靠另一种数据结构 - 堆,学名优先队列,但和队列没有关系。\
堆,他的操作有以下几种:\
priority_queue<int> q 定义一个叫做 priority_queue<int,vector<int>,greater<int> >\
q.push(x); 往名为 q.pop(); 删除堆 q.top() 返回堆 q.size() 返回堆
很明显,他可以把当前最大的元素告诉你,那我们就可以利用他排序。\
一开始我们先把所有元素塞入这个堆,然后我们每次输出堆的最小值(所以我们要用上面的第二种定义),然后把最小值丢出去,再进行上面的操作,就可以排完序了。\
因为堆单次操作的时间复杂度是
#include<bits/stdc++.h>
using namespace std;
int n,m;
priority_queue<int,vector<int>,greater<int> > q;//定义小根堆
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x;
cin>>x;
q.push(x);//把元素塞入数组
}
while(q.size()>0)
{
cout<<q.top()<<" ";//输出最小值
q.pop();//弹出最小值
}
return 0;
}
警惕,下面是三种时间复杂度很高的算法,且无法AC此题
6. 冒泡排序
上面我们都是从整体考虑问题,这里我们从个体来看待。\
假如我们只用 x<y 这一个东西,能否完成一整个数组的排序呢?当然可以!\
想想,我们是不是可以每次加入一个数,然后把他放入我们的新数组中?\
但我们该如何把这个新进来的元素归位呢?一个一个比大小!\
首先保证我们是上个数已经正确归位了才来处理这个元素的,所以我们的新数组一定是有序的!\
我们先把他放在数组最后面,然后我们把它和他前面的一个元素比大小,如果我们的新元素更小,就把他和前面的元素换个位置,然后到了下一个位置,继续和下一个元素比,重复操作,直到出现了比新元素更小的元素,或者已经到了第一个元素,就停止,然后处理下一个新进来的元素。\
这样,旧数组里的所有数解决完,我们就排完序啦!\
因为这种相邻元素比大小很像冒泡泡(我不觉得)所以叫冒泡排序。\
总的下来时间复杂度是
#include<bits/stdc++.h>
using namespace std;
int a[1000005];
int n,m;
bool cmp(int q,int h)
{
return q<h;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>a[i];
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<i;j++)//往前移动
{
if(a[j]>a[i])//比大小
{
swap(a[i],a[j]);
}
//这里可以选择跳出循环,但不跳出也不会出BUG,既然已经有比我小的了,前面的肯定更小
}
}
for(int i=1;i<=m;i++) cout<<a[i]<<" ";
return 0;
}
7. 选择排序
其实选择排序就是不用堆的堆排序。\
听起来很神奇的样子。\
堆排序是每次拿出最小值,是