排序算法
排序算法
我们常常使用数据排序,但如果暴力算法,时间复杂度太大,需要用到排序算法
排序是指将一组原本无序的数据元素,按照具体要求,排列成有规律的按关键字有序(从小到大或从大到小)的序列。
一、基于比较的排序
\(O(n^2)\)家族 选择排序、插入排序、冒泡排序
\(O(n\log n)\)家族 堆排序、归并排序、快速排序
冒泡排序
算法思想:
从数组中第一个数开始,依次遍历数组中的每一个数,通过相邻比较交换,每一轮循环下来找出剩余未排序数的中的最大数并”冒泡”至数列的顶端。
算法步骤:
(1)从数组中第一个数开始,依次与下一个数比较并次交换比自己小的数,直到最后一个数。如果发生交换,则继续下面的步骤,如果未发生交换,则数组有序,排序结束,此时时间复杂度为\(O (n)\);
(2)每一轮”冒泡”结束后,最大的数将出现在乱序数列的最后一位。重复步骤(1)。
稳定性:稳定排序。
时间复杂度: \(O (n)\)至\(O (n^2)\),平均复杂度为\(O (n ^ 2)\)
最好的情况:如果待排序数据序列为正序,则一趟冒泡就可完成排序,排序码的比较次数为n-1次,且没有移动,时间复杂度为\(O (n)\)。
最坏的情况:如果待排序数据序列为逆序,则冒泡排序需要 n-1 次趟起泡,每趟进行 n-i 次排序码的比较和移动,即比较和移动次数均达到最大值:
移动次数等于比较次数,因此最坏时间复杂度为O(n^2)。
for(int i=1;i<=n;i++)
for(int j=n;j>=i+1;j--)
if(a[j]<a[j-1])
swap(a[j],a[j-1]); //交换
优化:
for(int i=1;i<=n-1;i++)
{
int ok=1;
for(int j=n;j>=i+1;j--)
if(a[j]<a[j-1])
{
ok=0;
swap(a[j],a[j-1]);
}
if(ok==1) break;
}
选择排序
每一趟从序列中选出最小(或最大)的数放到待排序的序列最前,直到全部排完
算法实现:
第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;
第二趟,从第二个记录开始的 n-1 个记录中再选出关键码最小的记录与第二个记录换;
以此类推,
第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,直到整个序列按关键码有序。
for(int i=1;i<n;i++)
{
int k=i;
for(int j=i+1;j<=n;j++)
if(a[j]<a[k]) k=j;
if(k!=i) swap(a[i],a[k]);
}
插入排序
思想:将一个记录插入到已排序好的有序表中,从而得到一个新记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。要点是设立哨兵,作为临时存储和判断数组边界之用。
for(i=0;i<n;i++)
{
for(j=i-1;j>=0;j--)//在前面有序区间找合适的插入位置
if(a[j]<a[i]) break;//找到比a[i]小的位置插入其后
if(j!=i-1)
{
temp=a[i];
for(k=i-1;k>j;k--)
a[k+1]=a[k];//空出空位,让给a[i]
a[k+1]=temp;
}
}
优点:支持插入元素,每插入一个元素,可在O(n)的时间里,找到其位置,返回数组;
快速排序
快速排序采用的思想是分治思想。
算法原理:
(1)从待排序的n个记录中任意选取一个记录(通常选取第一个记录)为分区标准;
(2)把所有小于该排序列的记录移动到左边,把所有大于该排序码的记录移动到右边,中间放所选记录,称之为第一趟排序;
(3)然后对前后两个子序列分别重复上述过程,直到所有记录都排好序。
稳定性:不稳定排序。
时间复杂度: O(nlog2n)至O(n^2),平均时间复杂度为O(nlgn)。
最好的情况:是每趟排序结束后,每次划分使两个子文件的长度大致相等,时间复杂度为O(nlog2n)。
最坏的情况:是待排序记录已经排好序,第一趟经过n-1次比较后第一个记录保持位置不变,并得到一个n-1个元素的子记录;第二趟经过n-2次比较,将第二个记录定位在原来的位置上,并得到一个包括n-2个记录的子文件,依次类推 ,最坏O(n^2)
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);
}
归并排序
基于分治的思想,一个问题可能在小范围之内有确定的结果,逐步扩大范围,递推出去,得到结果。
首先不断分解数组,直到数组有序(内有一个元素)(二分),按数组下标分治,将两个有序的数组归并成一个有序数组;
void msort(int s,int t)
{
if(s==t) return;
int mid=(s+t)/2;
msort(s,mid);
msort(mid+1,t);
int i=s,j=mid+1,k=s;
while(i<=mid&&j<=t)
{
if(a[i]<=a[j])
{
r[k]=a[i];k++;i++;
}
else
{
r[k]=a[j];k++;j++;
}
}
while(i<=mid)
{
r[k]=a[i];k++;i++;
}
while(j<=t)
{
r[k]=a[j];k++;j++;
}
for(int i=s;i<=t;i++) a[i]=r[i];
}
应用:求逆序对。总的逆序对个数=左半部分逆序对个数+右半部分逆序对个数+左边的数大于右边的数的个数
void msort(int s,int t)
{
if(s==t) return;
int mid=(s+t)/2;
msort(s,mid);
msort(mid+1,t);
int i=s,j=mid+1,k=s;
while(i<=mid&&j<=t)
{
if(a[i]<=a[j])
{
r[k]=a[i];k++;i++;
}
else
{
r[k]=a[j];k++;j++;
ans+=mid-i+1;//统计
}
}
while(i<=mid)
{
r[k]=a[i];k++;i++;
}
while(j<=t)
{
r[k]=a[j];k++;j++;
}
for(int i=s;i<=t;i++) a[i]=r[i];
}
堆排序
堆排序基于堆这个数据结构,堆是一个完全二叉树,分为大(小)根堆,性质是父节点比子节点大(小),根节点最大 (小)
思想:每次从根节点取出最大(小),再将二叉树调整为堆,继续用堆的性质
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 1000010
using namespace std;
int n,a[N];
void up(int x)///UP操作
{
while(x>1)
{
int y=x/2;
if(a[y]>a[x])
{
swap(a[x],a[y]);
x=y;
}
else break;
}
}
void down(int x)//down操作
{
while(x*2<=n)
{
int y=x*2;
if(x*2+1>n)
{
if(a[y]<a[x])
{
swap(a[x],a[y]);
x=y;
}
else break;
}
else
{
int z=y+1;
if(a[y]<a[z])
{
if(a[y]<a[x])
{
swap(a[x],a[y]);
x=y;
}
else break;
}
else
{
if(a[z]<a[x])
{
swap(a[x],a[z]);
x=z;
}
else break;
}
}
}
}
void insert(int x)//添加
{
a[++n]=x;
up(n);
}
void del(int x)//删去
{
a[x]=a[n];
n--;
if(x!=1&&a[x]<a[x/2]) up(x);
else down(x);
}
void build()//建堆
{
for(int i=n/2;i;i--) down(i);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
build();
int m=n;
for(int i=1;i<=m;i++)
{
int x=a[1];
del(1);
cout<<x<<' ';
}
cout<<endl;
}
二、非基于比较的排序
桶排序
思想:
若待排序的记录的关键字在一个明显有限范围内(整型)时,可设计有限个有序桶,每个桶装入一个值(当然也可以装入若干个值),顺序输出各桶的值,将得到有序的序列。
算法实现:
1.把每个待排序的数放入相应的桶内。统计每个桶内数据的个数。
2.从第0号桶开始依次输出桶内的数。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int main()
{
int b[101],n,i,j,k,m=-100;//m表示这组数的最大值
memset(b,0,sizeof(b));//初始化
cin>>n;
for(i=1;i<=n;i++)
{
cin>>k;b[k]++;m=max(m,k);
}
for(i=0;i<=m;i++)
while(b[i]>0)
{
cout<<i<<" ";b[i]--;
}
cout<<endl;
}
缺点:当数据过于分散时,大量的时间和空间被浪费。

浙公网安备 33010602011771号