比较排序
快速排序,归并排序,堆排序等这些算法都有一个有趣的性质,在排序的最终结果中,各元素的次序依赖于它们之间的比较,我们把这类算法称为比较排序,任何比较排序在最坏情况下,都要经过 Ω ( n l g n ) \Omega(nlgn) Ω(nlgn)次比较,归并排序和堆排序是渐进最优的,并且任何已知的比较排序最多就是在常数因子上优于它们
- 决策树模型:比较排序可以抽象为一颗决策树,决策树是一颗完全二叉树,它可以表示,在给定输入规模情况下,某一特定排序算法对所有元素的比较操作。对一个正确的比较排序算法来说,
n
n
n个元素有
n
!
n!
n!种可能的排列,且这些排列都应该出现在决策树的叶节点上,而且,每一个叶节点都是可以从根节点经由某条路径到达的,如下图所示,作用于3个元素的插入排序树
加了阴影的路径表示对输入序列 < a 1 = 6 , a 2 = 8 , a 3 = 5 > <a_1=6,a_2=8,a_3=5> <a1=6,a2=8,a3=5>进行排序所做的决策,叶节点上的排列 < 3 , 2 , 1 > <3,2,1> <3,2,1>表示输入的排序结果 a 3 = 5 ≤ a 1 = 6 ≤ a 2 = 8 a_3=5 \le a_1=6 \le a_2=8 a3=5≤a1=6≤a2=8,对于输入元素来说,共有 3 ! = 6 3!=6 3!=6种可能的排列,因此决策树至少包含6个叶节点 - 定理:在最坏情况下,任何比较排序算法都需要做
Ω
(
n
l
g
n
)
\Omega(nlgn)
Ω(nlgn)次比较
考虑一颗高度为 h h h,具有 l l l个可达叶节点的决策树,它对应一个对 n n n个元素所做的比较排序,因为输入数据 n ! n! n!种可能的排列都是叶节点,所以有 n ! ≤ l n! \le l n!≤l,由于在一颗为 h h h的二叉树中,叶节点的数目不多于 2 h 2^h 2h,我们得到 n ! ≤ l ≤ 2 h n! \le l \le 2^h n!≤l≤2h对该式两边取对数,有 h ≥ l g ( n ! ) = Ω ( n l g n ) h \ge lg(n!)= \Omega(nlgn) h≥lg(n!)=Ω(nlgn)
计数排序
计数排序的基本思想是:对每个输入元素
x
x
x,确定小于
x
x
x的元素个数,利用这个信息,就可以直接把
x
x
x放到它在输出数组中的位置上。
在计数排序的代码中,假设输入一个数组
A
[
1..
n
]
A[1..n]
A[1..n],我们还需要两个数组:
B
[
1..
n
]
B[1..n]
B[1..n]存放排序的输出,
C
[
0..
k
]
C[0..k]
C[0..k]提供临时存储空间
python实现如下:
# -*-coding:utf8 -*-
import sys
def counting_sort(A, B, k):
# let C[0..max_num] be a new array
C = [0]*(k+1)
#遍历A中的每个元素,如果一个输入的元素值为i,则将C[i]值加1
for i in range(len(A)):
C[A[i]]+=1
#计算C中有多少个是小于等于i
for i in range(1,k+1):
C[i] = C[i]+C[i-1]
#计数排序, B索引从0开始,所以每次A[i]放入的位置,由C[A[i]]-1
#i从A的最后一个元素开始遍历,依次向前,保证计数排序的稳定性
for i in range(len(A)-1,-1,-1):
B[C[A[i]]-1] = A[i]
C[A[i]]-=1
return B
if __name__=='__main__':
A = [2,5,0,2,3,0,3]
print(A)
k = max(A)
B = [0]*len(A)
B = counting_sort(A,B,k)
print(B)
下图展示了计数排序的运行过程,初始数组图(a)
A
[
2
,
5
,
3
,
0
,
2
,
3
,
0
,
3
]
A[2,5,3,0,2,3,0,3]
A[2,5,3,0,2,3,0,3],
C
C
C中统计
C
[
i
]
C[i]
C[i]中保存的等于
i
i
i的元素个数,(b)中,
C
[
i
]
C[i]
C[i]存储的是小于或等于i的个数,(c)~(e)中,计数排序输出数组
B
B
B和辅助数组
C
C
C的情况,(f)最终排序好的输出数组
B
B
B
计算排序的下界优于
Ω
(
n
l
g
n
)
\Omega(nlgn)
Ω(nlgn),因为它并不是一个比较排序算法。计数排序是使用输入元素的实际值来确定其在数组中的位置。
计算排序的一个重要性质就是它是稳定的:具有相同值的元素在输出数组中的相对次序与它们在输入数组中的相对次序相同。计数排序经常会被用作基数排序算法的子过程。
桶排序
桶排序假设输入数据服从均匀分布,平均情况下它的时间代价为
O
(
n
)
O(n)
O(n)。与计数排序假设输入数据都属于一个小区间内的整数,而桶排序假设输入是由一个随机过程产生,该过程将元素均匀,独立地分布在[0,1)区间上
在桶排序的代码中,我们假设输入是一个包含
n
n
n个元素的数组
A
A
A,且每个元素
A
[
i
]
A[i]
A[i]满足
0
≤
A
[
i
]
<
1
0\le A[i] <1
0≤A[i]<1,此外,算法还需要一个临时数组
B
[
0..
n
−
1
]
B[0..n-1]
B[0..n−1]来存放链表(即桶)
下图展示了10个元素的桶排序过程:
python实现如下:
# -*-coding:utf8 -*-
import sys
def bucket_sort(A):
n = len(A)
# 初始化B
B = [ [] for i in range(n) ]
for i in range(n):
#将A[i]插入B中
B[int(n*A[i])].append(A[i])
del A[:]
for x in B:
#对每个list x中的值进行排序,调用内部sorted排序函数,依次插入A中
for i in sorted(x):
A.append(i)
if __name__=='__main__':
A = [.78,.17,.39,.26,.72,.94,.21,.12,.23,.68]
print(A)
bucket_sort(A)
print(A)