在续讲故事之前,我们先来聊一聊勇者打工的经历。
*
停,在那之前还得科普一件事情。我们来讲一下什么是排序算法的稳定性。
这个大概是最玄学的东西了,引用百科的话:
“假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。”
好的定义弄明白了,我们也大概知道不稳定有什么坏处了——就是如果有相同的数字的话,不稳定的算法就会将他们再排一遍,从而浪费时间。
然后呢,再补上一句百科的话,咱们开始讲正文:
“需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。”
*
话说勇者在keke的工厂里干活,keke的工厂主要是负责工业(而且这个世界上只有这一个工厂),所以来找他要钢材的人特别多。Keke按照订单顺序已经生产出了不同长短的钢材,但是为了派货方便,他决定将钢材按照从大到小的顺序排列。
“什么鬼啊怎么keke要解决问题就来找我呢?”勇者抱怨道。
“听说解决问题的人可以获得一个int的Gold……”路由器小声说。
“那还等什么啊快去干啊!”
然而keke的厂子很多,每一个厂子的特性不一样。
——————————
选择排序
(瞎编题时间)
Keke的第一个厂子没什么特别的,客人都是拖延症,不着急来拿钢材,所以随便搞,只要将钢材从大到小排列就好了。
——————————
勇者虽然一根筋,但是最基础的想法还是有的“只要枚举每次没排序区间内的最大值将它放在前面不就好了?”
于是,他很快的写好了一段魔法代码,经路由器认定,这就是选择排序。而且由于用到了两层循环,故复杂度为O(N*N)。
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int INF=-99999999;
int main(){
int n,a[100];
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int maxn=INF,num=0,sum=1;
while(sum!=n){
for(int i=sum;i<=n;i++){
if(maxn<a[i]){
maxn=a[i];
num=i;
}
}
int t=a[num];a[num]=a[sum];a[sum]=t;sum++;maxn=INF;
}
for(int i=1;i<=n;i++){
printf("%d ",a[i]);
}
return 0;
}
*是的这里路由器再插一嘴。
从前也误导过其他童鞋,因为我曾经在最开始学排序的时候写过这样的代码*
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int main(){
int n,a[100];
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(a[i]>a[j]){
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
}
for(int i=1;i<=n;i++){
printf("%d ",a[i]);
}
return 0;
}
*
我一直以为是冒泡排序…………特别尴尬,所以在此纠正我写的其实是很low的选择排序哦!(但是说实话,这代码好背啊,而且如果换排序的话一般都会用快排的说很少用到o(n*n)的算法……)”
*
冒泡排序
原代码
但是勇者得意的时候,路由器却说“我查了一下,这个魔法貌似不稳定啊……”
勇者火冒三丈“啊?但是你看一下定义啊!它哪里不稳定了?”
确实啊,两个魔法程序丝毫不违背定义,因此路由器经过深思熟虑之后,自我认为他们应该是稳定的。其实网上关于选择排序到底是不是稳定的已经争论很久了,在此路由器给出的看法是:看代码实现是怎样的,他的稳定性就是怎样的。
“那既然有人说不稳定的话,你不如再写一个……”路由器小声嘟囔道。
“好啊!有人挑战我不是吗,我再写一个!”
于是,勇者花了一天一夜的时间,又写了一个魔法,经过路由器认定,这个是冒泡排序。O(N*N)也是显而易见的。
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int main(){
int n,a[100];
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
bool ok=1;
for(int i=1;i<=n-1;i++){
for(int j=1;j<=n-1;j++){
if(a[j]<a[j+1]){
int t=a[j];
a[j]=a[j+1];
a[j+1]=t;
ok=0;
}
}
if(ok==1)break;
}
for(int i=1;i<=n;i++){
printf("%d ",a[i]);
}
return 0;
}
勇者自豪地说:“这个就是将两个相邻的数字进行比较,将大的往上浮,小的往下沉,这样一遍一遍来,总是能将所有大的放在上面,小的沉在下面。”
“而且,这个魔法最高明之处(勇者又有点嘚瑟了),在于在已经排好序的情况下,用ok判断是否已经排好序了,如果我们跑一趟却发现没有发生一次交换的话,那就说明已经排好序了,因此直接跳出循环就可。”
“诶嘿,想不到你还蛮聪明的吗?不过我这里还有一个更好的优化”路由器神秘一笑,在某个魔法书里找到了:
鸡尾酒排序——冒泡排序的改进
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int main(){
int n,a[100];
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int top=1,bot=n;
bool ok=1;
while(top<=bot){
ok=1;
for(int j=top;j<=bot-1;j++){
if(a[j]<a[j+1]){
int t=a[j];
a[j]=a[j+1];
a[j+1]=t;
ok=0;
}
}
if(ok==1)break;
for(int j=bot-1;j>=top;j--){
if(a[j+1]>a[j]){
int t=a[j];
a[j]=a[j+1];
a[j+1]=t;
ok=0;
}
}
if(ok==1)break;
top++;bot--;
}
for(int i=1;i<=n;i++){
printf("%d ",a[i]);
}
return 0;
}
“这个之所以好,就在于它从后往前扫了一遍,从前往后又扫了一遍,等于双向冒泡,对于像类似于3 2 1 4这类的数据有奇效哦。”
勇者高兴地将冒泡排序魔法交给了第一个厂子,开赴下一个厂子。
————————————
但是故事还没有结束。勇者到底能不能拿到int个G呢?敬请期待下一篇……