计数排序

来源:互联网 发布:同花顺炒股交易软件 编辑:程序博客网 时间:2024/06/13 23:37

以空间换时间的计数排序

算法基本思想

假设元素大于等于0
1.依次扫描原数组,将元素值k记录在中间数组的k位上:
这里写图片描述
2.将中间数组变换后,输出到结果数组:依次扫描中间数组,如果为1,将其插入输出数组的空白处
这里写图片描述
伪代码:

CountSort(A) max = maxOf(A) //线性 let B[max+1] be a new array of 0 let C[A.length] be a new array for i = 0 to A.length   //线性    B[A[i]] = 1   j = 0   for i = 0 to max                //  线性    if B[i] == 1             C[j++] = i  C is the output aray

Bug:如果有重复元素,上述算法出现bug:无论出现多少次该位置都计数为1,这样在恢复成有序数组时,就会少元素.
改进:中间数组记录个数(每出现一次某数字,就在对应下标的元素上加1)而不是记录是否存在(1);

但这在恢复时出现一些问题,我们是否要在内部执行循环以保证若干个值相同的元素恢复到指定位置

改进:

中间数组记录个数(每出现一次某数字,就在对应下标的元素上加1)而不是记录是否存在(1);

但这在恢复时出现一些问题,我们是否要在内部执行循环以保证若干个值相同的元素恢复到指定位置?
这里写图片描述
b0==2,说明0这个数字在结果数组中应该出现两次,同理b3==3,说明3这个数字应该在结果数组中出现3次,我们在恢复0这个数字时,是否要循环2次插入0 ?如果这样做,时间复杂度不稳定。

B数组的元素和恰好为A数组的长度,b0+b2+b3+b5恰好等于8,这是因为B元素值就是A元素出现的次数。现在我们可以将B数组做些转化,以使得在恢复时我们明确知道A的元素即B中值大于0的那些元素的下标,应该出现在结果元素的哪个位上。

这个转换将B数组的元素替换为“自己+上一个元素”和,这样B[i]就记录下了小于i的元素的个数-1:

这里写图片描述
我们从后往前看,b5=8,这说明小于5的元素有7个,5这个数字应该出现在第8位(下标7),b4=7,说明4在第7位?不是的,其实4这个数字并不会出现,因为3才该在第七位,我们只需扫描A数组,只有出现的元素才会到B’里面找位置:
这里写图片描述

CountSort(A)    max = maxOf(A)  // 线性    let B[max+1] be a new array of 0    let C[A.length] be a new array     for i = 0 to A.length //线性       B[A[i]] += 1    for i = 1 to B.length  //转化       B[i] = B[i]+B[i-1]    j = 0    for i = A.length  down to 0 //线性      C[B[A[i]]-1] = A[i]      B[A[i]] --        //自减     C is the output aray

元素变下标,下标变元素。
问题2:这样真的高效吗?空间换时间值得吗?
空间的耗费取决于A数组中最大的那个整数,假设极端情况A就一个值为10000的元素,那我们岂不是要开辟一个长度位10000的中间数组,且只有B[10000]记录为1,这是多大的浪费啊?

所以,这种算法只供观赏,只有在A的元素紧密排列的情况下,才比较可用,因为这样才不会造成太大的空间浪费。

代码

/**计数排序  *思路:开辟新的空间,空间大小为max(source)  *扫描source,将value作为辅助空间的下标,用辅助空间的改位置元素记录value的个数,如:8  6 5 2 1,helper=arr(9)  * 一次扫描,value为8,将helper[8]++,value为6,helper[6]++...  * 如此这般之后,遍历helper,如果index值为0,说明index不在source中出现  * 如果index的值为1,说明index在source中出现了1次,为2自然是出现两次  * 遍历helper就能将source修复为升序排列  *       int[] helper = new int[maxOf(source)+1];  *      for(int e : source)  *        helper[e]++;  *      k=0;  *      for(i in 1...)  *        while(helper[i]>0)  *          source[k++] = i;  *          helper[i]--;  * 时间复杂度: 扫面一次source,扫描一次helper,复杂度为N+k<br />  * 空间复杂度:辅助空间k,k=maxOf(source)<br />  * 非原址排序<br />  * 稳定性:相同元素不会出现交叉,非原址都是拷来拷去<br />  * 如果要优化一下空间,可以求minOf(source),helper的长度位max-min+1,这样能短点<br />  * 计数有缺陷,数据较为密集或范围较小是,适用。  */public class CountSort {  public static void sort(int[] source) {   int[] helper = new int[Util.maxOf( source ) + 1];   for (int e : sourse) {     helper[e]++;    }    int k = 0;    for (int i = 0; i <helper.length; i++) {      while(helper[i] > 0) {        source[k++] = i;        helper[i]--;       }     }   }   public static void sort2(int[] source) {     int[] helper = new int[Util.maxOf(source) + 1];       int[] output = new int[source.length]; //输出数组       for (int e : source) {           helper[e] ++;       }       for (int i = 1; i < helper.length ; i++) { //转换           helper[i] = helper[i] + helper[i-1];        }        for(int i = 0; i < source.length; i++) {          output[helper[source[i]]-1] = source[i];            helper[source[i]]--;      }   }}
原创粉丝点击