对科研中常见的几种排序方法做个总结,方便日后查看与回顾。(对本人认为比较简单易懂的排序方法过程比较简略,对比较复杂的排序方法过程就比较详细)
编程语言:C++(经过本人验证,可以正常运行)。
一、直接插入排序(稳定):
思想:“一趟一个“地将待排序记录插入到已经排好序的部分记录的适当位置中,使其成为一个新的有序序列,直到所有待排序记录全部插入完毕。
分析:
空间:仅需使用一个辅助单元。故空间复杂度为O(1);
最好情况:待排序序列已经有序,每趟操作只需比较1次和移动0次,此时,总的比较次数为n-1,总移动次数为0,故最好情况算法复杂度为O(n);
最坏情况:待排序序列按逆序排序,这时在第j趟操作中,为插入元素需要同前面的j个元素比较,移动元素的次数为j+1.此时有:总的比较次数=1+2+...+(n-1)=n(n-1)/2;总移动次数=2+3+4+...+n=(n+2)(n-1)/2,故最坏情况算法复杂度为O(n^2);
平均情况:在第j趟操作中,插入记录大约需要同前面j/2个元素比较,移动记录的次数为j/2+1次,此时总的比较次数约为n^2/4;总移动次数为n^2/4;故时间复杂度为O(n^2);
代码:
void insertSort(int *a,int low,int high)//插入排序
{
for(int i=low+1;i<high;i++)
{
if(a[i]<a[i-1])
{
int tmp=a[i];
a[i]=a[i-1];
int j=i-2;
for(;j>=low&&tmp<a[j];j--)
a[j+1]=a[j];
a[j+1]=tmp;
}
}
}
二、二分插入排序(稳定):
思想:与直接插入类似,在找寻插入位置时采用二分查找方式;
分析:与直接插入相同,仅仅减少了元素的比较次数,并没减少元素的移动次数,因此仍为O(n^2);
代码:
void binaryInsertSort(int *a,int low,int high)//二分插入排序
{
for(int i=low+1;i<high;i++)
{
int tmp=a[i];
int hi=i-1;
int lo=low;
while(lo<=hi)
{
int mid=(lo+hi)/2;
if(a[mid]>tmp) hi=mid-1;
else lo=mid+1;
}
for(int j=i-1;j>hi;j--)
{
a[j+1]=a[j];
}
a[hi+1]=tmp;
}
}
三、希尔排序(不稳定):
思想:”基本有序化“可以提升插入排序的效率,将记录序列分成若干子序列,每个子序列分别进行插入排序。需要注意的是,这种子序列是由间隔为某个增量的一组数据组成,即跳跃式选择数据组。(这个解释起来比较抽象,面试中也很少问,但编程贼简单)
分析:时间复杂度:开始时增量较大,分组较多,每组的记录数目较少,当排序规模n值较小时,n和n^2的差距也较小,但随着增量d逐渐缩小,分组逐渐减小,而各组的记录数目逐渐增多,但由于已经基本有序,所以新的一趟也较快。时间复杂度与增量的设置有关,约在O(n^1.25)和O(n^1.6)之间;
代码:
void shellInsert(int *a,int low,int high,int deltaK)//希尔排序
{
for(int i=low+deltaK;i<high;i++)
{
if(a[i]<a[i-deltaK])
{
int tmp=a[i];
int j=i-deltaK;
for(;j>=low&&tmp<a[j];j=j-deltaK)
a[j+deltaK]=a[j];
a[j+deltaK]=tmp;
}
}
}
void shellSort(int *a,int low,int high,int *delta,int m)//希尔排序
{
for(int k=0;k<m;k++)
{
shellInsert(a,low,high,delta[k]);
}
}
四、冒泡排序(稳定):
思想:贼简单,对n个数进行n-1次扫描,每次扫描比较相邻两个数字,大的移到后面。这样每趟排序会把最大的数字扔到最后面;
分析:
空间:需要一个辅助单元,故空间复杂度为O(1);
最好情况:数据全部排好序,这时循环n-1次,时间复杂度为O(n),但是程序中可以设置flag,某一次未发生位置交换,程序退出;
最坏情况:数据全部逆序存放,这时循环n-1次,比较次数为n-1+n-2+...+1=n(n-1)/2;移动次数为3*(n-1+n-2+...+1)=3n(n-1)/2,时间复杂度为O(n^2);
平均情况:与直接插入分析一样,时间复杂度为O(n^2);
代码:
void bubbleSort(int *a,int low,int high)//冒泡排序普通实现
{
int n=high-low;
for(int i=0;i<n-1;i++)
{
int flag=0;
for(int j=0;j<n-i-1;j++)
{
if(a[j]>a[j+1])
{
flag=1;
int tmp=a[j];
a[j]=a[j+1];
a[j+1]=tmp;
}
}
if(flag==0)
break;
}
}
这里有个小插曲,有一次腾讯面试,让用递归实现冒泡,瞬间懵逼。
代码(冒泡排序的递归实现):
void bubbleSort2(int *a,int low,int high)//冒泡排序递归实现
{
if(high==1)
return;
for(int i=0;i<high-1;i++)
{
if(a[i]>a[i+1])
{
int tmp=a[i];
a[i]=a[i+1];
a[i+1]=tmp;
}
}
bubbleSort2(a,low,--high);
}
五、快速排序(不稳定):
思想:每次选择一个元素放置到它应该在的位置,划分原来序列为前面的数都不大于它,后面的数都不小于它;
分析(消耗的时间空间主要在递归上):
空间:最好情况,每次取到的元素刚好平分整个数组,此时空间复杂度为O(log n);最坏情况,每次取到的就是数组的最大或最小的,这种情况就是冒泡排序,此时空间复杂度为O(n),所以空间复杂度为O(log n)~O(n);
最好情况:相当于一个完全二叉树结构,即每次标准元素都把当前数组分成两个大小相等的子数组,这是分解次数等于完全二叉树的深度lb n;每次快排过程无论把数组怎样划分,全部的比较次数都接近于n-1次,所以时间复杂度为O(n lb n);
最坏情况:退化成冒泡排序,时间复杂度为O(n^2);
平均情况:时间复杂度为O(n lb n);
代码:
void quickSort(int *a,int low,int high)//快速排序
{
int i=low;
int j=high-1;
int tmp=a[low];
while(i<j)
{
while(i<j&&tmp<=a[j]) j--;
if(i<j)
{
a[i]=a[j];
i++;
}
while(i<j&&tmp>=a[i]) i++;
if(i<j)
{
a[j]=a[i];
j--;
}
}
a[i]=tmp;
if(low<i) quickSort(a,low,i-1);
if(i<high) quickSort(a,i+1,high);
}
六、直接选择排序(不稳定):
思想:巨简单。在每趟过程中将待排序数组最小数放到最前面。不多解释了。
分析:
空间:需要一个辅助单元,故O(1);
最好最坏情况:无论初始状态如何,都需要进行n-1+n-1+...+1=n(n-1)/2次比较,所以时间复杂度为O(n^2);
代码:
void selectSort(int *a,int low,int high)//直接选择排序
{
int n=high-low;
for(int i=0;i<n-1;i++)
{
int min=i;
for(int j=i+1;j<n;j++)
{
if(a[min]>a[j])
min=j;
}
if(min!=i)
{
int tmp=a[min];
a[min]=a[i];
a[i]=tmp;
}
}
}
七、堆排序(不稳定):
思想:把待排序的数据元素构造成一个完全二叉树结构,则每次选择出一个最大的数据元素只需比较完全二叉树的高度次,即lb n次,则排序算法的最好最坏时间复杂度均为O(n lb n);
空间:O(1);
这个比较复杂,具体的讲解可以参考博文:http://www.cnblogs.com/skywang12345/p/3602162.html
代码:
void heapAdjust(int *a,int low,int high)//堆排序,调整堆
{
int tmp=a[low];
for(int j=2*low+1;j<high;j=j*2+1)
{
if(j<high-1&&a[j]<a[j+1]) j++;
if(tmp>=a[j]) break;
a[low]=a[j];
a[j]=tmp;
low=j;
}
//a[low]=tmp;
}
void heapSort(int *a,int high)//堆排序
{
for(int i=(high)/2-1;i>=0;i--)
heapAdjust(a,i,high);
for(int i=high-1;i>0;i--)
{
int tmp=a[0];
a[0]=a[i];
a[i]=tmp;
heapAdjust(a,0,i);
}
}
八、归并排序(稳定):
思想:将若干个已经排好序的子序列合并成一个有序的序列。
分析:
时间:归并的次数约为lb n,任何一次的归并比较次数约为n-1,所以最好最坏时间复杂度均为O(n lb n);
空间:需要一个与原数组大小一样的辅助数组,故空间复杂度为O(n).
代码:
void merge(int *a,int p,int q,int r)//一次归并算法
{
int n1=q-p+1;
int n2=r-q;
int *L=new int[n1+1];
int *R=new int[n2+1];
for(int i=0;i<n1;i++)
{
L[i]=a[p+i];
}
for(int j=0;j<n2;j++)
{
R[j]=a[q+j+1];
}
L[n1]=1000000;
R[n2]=1000000;
for(int i=0,j=0,k=p;k<=r;k++)
{
if(L[i]<=R[j])
{
a[k]=L[i];
i++;
}
else
{
a[k]=R[j];
j++;
}
}
delete []L;
delete []R;
}
void mergepass(int *a,int p,int r)//二路归并
{
if(p<r)
{
int q=(p+r)/2;
mergepass(a,p,q);
mergepass(a,q+1,r);
merge(a,p,q,r);
}
}
最后补上一张各种排序算法的对比:
来源:CSDN
作者:西电校草
链接:https://blog.csdn.net/cppjava_/article/details/72553243