排序算法三
基本的声明及公用函数库在【算法导论】排序算法 零中介绍。
一、选择法排序、冒泡排序、插入法排序
二、快速排序、分治法排序、堆排序
三、计数排序、基数排序、桶排序
gtest介绍及测试用例如下:测试框架之GTest
MIT《算法导论》下载:hereorhttp://download.csdn.net/detail/ceofit/4212385
源码下载:here orhttp://download.csdn.net/detail/ceofit/4218488
第一节讲了几个最简单的排序算法,时间复杂度都是n*n
第二节讲了几个稍微复杂的排序算法,时间复杂度可优化到nlogn,有些空间复杂度比较大。
本节进一步对时间进行优化,讲解几个时间复杂度最有情况下可达n ,但空间复杂度比较大的排序算法,典型的空间换取时间,而且这几个算法的使用范围有一定的限制。包括计数排序、基数排序、桶排序。
计数排序
计数排序大家应该都知道,时间复杂度n,空间复杂度与数值范围有关。
计数排序建立一个大数组,数组大小为可能出现的数值的最大值,然后将排序序列的数值为下标的位置置1,由于下标隐含有序,所以可得到有序序列。为兼容存在相同的数值,将大数组初始化为0,每次置位操作改为++即可。
例如
2 4 5 3 1
建立一个6个元素的数组A
遍历待排序序列,置位
A[2]=1,A[4]=1,A[5]=1,A[3]=1,A[1]=1;
然后将A从0-5遍历查找值为1的元素下标即可。
结果是
1 2 3 4 5
有相同数值的序列将置位改为++。
//计数排序,空间换取时间O(n)且不通用。要求取值为整数,且范围与内存有关,排序性能与范围有关。 void CountingSort(int *pArray,int cnt);
为了通用,先获取最大值,使用动态开辟内存。如果已知数值范围,可不获取最大值。
#include "SK_Common.h" #include "SK_Sort.h" void CountingSort(int *pArray,int cnt) { if(cnt <= 1) return; int i = 0,j = 0; int index = 0; //分配数组 int maxnum = get_max(pArray,cnt); int *nums = new int[maxnum+1]; //初始化 for(i=0; i<maxnum; i++) { nums[i] = 0; } //置位 for(i=0; i<cnt; i++) { nums[pArray[i]]++; } index = 0; //排序合并 for(i=0; i<maxnum; i++) { if(nums[i] == 0) { continue; } for(j=0; j<nums[i]; j++) { pArray[index++] = i; } } delete[] nums; }
基数排序
基数排序来源于原始的卡片计算机,比如10进制数,可从低位到高位依次排序,最终得到的是有序序列。每位排序时可使用计数排序或其他方法。其中排m位时要求除m位有序外,前m-1位的相对顺序不变。这种称为稳定的排序,例如冒泡法就是一种稳定的排序。
这个举个书上的例子:
7个三位数,先按照个位排序,再排十位,再排百位,最终得到有序序列。
//基数排序,从低位到高位依次排序,要求对一个位排序是稳定的,一层排序后除此层大小外相对位置不变。 //对一位的排序希望是高效的。时间复杂度、空间复杂度与单个位排序算法有关。 void RadixSort(int *pArray,int cnt);
本例只求说明,使用冒泡排序,所以效率比较低,可修改为其他稳定的排序方法。
#include "SK_Common.h" #include "SK_Sort.h" //按4位比较 static int compareby4bit(int val1,int val2,int pos) { int siftcnt = pos*4; int dmask = 0xf; int cmp1,cmp2; cmp1 = (val1>>siftcnt) & dmask; cmp2 = (val2>>siftcnt) & dmask; return cmp1-cmp2; } //冒泡法是稳定的,只是效率有点低。 void _radix_sort(int *pArray,int cnt,int pos) { int i=0,j=0; for(i=0;i<cnt-1;i++) { for(j=cnt-1;j>i;j--) { if(compareby4bit(pArray[j-1],pArray[j],pos) > 0) { exchange(&(pArray[j]),&(pArray[j-1])); } } } } //按4位为一层 void RadixSort(int *pArray,int cnt) { int i = 0; int maxbit; //获取位数,按4位为单位排序 maxbit=get_bitcnt(get_max(pArray,cnt)); for(i=0; i<(maxbit+3)/4; i++) { _radix_sort(pArray,cnt,i); } }
桶排序
桶排序也是采用分而治之的原理,不过与分治法排序的区别在于
1、桶排序只分1层,分治法一层一层分到单个数值。
2、桶排序桶间先保证有序,然后各桶内再排序,最后合并。分治法是各部分先自己排序,部分直接不保证有序,最后合并。
例如
2 4 5 3 1
比如2个桶,可与3比较,<=3的一个桶,>3的一个桶
于是:
2 3 1
4 5
分别排序后为:
1 2 3
4 5
合并为:
1 2 3 4 5
//桶排序,先分桶,桶间有序,再桶内排序,最后合并。 //空间复杂度比较大,时间复杂度最优可为n void BucketSort(int *pArray,int cnt);
本例分了10个桶,按照大小存储。
#include "SK_Common.h" #include "SK_Sort.h" //将pArray分到10个桶中,每个桶大小为cnt,依次排在tmpArray中,cntArray存储各个桶的大小。 static void _devid(int *pArray,int cnt,int *tmpArray,int*cntarray) { int i = 0; int index; int max = get_max(pArray,cnt); int min = get_min(pArray,cnt); int tunit = (max-min+1)/10; if(tunit == 0) tunit = 1; //初始化个数 for(i=0;i<10;i++) { cntarray[i] = 0; } //分桶 for(i=0;i<cnt;i++) { index=(pArray[i]-min)/tunit; if(index > 9) index = 9; tmpArray[index*cnt+cntarray[index]] = pArray[i]; cntarray[index]++; } } //将10个桶合并为1个数组 static void _combin(int *pArray,int cnt,int *tmpArray,int *cntArray) { int index=0; int i,j; for(i=0;i<10;i++) { for(j=0;j<cntArray[i];j++) { pArray[index++] = tmpArray[i*cnt+j]; if(index >= cnt) break; } if(index >= cnt) break; } } //假设分10个桶 void BucketSort(int *pArray,int cnt) { int i = 0; int *tmpArray = new int[cnt*10]; int cntArray[10]; //分桶 _devid(pArray,cnt,tmpArray,cntArray); //桶内排序 for(i=0; i<10; i++) { BubbleSort(tmpArray+i*cnt,cntArray[i]); } //合并 _combin(pArray,cnt,tmpArray,cntArray); delete[] tmpArray; }