首先来说说排序的分类。
1. 插入排序---直接插入排序、折半插入排序、希尔排序;
2. 交换排序---冒泡排序、快速排序;
3. 选择排序---直接选择排序、堆排序;
3. 归并排序;
4. 分配排序---桶排序、基数排序;
5. 外部排序。
内部排序和外部排序的概念:在排序过程中,若整个文件都是放在内存中处理,排序时不涉及数据的内、外存交换,则称之为内部排序;反之,若排序过程中要进行数据的内、外存交换,则称之为外部排序。
需要注意的是:
(1)内排序适用于记录个数不很多的小文件;
(2)外排序则适用于记录个数太多,不能一次将其全部记录放入内存的大文件。
直接插入排序
有一个比喻非常恰当:插入排序与打扑克时整理手上的牌非常类似。摸来的第1张牌无须整理,此后每次从桌上的牌(无序区)中摸最上面的1张并插入左手的牌(有序区)中正确的位置上。为了找到这个正确的位置,须自左向右(或自右向左)将摸来的牌与左手中已有的牌逐一比较。
//插入排序 template <typename comparable> void insertSort(comparable *array, int length) { int i,j; comparable temp; for (i=1;i<length;i++) { temp=array[i]; j=i-1; while (j>=0&&array[j]>temp) { array[j+1]=array[j]; j--; } array[j+1]=temp; //排序一轮打印一次 for (int k=0;k<length;k++) { cout<<array[k]<<" "; } cout<<endl; } }
时间复杂度O(n2)空间复杂度O(1),稳定性:插入排序是稳定的,因为具有同一值的元素必然插在具有同一值的前一个元素的后面,即相对次序不变。
插入排序是一种简单的排序方法,他不仅适用于顺序存储结构(数组),而且适用于链接存储结构,不过在链接存储结构上进行直接插入排序时,不用移动元素的位置,而是修改相应的指针。
希尔(shell)排序
希尔排序(Shell Sort)又称为“缩小增量排序”。是1959年由D.L.Shell提出来的。该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
//希尔插入排序 template <typename comparable> void shellInsert(comparable *array,int length) { int k,j,i; comparable t; k=length/2; while(k>0) { cout<<k<<endl; for(j=k;j<length;j++) { t=array[j]; i=j-k; while((i>=0)&&(array[i]>t)) { array[i+k]=array[i]; i=i-k; } array[i+k]=t; //print for (int m=0;m<length;m++) { cout<<array[m]<<" "; } cout<<endl; } k=k/2; } }
时间复杂度O(n2)空间复杂度O(1),稳定性:希尔排序是不稳定的,大家可以写个例子,数组当中两个数相等,无法保证几轮排序之后两个数的相对位置不变,因此也无法保证稳定性。
希尔排序在时间性能上优于直接插入排序。原因如下:
(1) 当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
(2) 当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
(3) 在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
因此,希尔排序在效率上较直接插人排序有较大的改进。
快速排序
快速排序是一种交换排序,采用分治算法,其基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。
快速排序的基本思想如下:
设当前待排序的无序区为R[low..high],利用分治法可将快速排序的基本思想描述为:
①分解:
在R[low..high]中任选一个记录作为基准(Pivot),以此基准将当前无序区划分为左、右两个较小的子区间R[low..pivotpos-1)和R[pivotpos+1..high],并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为pivot)的关键字pivot.key,右边的子区间中所有记录的关键字均大于等于pivot.key,而基准记录pivot则位于正确的位置(pivotpos)上,它无须参加后续的排序。
注意:
划分的关键是要求出基准记录所在的位置pivotpos。划分的结果可以简单地表示为(注意pivot=R[pivotpos]):
R[low..pivotpos-1].keys≤R[pivotpos].key≤R[pivotpos+1..high].keys
其中low≤pivotpos≤high。
②求解:
通过递归调用快速排序对左、右子区间R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。
③组合:
因为当"求解"步骤中的两个递归调用结束时,其左、右两个子区间已有序。对快速排序而言,"组合"步骤无须做什么,可看作是空操作。
//快速排序的划分算法 template <typename comparable> int partition(comparable *array, int low, int high) { int temp=array[low]; int pivotkey = array[low]; while(low < high) { while (low < high && array[high] >= pivotkey) --high; array[low] = array[high]; while (low < high && array[low] <= pivotkey) ++low; array[high] = array[low]; } array[low] = temp; //print for (int m=0;m<10;m++) { cout<<array[m]<<" "; } cout<<endl; return low; } //快速排序 template <typename comparable> void quickSort(comparable *array,int low, int high) { if (low < high) { comparable piovtloc = partition(array, low, high); quickSort(array, low, piovtloc-1); quickSort(array, piovtloc+1, high); } }
最坏时间复杂度O(n2),最好时间复杂度O(logn),快速排序属于稳定排序。
选择排序
选择排序(Selection Sort)的基本思想是:每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子文件的最后,直到全部记录排序完毕。
常用的选择排序方法有直接选择排序和堆排序。
直接选择排序
第i趟排序开始时,当前有序区和无序区分别为R[1..i-1]和R[i..n](1≤i≤n-1)。该趟排序从当前无序区中选出关键字最小的记录R[k],将它与无序区的第1个记录R[i]交换,使R[1..i]和R[i+1..n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
这样,n个记录的文件的直接选择排序可经过n-1趟直接选择排序得到有序结果。
//直接选择排序 template <typename comparable> void selectInsert(comparable *array,int length) { int j,i,k; comparable temp; for (i=0;i<length;i++) { k=i; for (j=i+1;j<length;j++) { if (array[k]>array[j]) { k=j; } } if (i!=k) { temp = array[i]; array[i] = array[k]; array[k] = temp; } //print for (int m=0;m<length;m++) { cout<<array[m]<<" "; } cout<<endl; } }
直接选择排序的平均时间复杂度为O(n2),并且是不稳定的。
排序算法比较
(1)平方阶(O(n2))排序
一般称为简单排序,例如直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlgn))排序
如快速、堆和归并排序;
(3)O(n1+£)阶排序
£是介于0和1之间的常数,即0<£<1,如希尔排序;
(4)线性阶(O(n))排序
如桶、箱和基数排序。
不同条件下,排序方法的选择
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插入,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
若要求排序稳定,则可选用归并排序。