1 本节思路
之前的算法的最基本的思想是比较元素大小,所以算法复杂度最好是 Θ ( n l o g n ) \Theta(nlogn) Θ(nlogn),本节不再基于元素比较,而是基于计数的Counting sort,然后应用在Radix sort上。
2 Counting sort
2.1 算法思想
Counting sort算法的思想比较简单,就是通过统计每个数字的的个数确定每个数字的位置,譬如【8,6,2】这三个数,通过计数统计知道#8=1,#6=1,#2=1,很自然就是2排正位置1上,6排在位置1+1=2上,8排在位置2+1=3上,因为每个数字都是不重复,所以看起来很low,考虑到有充分数字的情况,就会领会到这个算法的巧妙之处了。
2.2 算法演示
2.3 算法描述
2.4 算法实现
# -*- coding: utf-8 -*-
import collections
def counting_sort(A, B, k):
# let C[0..k] be a new array
C = [0] * (k + 1)
for i in range(k + 1):
C[i] = 0
for j in range(1, len(A)):
C[A[j]] = C[A[j]] + 1
# C[i] now contains the number of elements equal to i.
for i in range(1, k + 1):
C[i] = C[i] + C[i - 1]
# C[i] now contains the number of elements less than or equal to i.
for j in range(len(A) - 1, 0, -1):
B[C[A[j]]] = A[j]
C[A[j]] = C[A[j]] - 1
if __name__ == '__main__':
A = ['x', 2, 5, 3, 0, 2, 3, 0, 3]
B = [0] * len(A)
k = max(A[1:])
print('before sort:', A[1:])
counting_sort(A, B, k)
print('after sort:', B[1:])
运行结果:
before sort: [2, 5, 3, 0, 2, 3, 0, 3]
after sort: [0, 0, 2, 2, 3, 3, 3, 5]
2.5 算法分析
容易得到Counting sort的算法复杂度是 O ( k + n ) O(k+n) O(k+n),k为集合中元素个数,n为排序序列中元素个数,因为 k < n k<n k<n,所以可以认为这个算法是线性时间复杂度,其原因就是空间换时间,所以 其用途在于小范围内的数据,如果数据范围过大(排序的元素个数不一定很多)会占用大量的内存,下面Radix sort正好可以应用到这一点。
3 Radix sort
3.1 Radix sort算法思想
Radix sort按位从后至前排序,因为要防止重复数字问题,所以顺序是从后至前。下面的演示很简单的表达了这种思想:
3.2 算法描述
这里stable sort 指的是counting sort。
3.3 算法分析
- 考虑一个极端的情况,在一个b=32位的计算机上,每一个数字都可以表示为b个bit的序列,此时 b = l o g 2 n b=log_2{n} b=log2n,应用上面的lemma8.3容易得到,在这种情况下使用RADIX-SORT的算法复杂度是 Θ ( b ( n + 2 ) ) = Θ ( l o g n ( n + 2 ) ) \Theta(b(n+2))=\Theta(logn(n+2)) Θ(b(n+2))=Θ(logn(n+2)),之前的基于“元素比较”排序算法没有什么提高。
- 考虑另一个方向的极端情况,假如计算机内存超级大随便用(不考虑缓存消耗),那么上面问题的复杂度是 Θ ( n + 32 ) \Theta(n+32) Θ(n+32),理论上完美,实际上不可信。
- 两个极端的情况之间存在着一种权衡,这种权衡关系如下面lemma8.4所示,这个lemma8.4中使用适当的颗粒度r作为一位进行counting sort。
- 有点问题。。。待续。。。