1.冒泡排序
2.简单选择排序(直接排序)
3.快速排序
4.shell排序(不太熟练)
5.归并排序(不太熟练)(对代码理解还不到位)
一:冒泡排序
算法思想:
从数组中第一个数开始,依次遍历数组中的每一个数,通过相邻比较交换,每一轮循环下来找出剩余未排序数的中的最大数并”冒泡”至数列的顶端。
算法步骤:
(1)从数组中第一个数开始,依次与下一个数比较并次交换比自己小的数,直到最后一个数。如果发生交换,则继续下面的步骤,如果未发生交换,则数组有序,排序结束,此时时间复杂度为O(n);
(2)每一轮”冒泡”结束后,最大的数将出现在乱序数列的最后一位。重复步骤(1)。
稳定性:稳定排序。
时间复杂度: O(n)至O(n2),平均时间复杂度为O(n2)。
最好的情况:如果待排序数据序列为正序,则一趟冒泡就可完成排序,排序码的比较次数为n-1次,且没有移动,时间复杂度为O(n)。
最坏的情况:如果待排序数据序列为逆序,则冒泡排序需要n-1次趟起泡,每趟进行n-i次排序码的比较和移动,即比较和移动次数均达到最大值:
比较次数:Cmax=∑i=1n−1(n−i)=n(n−1)/2=O(n2)
移动次数等于比较次数,因此最坏时间复杂度为O(n2)。
示例代码:
void bubbleSort(int array[],int len){
//循环的次数为数组长度减一,剩下的一个数不需要排序
for(int i=0;i<len-1;++i){
bool noswap=true;
//循环次数为待排序数第一位数冒泡至最高位的比较次数
for(int j=0;j<len-i-1;++j){
if(array[j]>array[j+1]){
array[j]=array[j]+array[j+1];
array[j+1]=array[j]-array[j+1];
array[j]=array[j]-array[j+1];
//交换或者使用如下方式
//a=a^b;
//b=b^a;
//a=a^b;
noswap=false;
}
}
if(noswap) break;
}
}
二.简单选择排序
原理:从所有记录中选出最小的一个数据元素与第一个位置的记录交换;然后在剩下的记录当中再找最小的与第二个位置的记录交换,循环到只剩下最后一个数据元素为止。
稳定性:不稳定排序。
时间复杂度: 最坏、最好和平均复杂度均为O(n2),因此,简单选择排序也是常见排序算法中性能最差的排序算法。简单选择排序的比较次数与文件的初始状态没有关系,在第i趟排序中选出最小排序码的记录,需要做n-i次比较,因此总的比较次数是:∑i=1n−1(n−i)=n(n−1)/2=O(n2)。
示例代码:
void selectSort(int A[],int len)
{
int i,j,k;
for(i=0;i<len;i++){
k=i;
for(j=i+1;j<len;j++){
if(A[j]<A[k])
k=j;
}
if(i!=k){
A[i]=A[i]+A[k];
A[k]=A[i]-A[k];
A[i]=A[i]-A[k];
}
}
}
三: 快速排序
冒泡排序是在相邻的两个记录进行比较和交换,每次交换只能上移或下移一个位置,导致总的比较与移动次数较多。快速排序又称分区交换排序,是对冒泡排序的改进,快速排序采用的思想是分治思想。。
算法原理:
(1)从待排序的n个记录中任意选取一个记录(通常选取第一个记录)为分区标准;
(2)把所有小于该排序列的记录移动到左边,把所有大于该排序码的记录移动到右边,中间放所选记录,称之为第一趟排序;
(3)然后对前后两个子序列分别重复上述过程,直到所有记录都排好序。
稳定性:不稳定排序。
时间复杂度: O(nlog2n)至O(n2),平均时间复杂度为O(nlgn)。
最好的情况:是每趟排序结束后,每次划分使两个子文件的长度大致相等,时间复杂度为O(nlog2n)。
最坏的情况:是待排序记录已经排好序,第一趟经过n-1次比较后第一个记录保持位置不变,并得到一个n-1个元素的子记录;第二趟经过n-2次比较,将第二个记录定位在原来的位置上,并得到一个包括n-2个记录的子文件,依次类推,这样总的比较次数是:
Cmax=∑i=1n−1(n−i)=n(n−1)/2=O(n2)
示例代码:
//a:待排序数组,low:最低位的下标,high:最高位的下标
void quickSort(int a[],int low, int high)
{
if(low>=high)
{
return;
}
int left=low;
int right=high;
int key=a[left]; /*用数组的第一个记录作为分区元素*/
while(left!=right){
while(left<right&&a[right]>=key) /*从右向左扫描,找第一个码值小于key的记录,并交换到key*/
--right;
a[left]=a[right];
while(left<right&&a[left]<=key)
++left;
a[right]=a[left]; /*从左向右扫描,找第一个码值大于key的记录,并交换到右边*/
}
a[left]=key; /*分区元素放到正确位置*/
quickSort(a,low,left-1);
quickSort(a,left+1,high);
}
四:shell排序
Shell 排序又称缩小增量排序, 由D. L. Shell在1959年提出,是对直接插入排序的改进。
原理: Shell排序法是对相邻指定距离(称为增量)的元素进行比较,并不断把增量缩小至1,完成排序。
Shell排序开始时增量较大,分组较多,每组的记录数目较少,故在各组内采用直接插入排序较快,后来增量di逐渐缩小,分组数减少,各组的记录数增多,但由于已经按di−1分组排序,文件叫接近于有序状态,所以新的一趟排序过程较快。因此Shell排序在效率上比直接插入排序有较大的改进。
在直接插入排序的基础上,将直接插入排序中的1全部改变成增量d即可,因为Shell排序最后一轮的增量d就为1。
稳定性:不稳定排序。
时间复杂度:O(n1.3)到O(n2)。Shell排序算法的时间复杂度分析比较复杂,实际所需的时间取决于各次排序时增量的个数和增量的取值。研究证明,若增量的取值比较合理,Shell排序算法的时间复杂度约为O(n1.3)。
对于增量的选择,Shell 最初建议增量选择为n/2,并且对增量取半直到 1;D. Knuth教授建议di+1=⌊di−13⌋序列。
//A:输入数组,len:数组长度,d:初始增量(分组数)
void shellSort(int A[],int len, int d)
{
for(int inc=d;inc>0;inc/=2){ //循环的次数为增量缩小至1的次数
for(int i=inc;i<len;++i){ //循环的次数为第一个分组的第二个元素到数组的结束
int j=i-inc;
int temp=A[i];
while(j>=0&&A[j]>temp)
{
A[j+inc]=A[j];
j=j-inc;
}
if((j+inc)!=i)//防止自我插入
A[j+inc]=temp;//插入记录
}
}
}
注意:从代码中可以看出,增量每次变化取前一次增量的一般,当增量d等于1时,shell排序就退化成了直接插入排序了。
5、归并排序(Merge Sort)
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
5.1 算法描述
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
5.2 动图演示
5.3 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
5.4 算法分析
归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。