今天讲两种比较诡异的排序算法,之所以诡异是因为这两种排序算法与之前介绍的几种不太一样(见排序算法前三篇)。之前的排序算法都是基于元素比较来得到的,它们中间,像堆排序,快排等都有着比较理想的时间复杂度下界O(nlgn)。那么有没有更快的排序算法?有!下面的这两种排序算法的最好时间复杂度是O(n)。是不是很诱惑?一起来看看。
1)桶排序
桶排序实际上是先定义m个单元,每个单元满足一定的条件并按顺序排列,然后将所有的数据根据一种映射法则,分配到相应的单元内。然后对每个单元进行排序,最后汇总。说的很抽象,下面举个例子说明:假设现在要统计一个学院的期末考试成绩,共有1000个学生。每个学生的成绩是0-100分之间。那么如何对所有学生的分数进行排名?
so easy!我们定义一个指针数组arr[101],每个元素假设又指向一个容器,这个容器是什么数据结构我们先不管。我们暂且称为桶。并且规定arr[0]对应的桶存放所有分数为0的成绩,arr[1]对应的桶存放分数为1的成绩,依次类推,arr[100]存放分数为100的成绩。于是遍历查询这1000个学生的成绩,并依据分数投放到数据中对应的桶内。最后按下标序号依次输出每个桶内的输出数据,是不是就已经排好序了?ok,这就是桶排序的真相。如果桶足够大,它几乎是没有什么耗时的。当然这只是最简单的情况,不过不管怎么样,桶排序在保证一个桶内只有一个元素时,复杂度就是O(n)了,不过这要付出空间代价,因为你要使得桶足够大。另外,这个例子也说明了桶排序是有限制条件的,那就是学生的分数必须是在某一个明确的范围内,这里是[0,100]。这很好理解,如果分数是不确定的,那么就无法建立桶的序号与分数的关系了,那么你怎么能找到分数对应哪个桶呢?
桶排序的适用场景:元素的取值范围在一个比较小的范围内。
桶排序一般要解决两个问题:1、上面桶的结构如何实现?你可以一次定义一个比较大的数组,省得麻烦,像上面那样,每个桶设置成1000大小的数据,就算所有学生的成绩全部是0分或100分,我们也不用担心桶被放满。但是这太浪费了,所以我们可以采用链表。即每个数组元素对应一个链表指针。
2、如何将数据映射到桶的序号上去?即你要知道你的这个数据该放到哪个桶内?
下面举个简单的例子来说明桶排序的实现。
例子:对数组 {2, 3, 1, 29, 70, 16, 34, 11, 10, 33, 79, 6, 46, 100, 25, 82}排序。
初步看出,元素取值范围为[0,100]。你可以像排成绩的例子中那样,定义一个100大小的指针数组,依次投放到各个桶,再按序输出就ok了。不过为了清晰的说明桶排序的两个问题,我们要这样搞:定义一个大小为10的数组,每个元素对应一个链式结构的桶。于是数组的第一个桶用来存放[0-10]的元素,第二个桶存放[11-20]的元素,依次类推,最后一个桶存放[91-100]的元素。那么我们要用到的映射准则就很明了了,一个数n对应到下标是n/10的桶,如果n是10的整数倍,下标要减1。上代码:
1 int MapForBarrel(int n) 2 { 3 if(n%10 == 0) 4 return (n == 0)?0:(n/10 - 1); 5 else 6 return n/10; 7 } 8 /***barrel sort************************************ 9 ***array-the array to be sorted 10 ***nsize-the size of array 11 ***ncount-the number of barrel 12 ***pmapfunc-the custom map function 13 **************************************************/ 14 void BarrelSort(int array[], int nsize, int ncount, void* pmapfunc) 15 { 16 /**buile a barrel**/ 17 typedef int (*pfunc)(int);//function pointer to map 18 typedef struct _node //list node struct 19 { 20 int data; 21 struct _node *pnext; 22 }node,*pnode; 23 pnode *barrel,ptemp; 24 int i,j,ntemp; 25 int index; 26 pfunc mapfunction; 27 pnode plisthead, plistlast, plisttemp; 28 29 if(NULL == pmapfunc) 30 return; 31 mapfunction = (pfunc)pmapfunc;//assign the map function pointer 32 barrel = (pnode *)malloc(ncount*sizeof(pnode));//malloc for the barrel 33 if(NULL == barrel) 34 return; 35 for(i=0; i<ncount; i++) 36 { 37 barrel[i] = 0;//initialise the barrel 38 } 39 40 /**begin to deal with all element in array***/ 41 for(i=0; i<nsize; i++) 42 { 43 ntemp = array[i]; 44 index = mapfunction(ntemp); 45 plisttemp = (pnode)malloc(sizeof(node));//malloc a temporary node 46 plisttemp->data = ntemp; 47 plisttemp->pnext = NULL; 48 if(NULL == barrel[index])//the list is empty 49 barrel[index] = plisttemp; 50 else 51 { 52 plistlast = plisthead = barrel[index]; 53 while(plisthead && plisthead->data < ntemp) 54 { 55 plistlast = plisthead; 56 plisthead = plisthead->pnext; 57 } 58 if(plisthead == barrel[index])//insert this node at the list begin 59 { 60 barrel[index] = plisttemp; 61 plisttemp->pnext = plisthead; 62 } 63 else//inset this node at the middle or end postion of the list 64 { 65 plisttemp->pnext = plistlast->pnext; 66 plistlast->pnext = plisttemp; 67 } 68 } 69 } 70 /***the barrel is sorted, than copy back to array***/ 71 for(i=0,j=0; i<ncount; i++) 72 { 73 plisttemp = barrel[i]; 74 while(plisttemp) 75 { 76 plistlast = plisttemp; 77 array[j++] = plisttemp->data; 78 plisttemp=plisttemp->pnext; 79 free(plistlast);//free the node; 80 } 81 } 82 /***release the barrel's memory***/ 83 free(barrel); 84 }
所以,最后桶排序结束以后的数据存放形式是这样的:
2)bitmap排序
这个排序算法就更加有趣了,简直就是非主流的思想。与桶排序类似,也是讲元素映射到相应的单元,但是它并不去比较元素,而是巧妙的借助单元序号达到自动排序的目的。还是很抽象,下面举个例子:对数组a{1,5,3,2,4}排序,我们先定义一个大小大于等于6的数组b(大了也没用),并初始化每个元素为0,为什么是6,等下说。然后遍历要排序的数组元素a,并将每个元素的值作为数组b的下标索引,并将该索引位置的b的值置为1,完了以后,遍历b数组,如果当前位置元素值为1,则输出其位置的值(注意不是位置元素的值),这样完了以后,输出的结果就是已经排好序的a数组了。不信你试试看。
a=1; b={0,1,0,0,0,0};
a=5; b={0,1,0,0,0,1};
a=3; b={0,1,0,1,0,1};
a=2; b={0,1,1,1,0,1};
a=4; b={0,1,1,1,1,1};
然后遍历b,如果某一位为1,输出其下标,结果为:
1,2,3,4,5
是不是很神奇?之所以能排序,是因为我们实际上利用了数据和下标之间的一一对应关系,实现了一个巧妙的转换。所以要排序的数组元素必须各不相同,且新数组(我们成为bitmap数组)的大小不能小于排序元素的最大值。这就是上面为什么要至少定义一个不小于6个元素的数组b的原因。
弄清楚了bitmap排序的思想以后,接下来就是如何实现的问题了。其实bitmap排序实现的关键步骤就是如何将元素值对应的bitmap数组下标位置的值置1,有点绕口,慢慢理一下就好。非常简单,看代码:
1 void BitMapSort(int array[], int narrsize, int bitarray[], int nbitarrsize) 2 { 3 int i,j; 4 int index; 5 for(i=0; i<nbitarrsize; i++) 6 bitarray[i] = 0;//初始化为0 7 for(i=0; i<narrsize; i++) 8 { 9 index = array[i];//原数组的值,也是对应bitarray的下标 10 bitarray[index] = 1;//bitarray[index]置1; 11 } 12 for(i=0,j=0; i<nbitarrsize; i++) 13 { 14 //测试bitmap数组的各个位置的值是否为1 15 if(bitarray[i] == 1) 16 array[j++] = i; 17 } 18 }
从代码上可以看出,bitmap排序算法不需要比较,时间复杂度为O(n)。