在公司实习了,由于公司的性质,以后的工作中用到算法&数据结构的知识可能会很少,很想把以前学的数据结构&算法的知识系统的回忆一下,但由于时间的原因,再加上我一直都很喜欢排序算法的思想。近期主要就排序这个问题做一个系统的介绍:冒泡排序,简单选择排序,直接插入排序,希尔排序,快速排序,归并排序,堆排序,基数排序。
排序的稳定性:假设 ,在排序前的序列中第i记录领先于第j个记录。如果排序后第i记录仍领先于第j个记录,则称所用的排序算法是稳定的;反之,所用的排序的算法是不稳定的。
对于内排序来说,算法的性能主要受3个方面影响:时间性能(衡量排序算法好坏的最重要的标志);辅助空间(程序在执行过程中需要的额外辅助空间);算法的复杂度(算法本省的复杂度,比如堆排序相对就比较复杂)。
交换两个数据主要有三种方法,下面算法实现:
int swap(int array[],int m,int n)//实现两个元素的交换的三种方法
{
//第一种方法需要一个额外空间存储
int temp;
temp=array[m];
array[m]=array[n];
array[n]=temp;
//第二种方法容易发生溢出现象
/*array[m]=array[m]+array[n];
array[n]=array[m]-array[n];
array[m]=array[m]-array[n];*/
//第三种方法采用位异或的信息存储方法
/*array[m]=array[m]^array[n];
array[n]=array[m]^array[n];
array[m]=array[m]^array[n];*/
return 0;
}
1、冒泡排序
1、思想描述:一种交换排序,两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
2、算法复杂度及稳定性:
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定性算法
3、算法实现:
/**************************************************************/
/********************冒泡排序算法******************************/
void BubbleSort(int array[],int length)
{
int i,j;
for(i=0;i { for(j=length-2;j>=i;j--)//从后朝前比较,将较小的放置前面 { if(array[j]>array[j+1]) swap(array,j,j+1); } } } 上述算法能否进一步改进呢,我们发现对于已经有序的序列来说,外部仍然需要进行length-1次,显然这个时候不需要再继续后面的循环。为了实现这种想法,可以增加一个标志位来标记数组是否已经有序。 改进算法: void BubbleSortImprove(int array[],int length) { int i,j; boolflag=true; for(i=0;i { flag=false;//初始化为false for(j=length-2;j>=i;j--)//从后朝前比较,将较小的放置前面 { if(array[j]>array[j+1]) { swap(array,j,j+1); flag=true; //此时发生数据交换,序列不是有序序列,标记为true } } } } 2、简单选择排序 1、算法思想描述:通过 次的关键字比较,从n-i+1个记录中选出关键字最小的记录,并和第 个记录交换。 2、算法复杂度及稳定性: 平均时间复杂度:O(n^2) 空间复杂度:O(1) 稳定性:稳定性算法 3、算法的具体实现: /**************************************************************/ /********************简单选择排序******************************/ void SelectSort(int array[],int length) { inti,j,min; for(i=0;i { min=i; //记录length-i最小元素 for(j=i+1;j { if(array[j] min=j; } if(i!=min) swap(array,i,min); } } 3、直接插入排序 1、算法思想:假设前 个记录已经有序,将第 个记录插入到已经已经排好序的有序表中,从而得到一个有 个记录的新的有序表。 2、算法复杂度及稳定性 平均时间复杂度:O(n^2) 空间复杂度:O(1) 稳定性:稳定性算法 3、算法实现: /**************************************************************/ /********************直接插入排序******************************/ void InsertSort(int array[],int length) { inti,j,temp; for(i=1;i { if(array[i] { temp=array[i]; for(j=i;j>0&&temp { array[j]=array[j-1];//记录后移 } array[j]=temp;//将array[i]插入到正确位置 } } } 4、希尔排序 1、算法思想:实质上就是直接插入排序的改进,先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。 2、算法复杂度及稳定性 平均时间复杂度:O(nlogn)-O(n^2) 空间复杂度:O(1) 稳定性:不稳定性算法 3、算法实现 /**************************************************************/ /********************希尔排序******************************/ void ShellSort(int array[],int length) { inti,j,temp,gap; for(gap=length/2;gap>0;gap=gap/2)//增量序列 { for(i=gap;i { if(array[i] { temp=array[i]; for(j=i;j>0&&temp { array[j]=array[j-gap]; } array[j]=temp; } } } } 5、堆排序 1、算法思想:将待排序的序列构造成一个大顶堆。此时整个序列的最大值就是堆顶的根节点。将其移走(就是讲其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的 个序列重新构造堆,这样就会得到n个元素中的次大元素,如此反复,最终就会得到一个有序序列了。 2、算法复杂性及稳定性 平均时间复杂度:O(nlogn) 空间复杂度:O(1) 稳定性:不稳定性算法 3、算法的实现: /**************************************************************/ /********************堆排序******************************/ void HeapAdjust(int array[],int local,int length) { inti,temp; temp=array[local]; for(i=2*local+1;i { if((i i++; if(temp>=array[i])//应插入位置 break; array[local]=array[i]; local=i; } array[local]=temp;//插入 } void HeapSort(int array[],int length)//堆排序 { int i; for(i=length/2-1;i>=0;i--)//从第一个非叶子节点开始自上而下的调整每一个子树 { HeapAdjust(array,i,length); } for(i=length-1;i>=0;i--) { swap(array,0,i); //将堆顶记录和当前未排序的子序列的最后一个记录交换 HeapAdjust(array,0,i);//将剩下的记录重新调整为大顶堆 } } 6、归并排序 1、算法思想:归并排序就是利用归并的思想,假设初始含有n个记录,把其看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到 个长度为1或2的有序子序列;然后在两两合并……,如此重复,直至得到一个长度为n的有序序列为止,成为2-路归并排序。 2、算法复杂度及稳定性: 平均时间复杂度:O(nlogn) 空间复杂度:O(n) 稳定性:稳定性算法 3、算法实现: /**************************************************************/ /********************归并排序的递归形式************************/ void Merge(int array[],int tm[],int start,int mid,intend)//将有序的array[start...mid]和array[mid+1...end]合并为有序的tm[start...end] { int i,j,k; k=start; i=start; j=mid+1; while(i<=mid&&j<=end)//将array中的记录有小到大归并入tm中 { if(array[i]<=array[j]) tm[k++]=array[i++]; else tm[k++]=array[j++]; } if(i<=mid)//将剩余的array[i...mid]复制到tm中 { while(i<=mid) tm[k++]=array[i++]; } if(j<=end)//将剩余的array[j...end]复制到tm中 { while(j<=end) tm[k++]=array[j++]; } } void MSort(int array[],int tm[],int start,int end) { inttm2[MAXSIZE+1]; if(start==end)//如果就一个记录可以直接归并 { tm[start]=array[end]; } else { intmid=(start+end)/2; MSort(array,tm2,start,mid);//首先将array[start...mid]归并为一个有序数组,并放在tm2[start..mid]中 MSort(array,tm2,mid+1,end);//然后将array[mid+1...end]归并为一个有序数组,并放在tm2[mid+1...end]中 Merge(tm2,tm,start,mid,end);//最后将tm2[start...mid]和tm2[mid+1...end]合并成一个有序记录,并放在tm[start...end]中 } } void MergeSort(int array[],int length) { MSort(array,array,0,length-1);//归并排序的递归形式 } 7、快速排序 1、算法思想:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字记录均比另一部分的关键字小,然后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。 2、算法复杂度及稳定性分析: 平均时间复杂度:O(nlogn) 空间复杂度:O(logn)-O(n) 稳定性:不稳定性算法: 3、算法实现: /**************************************************************/ /***********************快速排序******************************/ int Partition(int array[],int low, int high)//交换顺序表array中字表的记录,使枢轴到位,并返回其所在的位置 { intmid=low+(high-low)/2;//计算数组中间的元素的下标 if(array[low]>array[high])//交换左端和右端数据,保证左端数据较小 swap(array,low,high); if(array[high] swap(array,mid,high); if(array[low]>array[mid])//交换左端和中间数据,保证左端数据较小 swap(array,low,mid); intpivot=array[low];//用第一个记录作为枢轴的记录 while(low { while(low high--; array[low]=array[high];//直接替换,不需要交换,将比枢轴小的记录放到低端 while(low low++; array[high]=array[low];//直接替换,不需要交换,将比枢轴大的记录放到高端 } array[low]=pivot; returnlow;//返回枢轴的位置 } void QSort(int array[],int low,int high) { if(low { intpartition=Partition(array,low,high);//将array[low...high]一分为二,算出枢轴的位置 QSort(array,low,partition-1); QSort(array,partition+1,high); } } void QuictSort(int array[],int length) { QSort(array,0,length-1); } 改进主要有优化枢轴记录的选取,一种取中间,随机选取;用替换替代交换;对于记录较少的可以直接使用直接插入排序;尾递归的方法缩减堆栈的深度。 void QSort1(int array[],int low,int high)//尾递归 { if(low { intpartition=Partition(array,low,high);//将array[low...high]一分为二,算出枢轴的位置 QSort1(array,low,partition-1); low=partition+1;//尾递归 } } 8、基数排序 1、算法思想:n个记录的关键字进行排序,每个关键字看成一个d元组: ,分为两种方式:LSD(由数值的最右边(低位)开始)和MSD(由数值的最左边(高位)开始)。只讲述LSD,排序时先按 的值,从小到大将记录分到r(称为基数)个盒子中,一次手机;然后在按 的值继续。 2、算法复杂度及稳定性分析:
平均时间复杂度:O(d(n+rd)) 空间复杂度:O(rd) 稳定性:稳定性算法 基数排序可参考:http://blog.csdn.net/cjf_iceking/article/details/7943609 八种常见内部排序算法总结回顾 本文将常见的8种内部排序算法做一个总结,排序的重要性不言而喻,主要从算法思想,算法复杂度及稳定性(排序稳定对于某些特殊需求来说是不言而喻的),算法的具体实现(VC++6.0下C语言实现)。下面用一个表格就这八种算法做一个汇总: 改进算法排序:希尔排序,堆排序,归并排序,快速排序,基数排序。 (2)从平均时间性能而言,快速排序最佳,其所需时间最省,但快速排序在最坏的情况下的时间性能不如堆排序和归并排序。但对于后两者而言,在n较大时,归并所需时间较省,但所需辅助存储量最多。 (3)基数排序最适用于n值很大而关键字较小的序列。而对于基本有序的序列,冒泡和插入性能较佳。 (4)从方法的稳定性来比较,基数排序是稳定的,几种简单排序算法均是稳定性排序算法,而对于改进的算法,比如希尔排序,快速排序,堆排序均是不稳定排序。 综上所述:对于所有的排序算法,没有哪种算法是最优的,在实用是要根据不同的情况适当选择,甚至可以多种方法综合实用(比如快速排序可以和直接插入排序(对于基本有序的序列性能较佳))。
简单排序:冒泡排序,简单选择排序,直接插入排序(1)从算法的性质上来看,八种算法分为两类: