说明:该系列博客整理自《算法导论(原书第二版)》,但更偏重于实用,所以晦涩偏理论的内容未整理,请见谅。另外本人能力有限,如有问题,恳请指正!
1、计数排序
计数排序 假设 n 个输入元素中的每一个都是介于0到 k 之间的整数,此处 k 为某个整数。当 k = O ( n )时,计数排序的时间复杂度为 Θ ( n )。
计数排序的基本思想就是对每一个输入元素 x ,确定出小于 x 的元素个数。有了这一信息,就可以把 x 直接放到它在最终输出数组中的位置上。
在计数排序算法的代码中,我们假定输入为数组A[1...n],length[A]=n。另外还需要两个数组:存放排序结果的B[1...n],以及提供临时存储区的C[0...k]。
时间复杂度为时间复杂度为O(n+k),因此待排序的关键字范围0~k不宜过大。
COUNTING-SORT(A, B, k)
1 let C[0 .. k] be a new array
2 for i = 0 to k
3 C[i] = 0
4 for j = 1 to A.length
5 C[A[j]] = C[A[j]] + 1//在C[A[j]] 位置记录A[j]这个数出现的次数
6 // C[i] now contains the number of elements equal to i.
7 for i = 1 to k
8 C[i] = C[i] + C[i - 1]
9 // C[i] now contains the number of elements less than or equal to i.
10 for j = A.length downto 1
11 B[C[A[j]]] = A[j]
12 C[A[j]] = C[A[j]] - 1
在1-2行中的for循环初始化操作之后;在3-4行要检查每个输入元素:如果输入元素值为i,则增加C[i]的值,于是经过3-4行后,C[i]中存放了等于i的元素的个数;在6-7行中,通过在数组C中记录计数和,可以确定对每一个i=0,1...,k,有多少输入元素是小于等于i的;在9-11行,将每个元素A[j]放到输出数组B中与其相对应的最终位置上。==>计数排序的使用前提是知道输入数组中的最大输入值k,通过大小为k的数组来统计输入数组中元素的个数及位置,最终通过对计数的统计得到最终的排序数组。
计数排序的一个重要性质是它是稳定的:具有相同值得元素在输出数组中的相对次序与它们在输入数组中的次序相同。而且,计数排序经常作为基数排序算法的一个子程序。
2、基数排序
基数排序 要求待排序的元素拥有相同的位数,且从元素的低位到高位依次排序。基数排序算法的代码是很直观的,在下面伪码过程中,假设数组长度为n,每个元素都有d位数字,其中第1位为最低位,第d位是最高位。
时间复杂度:设在基数排序中,r为基数,d为位数。则基数排序的时间复杂度为O(d(n+r))。
RADIX-SORT(A, d)
1 for i = 1 to d
2 use a stable sort to sort array A on digit i
基数排序的简单Java实现
public static void radixSort(int[] array) {
int len= String.valueOf(array[0]).toString().length();
for(int i= 0; i < len; i++)
radixQuickSort(array, i);
}
/** * 计数排序的变形 */
public static void radixQuickSort(int[] array, int power) {
int[] result = new int[array.length];
int[] temp=new int[10];
for(int i= 0; i < 10; i++)
temp[i] = 0;
for(int j= 0; j < array.length; j++)
temp[getDigit(array[j], power)]++;
for(int i= 1; i < 10; i++)
temp[i] = temp[i] + temp[i - 1];
for(int j= array.length - 1; j >= 0; j--) {
result[temp[getDigit(array[j], power)] - 1] = array[j];
temp[getDigit(array[j], power)]--;
}
for(int i= 0; i < result.length; i++)
array[i] = result[i];}
/** * 获得数字第n位的值 */
public static int getDigit(int value, int power) {
return (int) (value / Math.pow(10, power)) % 10;
}
3、桶排序
当 桶排序 (bucket sort)的输入符合均匀分布时,它可以以线性时间运行。与计数排序类似,桶排序也对输入做了某种假设,因而运行的很快。具体来说,计数排序假设输入是由一个小范围内的整数构成,而桶排序假设输入由一个随机过程产生,该过程将元素均匀地分布在区间[ 0, 1 )上。
桶排序的思想就是把区间[ 0, 1 )划分成 m 个相同大小的子区间,或称 桶 。然后,将 n 个输入数分布到各个桶中去。因为输入数均匀分布在[ 0, 1 )上,所以,一般不会有很多数落在一个桶中的情况。为得到结果,先对各个桶中的元素进行排序,然后按次序把桶中的元素列出来即可。
在桶排序算法的代码中,假设输入的是一个含有n个元素的数组A,且每个元素满足0<=A[i]<1。另外还需要一个辅助数组B[0...m-1]来存放(指向)链表(桶),并假设可以用某种机制来维护这些表。
缺点:桶排序前提是知道排序的最大的数是几,然后就有多少个桶,然后将待排序的数依次放入桶中,但是这也是桶排序的缺点,即空间复杂度由最大的数决定。
时间复杂度为O(m+n),m为桶的个数,n为数组元素个数。
BUCKET-SORT(A)
1 let B[0 .. m - 1] be a new array//创建B数组,每一个数组元素代表一个桶,上面挂一个链表
2 n = A.length
3 for i = 0 to m - 1
4 make B[i] an empty list//B数组每一个数组元素上面挂一个链表
5 for i = 1 to n
6 insert A[i] into list B[FLOOR(mA[i])]//FLOOR(mA[i])公式成立的前提是0<=A[i]<1,如果0<=A[i]
7 for i = 0 to m - 1//将所有的链表排序
8 sort list B[i] with insertion sort
9 concatenate the lists B[0], B[1], ..., B[n - 1] together in order
如果输入数组均匀分布,则桶排序以线性期望时间运行。即使输入不符合均匀分布,桶排序也仍然可以以线性时间运行,只要输入满足这样一个性质,即各个桶尺寸的平方和与总的元素数呈线性关系。
4、参考
1)、算法导论读书笔记(8)
2)、排序算法之计数排序:该博客对计数排序做了改进,使得相对来说实现了原地排序。
3)、计数排序c语言实现:与博客中伪码对应的c语言实现,没有任何改进,无亮点。
4)、基数排序:与博客中伪码对应的c语言实现,没有任何改进,无亮点。
5)、桶排序:这个算法对桶排序时间复杂的的计算做了说明,我自己懒得写,记录。
6)、桶排序算法:讲解的通俗易懂。