线性时间排序
各种排序算法总结已经介绍了几种能在O(n*log(n))时间内培训n个数的算法。归并排序和堆排序达到了最坏情况下的上界;快速排序在平均情况下达到该上界。这些算法都有一个有趣的性质:在排序的最终结果中,各元素的次序依赖于它们之间的比较。这类算法为比较算法,还有一类算法是线性时间复杂度的排序算法,有计数排序、基数排序和桶排序,当然,这些算法使用运算而不是比较来确定排序顺序的。
1 计数排序
计数排序假设n个输入元素中的每一个都是0到n区间的一个整数,其中n是某个整数。计数排序的思想是:对每一个输入元素x,确定小于x的元素个数,利用这里信息可以直接把x放到它在输出数组中的位置了。例如,如果有5个元素小于x,则x最后应该放在第6个输出位置上(也就是下标为5)。当几个元素相同时,这一方案要修改一下,因为不能将相同的元素放到一个输出位置上。
计数排序是稳定的,即具有相同元素值的元素在数组中的先后位置在排序前后不发生改变。计数排序通常被用作基数排序的一个子过程。以下代码是计数排序的Java实现。
// 排序区间范围是[0, MAX) private final static int MAX = 100; public void countSort(int[] arr) { // 临时存放排序的输出 int[] aux = new int[arr.length]; // 提供临时存储空间 int[] count = new int[MAX + 1]; // 1.频率统计 for (int i = 0; i < arr.length; i++) { count[arr[i] + 1]++; } //2.频率转换为索引 for (int i = 1; i < count.length; i++) { count[i] += count[i - 1]; } // 3.将元素分类 for (int i = 0; i < arr.length; i++) { aux[count[arr[i]]++] = arr[i]; } // 4.回写 for (int i = 0; i < arr.length; i++) { arr[i] = aux[i]; } }
2 基数排序
基数排序(radix sorting)将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。 然后从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
基数排序的方式可以采用LSD(Least sgnificant digital)或MSD(Most sgnificant digital),低位优先的字符串排序(LSD)依赖于键索引计数法,相当于从右到左对字符串中每一个字符进行键索引排序,最后整个字符串数组也就排好序了。而MSD则相反,由键值的最左边开始。以下代码是基数排序的Java实现。
public void radixSortInternal(String[] arr, int index) { String[] aux = new String[arr.length]; int[] count = new int[26 + 1]; // 1.频率统计 for (int i = 0; i < arr.length; i++) { count[arr[i].charAt(index) - ‘a‘ + 1]++; } //2.频率转换为索引 for (int i = 1; i < count.length; i++) { count[i] += count[i - 1]; } // 3.将元素分类 for (int i = 0; i < arr.length; i++) { aux[count[arr[i].charAt(index) - ‘a‘]++] = arr[i]; } // 4.回写 for (int i = 0; i < arr.length; i++) { arr[i] = aux[i]; } } public void radixSort(String[] arr) { for (int i = arr[0].length() - 1; i >= 0; i--) { radixSortInternal(arr, i); } }
3 桶排序
桶排序假设输入数据服从平均分配,平均情况下代价为O(n)。与计数排序类似,因为对输入数据做了假设,所以其速度也很快。具体来说,计数排序假设输入数据都属于一个小区间内的整数,而桶排序则假设输入数据是由一个随机过程产生,该过程将元素均匀独立分布在[0, 1)(假设的)区间上。桶排序将[0, 1)区间划分为几个相同大小的子区间,或称为桶。因为数据量是随机独立分布在[0, 1)上的,所以一般不会出现一个桶内元素过多情况。为了得出排序结果,对每个桶中元素排序,然后遍历整每个桶就得到了排序后的元素。下图是排序过程图,以下代码是Java实现。
// 排序区间范围是[0, MAX) private final static int MAX = 100; public void bucketSort(int[] arr) { // 桶大小为10 TreeSet<Integer>[] bucket = new TreeSet[10]; for (int i = 0; i < 10; i++) { bucket[i] = new TreeSet(); } for (int i = 0; i < arr.length; i++) { bucket[arr[i] / 10].add(arr[i]); } for (int i = 0, k = 0; i < 10; i++) { for (Integer x : bucket[i]) { arr[k++] = x; } } }
参考资料:
1、《算法导论》8章-线性时间排序
2、luoxn28/algorithm_data_structure
3、各种排序算法总结
分类: 数据结构与算法分析