将杂乱无章的数据变得有规律。有各种各样的排序算法,看情况使用。
1. 选择排序
(1) 基本思想:
每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在待排序的数列的最前,直到全部待排序的数据元素排完。
(2)排序过程:
【示例】:
初 始 关键字 [49 38 65 97 76 13 27 49]
第一趟排序后 13[38 65 97 76 49 27 49]
第二趟排序后 13 27[65 97 76 49 38 49]
第三趟排序后 13 27 38 [97 76 49 65 49]
第四趟排序后 13 27 38 49 [76 97 65 49]
第五趟排序后 13 27 38 49 49 [97 65 76]
第七趟排序后 13 27 38 49 49 65 76 [97]
最后排序结果 13 27 38 49 49 65 76 97
代码:
void SelectSort(int R[])
{
for (int i=1;i<=n-1;i++)
{
K = I;
For (int j=i+1;j<=n;j++)
{
If (R[J] < R[K]) K = J;
}
If (K!=I)
{
Temp = R[I];
R[I] = R[K];
R[K] = Temp;
}
}
}
(时间复杂度为O(n^2))
2.冒泡排序
(1)基本的冒泡排序
①基本思想
依次比较相邻的两个数,把大的放前面,小的放后面。即首先比较第1个数和第2个数,大数放前,小数放后。然后比较第2个数和第3个数……直到比较最后两个数。第一趟结束,最小的一定沉到最后。重复上过程,仍从第1个数开始,到最后第2个数,然后……
由于在排序过程中总是大数往前,小数往后,相当气泡上升,所以叫冒泡排序。
下面是6个元素的排序的过程
4 5 7 1 2 3
┗━━┛
5 4 7 1 2 3
┗━━┛
5 7 4 1 2 3
┗━━┛
5 7 4 1 2 3
┗━━┛
5 7 4 2 1 3
┗━━┛
第一趟结束
5 7 4 2 3 ①
┗━━┛
7 5 4 2 3 1
┗━━┛
7 5 4 2 3 1
┗━━┛
7 5 4 2 3 1
┗━━┛ 第二趟结束
7 5 4 3 ② 1
┗━━┛
7 5 4 3 2 1
┗━━┛
7 5 4 3 2 1
┗━━┛ 第三趟结束
7 5 4 ③ 2 1
┗━━┛
7 5 4 3 2 1
┗━━┛ 第四趟结束
7 5 ④ 3 2 1
┗━━┛ 第五趟结束
⑦ ⑤ 4 3 2 1
②算法实现
for (int i=1;i<=n-1;i++)
for (int j=1;j<=n-i;j++)
if (a[j]<a[j+1])
{
temp=a[j]; a[j]=a[j+1]; a[j+1]=temp;
}
(2)改进
上例中,可以发现,第二趟结束已经排好序。但是计算机此时并不知道已经排好序。所以,还需进行一次比较,如果没有发生任何数据交换,则知道已经排好序,可以不干了。因此第三趟比较还需进行,第四趟、第五趟比较则不必要。
我们设置一个布尔变量bo 来记录是否有进行交换。值为false表示本趟中进行了交换,true 则没有。代码如下:
int i=1;
do
{
bo=true;
for (int j=1;j<=n-I;j++)
if (a[j]<a[j+1])
{
temp=a[j]; a[j]=a[j+1]; a[j+1]=temp;
bo=false;
}
I++;
}
While (!bo);
3. 桶排序
桶排序的思想是若待排序的记录的关键字在一个明显有限范围内(整型)时,可设计有限个有序桶,每个桶装入一个值(当然也可以装入若干个值),顺序输出各桶的值,将得到有序的序列。
例:输入n个0到100之间的不相同整数,由小到大排序输出。
#include<iostream>
#include <cstring>
using namespace std;
Int main()
{
int b[101],k,I,n;
memset(b,0,sizeof(b));
cin>>n;
for( i=1;I<=n;i++)
{
cin>>k; b[k]++;
}
for( i=0; I<=100;i++)
while (b[i]>0) {cout<<i<<" " ;b[i]--;}
cout<<endl;
}
4. 插入排序
插入排序是一种简单的排序方法,其算法的基本思想是:
假设待排序的数据存放在数组R[1..n]中,增加一个哨兵结点x。
(1) R[1]自成1个有序区,无序区为R[2..n];(2) 从i=2起直至i=n为止,将R[i]放在恰当的位置,使R[1..i]数据序列有序;
① x:=R[i];
② 将x与前i-1个数比较 , j:=i-1; while x<a[j] do j:=j-1;
③ 将R数组的元素从j位置开始向后移动: for k:=i downto j do a[k]:=a[k-1];
④ R[j]=x;
(3) 生成包含n个数据的有序区。.
例如:设n=8,数组R中8个元素是: 36,25,48,12,65,43,20,58,执行插入排序程序后,其数据变动情况:
第0步:[36] 25 48 12 65 43 20 58
第1步:[25 36] 48 12 65 43 20 58
第2步:[25 36 48] 12 65 43 20 58
第3步:[12 25 36 48] 65 43 20 58
第5步:[12 25 36 43 48 65] 20 58
第6步:[12 20 25 36 43 48 65] 58
第7步:[12 20 25 36 43 48 58 65]
代码:
void insertsort(int r[])
//对r[1..n]按递增序进行插入排序,x是监视哨
{
for (i=2;i<=n;i++) //依次插入r[2],...,r[n]
{
x=r[i];
j= i-1;
While (x< r[j]) //查找r[i]的插入位置//
{
r[j+1] =r[j]; //将大于r[i]的元素后移//
j--;
}
r[j+1] = x; //插入r[I] //
}
}
5.快速排序
快速排序是对冒泡排序的一种改进。它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
假设待排序的序列为{a[L],a[L+1],a[L+2],……,a[R]},首先任意选取一个记录(通常可选中间一个记作为枢轴或支点),然后重新排列其余记录,将所有关键字小于它的记录都放在左子序列中,所有关键字大于它的记录都放在右子序列中。由此可以将该“支点”记录所在的位置mid作分界线,将序列分割成两个子序列和。这个过程称作一趟快速排序(或一次划分)。
一趟快速排序的具体做法是:附设两个指针i和j,它们的初值分别为L和R,设枢轴记录取mid,则首先从j所指位置起向前搜索找到第一个关键字小于的mid的记录,然后从i所指位置起向后搜索,找到第一个关键字大于mid的记录,将它们互相交换,重复这两步直至i>j为止。
快速排序的时间的复杂性是O(nlog2n),速度快,但它是不稳定的排序方法。就平均时间而言,快速排序是目前被认为是最好的一种内部排序方法
由以上讨论可知,从时间上看,快速排序的平均性能优于前面讨论过的各种排序方法,但快速排序需一个栈空间来实现递归。若每一趟排序都将记录序列均匀地分割成长度相接近的两个子序列,则栈的最大深度为log(n+1)。
代码:
void qsort(int l,int r)
{ int i,j,mid,p;
i=l;
j=r;
mid=a[(l+r) / 2];
do
{
while (a[i]<mid) i++;
while (a[j]>mid) j--;
if (i<=j)
{
p=a[i];
a[i]=a[j];
a[j]=p;
i++;
j--;
}
}
while (i<=j);
if (l<j) qsort(l,j);
if (i<r) qsort(i,r);
end;
6.归并排序
归并排序(Merge sort)就是利用归并操作把一个无序表排列成一个有序表的过程。二路归并排序的过程是首先把待排序区间(即无序表)中的每一个元素都看作为一个有序表,则n个元素构成n个有序表,接着两两归并(即第一个表同第二个表归并,第三个表同第四个表归并,…),得到[n/2]个长度为2的有序表(最后一个表的长度可能小于2),称此为一趟归并,然后再两两有序表归并,得到[[n/2]/2]个长度为4的有序表(最后一个表的长度可能小于4),如此进行下去,直到归并第[log2n]趟后得到一个长度为n的有序表为止。
归并排序算法我们用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。对左右子区间的排序与原问题一样,所以我们可以调用同样的子程序,只是区间大小不一样。
代码:
归并简洁版
#include<iostream>
using namespace std;
void Merge_sort(int *a,int x,int y,int *t){
if(y-x>1){
int m=x+(y-x)/2;
int p=x,q=m,i=x;
Merge_sort(a,x,m,t);
Merge_sort(a,m,y,t);
while(p<m||q<y){
if(q>=y||(p<m&&a[p]<=a[q])) t[i++]=a[p++];
else t[i++]=a[q++];
}
for(i=x;i<y;i++) a[i]=t[i];
}
}
int main(){
int a[100],t[100];
int i,n;
cin>>n;
for(i=0;i<n;i++) cin>>a[i];
Merge_sort(a,0,n,t);
for(i=0;i<n;i++) cout<<t[i]<<' ';
}
归并排序完全版:
#include<iostream>
using namespace std;
int n;
void Merge(int (&a)[100],int low,int mid,int high){
int i,j,k;
int r[100]={0};
i=low; j=mid+1; k=low;
while(i<=mid&&j<=high){
if(a[i]<=a[j]){
r[k]=a[i];
i++;
k++;
}
if(a[j]<a[i]){
r[k]=a[j];
j++;
k++;
}
}
for(;i<=mid;i++){
r[k]=a[i];
k++;
}
for(;j<=high;j++){
r[k]=a[i];
k++;
}
for(i=low;i<=high;i++){
a[i]=r[i];
}
}
void MergeSort(int (&a)[100],int low,int high){
int mid;
int low1=low;
int high1=high;
if(low<high){
mid=(low+high)/2;
MergeSort(a,low1,mid);
MergeSort(a,mid+1,high1);
Merge(a,low1,mid,high1);
}
}
int main(){
int a[100];
int i;
cin>>n;
for(i=1;i<=n;i++){
cin>>a[i];
}
MergeSort(a,1,n);
for(i=1;i<=n;i++){
cout<<a[i]<<' ';
}
cout<<endl;
return(0);
}
7.c++标准模板库STL(sort版)
使用排序算法必须包含 algorithm 头文件
自带排序算法的一般形式为:
sort(arr+m,arr+n);
sort(arr+m,arr+n,comp);
对于sort(arr+m,arr+n) 我们举个简单的例子:
#include<iostream>
#include<algorithm>
using namespace std;
int a[10];
int main()
{
for (int i=0;i<10;++i) cin>>a[i];
sort(a+0,a+10);
for (int i=0;i<10;++i) cout<<a[i]<<' ';
cout<<endl;
return 0;
}
当然,有时我们需要从大到小的进行排序。那么我们可以用 sort(arr+n,arr+m,comp)进行排序。
不过,在调用sort(arr+n,arr+m,comp) 之前我们需要自己写个 comp函数。
从大到小排序的comp函数可以这样写:
int my_comp(const int & a,const int & b)
{
return a>b;
}
代码:
#include<iostream>
#include<algorithm>
using namespace std;
int a[10];
int my_comp(const int & a,const int & b)
{
return a>b;
}
int main()
{
for (int i=0;i<10;++i) cin>>a[i];
sort(a+0,a+10,my_comp);
for (int i=0;i<10;++i) cout<<a[i]<<' ';
cout<<endl;
return 0;
}
在更多情况下,我们不仅对一个特征进行排序,而是多个特征。例如将学生的成绩进行排序,当然用上面的做法是行不通的。这时,我们就想到了结构体这种数据类型。当我们采用sort()函数的默认规则排序结构体时,sort()默认结构体中的第一个成员为第一关键字,第二个成员为第二关键字,……,第N个元素为第N关键字,然后从小到大排序。
例如我们要将学生的成绩从大到小排序,当成绩相同时,根据姓名字典序小的优先的规则排进行序。显然我们无法采用默认规则进行排序。
这时我们可以定义这样的comp:
int score_comp(const student & a,const student & b)
{
if (a.score>b.score) return 1
if (a.score<b.score) return 0
if (a.name<b.name) return 1
return 0
}
8.c++标准模板库STL(优先队列版)
自己参悟吧……道理同7
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
struct node
{
string name;
int sumscore;
int mathscore;
}p[1001];
struct cmp
{
bool operator()(const node &a,const node &b)
{
if(a.sumscore>b.sumscore) return true;
return false;
}
};
int main()
{
int n;cin>>n;
priority_queue<node,vector<node>,cmp> q;
for(int i=1;i<=n;i++)
{
cin>>p[i].name;
cin>>p[i].sumscore>>p[i].mathscore;
q.push(p[i]);
}
for(int i=1;i<=n;i++)
{
cout<<q.top().name<<" ";
q.pop();
}
}
9.各种排序算法的比较
1.稳定性比较
插入排序、冒泡排序、二叉树排序、二路归并排序及其他线形排序是稳定的。
选择排序、希尔排序、快速排序、堆排序是不稳定的。
2.时间复杂性比较
插入排序、冒泡排序、选择排序的时间复杂性为O(n2);快速排序、堆排序、归并排序的时间复杂性为O(nlog2n);桶排序的时间复杂性为O(n);
若从最好情况考虑,则直接插入排序和冒泡排序的时间复杂度最好,为O(n),其它算法的最好情况同平均情况相同;若从最坏情况考虑,则快速排序的时间复杂度为O(n2),直接插入排序和冒泡排序虽然平均情况相同,但系数大约增加一倍,所以运行速度将降低一半,最坏情况对直接选择排序、堆排序和归并排序影响不大。
由此可知,在最好情况下,直接插入排序和冒泡排序最快;在平均情况下,快速排序最快;在最坏情况下,堆排序和归并排序最快。
3.辅助空间的比较
桶排序、二路归并排序的辅助空间为O(n),快速排序的辅助空间为O(log2n),最坏情况为O(n),其它排序的辅助空间为O(1);
4.其它比较
插入、冒泡排序的速度较慢,但参加排序的序列局部或整体有序时,这种排序能达到较快的速度。反而在这种情况下,快速排序反而慢了。
当n较小时,对稳定性不作要求时宜用选择排序,对稳定性有要求时宜用插入或冒泡排序。
若待排序的记录的关键字在一个明显有限范围内时,且空间允许是用桶排序。
当n较大时,关键字元素比较随机,对稳定性没要求宜用快速排序。
当n较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
请发表评论