继上篇博文,今天我将先介绍一下什么是计数排序,将计数排序描述清楚后,再进行后续的桶排序方法解决这个问题。
通常情况下,一提到排序,大家第一反应就是比较,其实,今天我要说的这个计数排序,不是基于比较的排序算法。
计数排序的主要思路(精髓):
针对给定的一组整数数据,在其中任意选取一个数X,统计出这组数中不比X大的所有数据的个数n,那么,此时,X的大小次序就应该在这组数的n+1的位置了。
针对这个思想,有几点要注意:
1. 给定的数组A的数据,必须都是整数
2. 给定的数据比较稀疏的情况下,效率(空间)比较差
计数排序的处理逻辑,分为下面几步:
1. 找出输入数组A中的最小值min,以及最大值max,求出数组A的元素最大距离k = max - min + 1;
2. 初始化计数数组C,其数组长度为K;
3. 统计数组A中每个元素A[i]与最小值min的差值作为C数组的下标对应的元素的个数;
4. 计算出C数组中每个元素C[j]及其之前所有元素的和作为当前元素C[j]的新值;
5. 初始化输出数组B,其长度和输入数组A的长度一样;
6. 对数组A进行倒序的方式,将数组A的元素基于其元素的个数排位,进行对输出数组赋值,其中,考虑输入元素中有重复元素的情况;
理论比较的晦涩吧,那么先上代码吧,下面是JAVA代码实现的计数排序:
1 /** 2 * @author "shihuc" 3 * @date 2017年1月16日 4 */ 5 package jishuSort; 6 7 import java.io.File; 8 import java.io.FileNotFoundException; 9 import java.util.Arrays; 10 import java.util.Scanner; 11 12 /** 13 * @author chengsh05 14 * 15 */ 16 class InnerMaxMin { 17 int max; 18 int min; 19 /** 20 * @return the max 21 */ 22 public int getMax() { 23 return max; 24 } 25 /** 26 * @param max the max to set 27 */ 28 public void setMax(int max) { 29 this.max = max; 30 } 31 /** 32 * @return the min 33 */ 34 public int getMin() { 35 return min; 36 } 37 /** 38 * @param min the min to set 39 */ 40 public void setMin(int min) { 41 this.min = min; 42 } 43 } 44 public class CountSortDemo { 45 46 /** 47 * @param args 48 */ 49 public static void main(String[] args) { 50 File file = new File("./src/jishuSort/sampleCount.txt"); 51 Scanner sc = null; 52 try { 53 sc = new Scanner(file); 54 int N = sc.nextInt(); 55 for(int i=0; i<N; i++){ 56 int S = sc.nextInt(); 57 int A[] = new int[S]; 58 for(int j=0; j<S; j++){ 59 A[j] = sc.nextInt(); 60 } 61 int B[] = countSortResult(A); 62 printResult(B); 63 } 64 } catch (FileNotFoundException e) { 65 e.printStackTrace(); 66 } finally { 67 if(sc != null){ 68 sc.close(); 69 } 70 } 71 } 72 73 /** 74 * 在时间复杂度为O(n)情况下,计算出最大值与最小值 75 * 76 * @param da 77 * @return 78 */ 79 public static InnerMaxMin getMaxMin(int da[]){ 80 InnerMaxMin mm = new InnerMaxMin(); 81 int max = Integer.MIN_VALUE; 82 int min = Integer.MAX_VALUE; 83 for(int i=0; i<da.length; i++){ 84 if(da[i] > max){ 85 max = da[i]; 86 } 87 if(da[i] < min){ 88 min = da[i]; 89 } 90 } 91 92 mm.setMax(max); 93 mm.setMin(min); 94 return mm; 95 } 96 97 /** 98 * 获取最大值与最小值之间的距离 k 99 * 计算逻辑步骤1 100 * 101 * @param mm 102 * @return k 103 */ 104 public static int getDistance(InnerMaxMin mm){ 105 return mm.getMax() - mm.getMin() + 1; 106 } 107 108 /** 109 * 获取转换后的计数数组C,然后将转换后的计数数组C进行计数。 此步骤的好处,是可以减少计数数组的空间开销,掐头和掐尾。 110 * 111 * 112 * @param A 原数组 113 * @param mm 最大最小值对象 114 * @return 差值数组 115 */ 116 public static int[] getCountArray(int A[], InnerMaxMin mm){ 117 int K = getDistance(mm); 118 int min = mm.getMin(); 119 120 /* 121 * 初始化计数数组C。 122 * 计算逻辑步骤2 123 */ 124 int C[] = new int[K]; 125 Arrays.fill(C, 0); 126 127 /* 128 * 计算出每个输入数组A中的元素自生的个数,主要考虑的是有重复的情况,所以先单独计算出每个元素自己一共出现了多少次。 129 * 计算逻辑步骤3 130 */ 131 for(int i=0; i<A.length; i++){ 132 int n = A[i]; 133 C[n - min]++; 134 } 135 136 /* 137 * 用到了最简单的动态规划思路,统计原始输入数组A中比X元素小的所有元素的个数之和 138 * 计算逻辑步骤4 139 */ 140 for(int j=1; j<C.length; j++){ 141 C[j] = C[j] + C[j - 1]; 142 } 143 144 return C; 145 } 146 147 /** 148 * 计数排序的总入口函数 149 * 150 * @param A 151 * @return 152 */ 153 public static int[] countSortResult(int A[]){ 154 /* 155 * 初始化计数数组的输出数组B。 156 * 计算逻辑步骤5 157 */ 158 int B[] = new int[A.length]; 159 InnerMaxMin mm = getMaxMin(A); 160 int C[] = getCountArray(A, mm); 161 int min = mm.getMin(); 162 163 /* 164 * 将输入元素基于其每个元素对应的计数个数排序操作,映射到相应的输出数组B的位置上。 165 * (对于输入数组元素中不比X元素大的元素的个数为n,那么X在排序后的输出数组中的位置为n+1) 166 * 计算逻辑步骤6 167 */ 168 for(int i = A.length - 1; i>=0; i--){ 169 B[C[A[i] - min] - 1] = A[i]; 170 C[A[i] - min]--; 171 } 172 return B; 173 } 174 175 /** 176 * 显示最终排序后的结果 177 * 178 * @param B 179 */ 180 public static void printResult(int B[]){ 181 for(int i=0; i<B.length; i++){ 182 System.out.print(B[i] + " "); 183 } 184 System.out.println(); 185 } 186 187 }
下面也附带上我的测试数据:
1 3 2 7 3 2 6 3 4 5 10 9 4 5 5 2 3 1 4 6 6 10 7 2 3 1 4 6 1 15 11 7 6
也附带上测试后的结果:
1 2 3 4 5 6 9 10 2 1 2 3 4 6 3 1 1 2 3 4 6 6 7 11 15
这个算法,是稳定的算法,性能往往比常规的比较排序要好,但是使用场景也是受限的,即参与排序的元素必须是整数,且最好不要是稀疏数组。另外,计数排序的空间消耗,相比比较排序要高。 理解计数排序的核心思想中的计算的精髓,那么这个算法就理解了。结合代码,进行理解,还是不那么难的。