排序算法汇总
排序算法非常多,这里为方便查找,特此汇总。
1 插入排序
先给出插入排序的伪代码输入:无序的数组A 输出:排好序的数组A
for j=2 to A.length:
key = A[j]
i = j - 1
while i>0 and A[i]>key:
A[i+1] = A[i]
i = i - 1
A[i+1] = key
插入排序算法的最坏情况的时间复杂度为
O
(
n
2
)
O(n^{2})
O(n2)
算法的代码实现
def InsertSort(A):
"""插入排序算法
Args:
A ([list]): 无序数组
"""
for j in range(1, len(A)):
key = A[j]
i = j - 1
while i >= 0 and A[i] > key:
A[i+1] = A[i]
i -= 1
A[i+1] = key
return A
# A = [10, 8, 4, 5, 2, 6, 4, 9]
# print(InsertSort(A)) => [2, 4, 4, 5, 6, 8, 9, 10]
2 归并排序
归并排序利用分治法的思想,大致过程是:将无序的数组A递归的向下二分,对其分别排序,然后再合并排好序的数组,其中合并数组是其关键操作,合并数组的伪代码如下:
MERGE(A, p, q, r): # 将A[p, q]和A[q+1, r]合并
n1 = q - p + 1
n2 = r - q
# 利用两个数组来存储A[p, q]和A[q+1, r]
for i = 1 to n1:
L[i] = A[p+i-1]
for j = 1 to n2:
R[j] = A[q+j]
i = 1; j = 1
for k = p to r:
if i > n1 or L[i] > R[j]:
A[k] = R[j]
j += 1
elif j > n2 or L[i] <= R[j]:
A[k] = L[i]
i += 1
代码实现归并排序:
def Merge(A, p, q, r):
n1 = q - p + 1
n2 = r - q
L = [0] * n1
R = [0] * n2
for i in range(n1):
L[i] = A[p+i-1]
for j in range(n2):
R[j] = A[q+j]
i, j = 0, 0
for k in range(p-1, r):
if i >= n1:
A[k] = R[j]
j += 1
elif j >= n2:
A[k] = L[i]
i += 1
elif L[i] >= R[j]:
A[k] = R[j]
j += 1
elif L[i] <= R[j]:
A[k] = L[i]
i += 1
return A
def MergeSort(A, p, r):
if p < r:
q = int((p+r)/2)
MergeSort(A, p, q)
MergeSort(A, q+1, r)
Merge(A, p, q, r)
return A
# A = [10, 8, 4, 5, 2, 6, 4, 9]
# print(MergeSort(A, 1, 8)) => [2, 4, 4, 5, 6, 8, 9, 10]
Merge算法的时间复杂度为 O ( n ) O(n) O(n),其递归迭代式为 T ( n ) = T ( n 2 ) + c n T(n) = T(\frac{n}{2}) + cn T(n)=T(2n)+cn,所以其算法时间复杂度为 O ( n log n ) O(n\log n) O(nlogn)。
3.快速排序
快速排序是一种最坏情况时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)的排序算法。虽然最坏情况时间复杂度很差,但是快速排序通常是实际排序应用中最好的选择,因为他的平均性能非常好,它的期望时间复杂度是
O
(
n
log
n
)
O(n\log n)
O(nlogn),此外快速排序是原址排序。
快速排序也使用了分治思想,大致步骤如下:
- 分解:将数组 A [ p , r ] A[p, r] A[p,r]划分为两个子数组 A [ p , q − 1 ] A[p, q-1] A[p,q−1]和 A [ q + 1 , r ] A[q+1, r] A[q+1,r], 使得 A [ q + 1 , r ] A[q+1, r] A[q+1,r]中的每一个元素都小于等于 A [ q ] A[q] A[q], A [ q ] A[q] A[q]都小于等于 A [ q + 1 , r ] A[q+1, r] A[q+1,r]中的每一个元素。
- 解决:通过递归程序对
A
[
q
+
1
,
r
]
A[q+1, r]
A[q+1,r]和
A
[
p
,
q
−
1
]
A[p, q-1]
A[p,q−1]进行相同操作即可。
所以根据上面的思想很容易写出伪代码:
Q U I C K S O R T ( A , p , r ) : QUICKSORT(A, p, r): QUICKSORT(A,p,r):
i f p < r if \ \ p<r if p<r:
q = P A R T I T I O N ( A , p , r ) q=PARTITION(A, p, r) q=PARTITION(A,p,r)
Q U I C K S O R T ( A , p , q − 1 ) QUICKSORT(A, p, q-1) QUICKSORT(A,p,q−1)
Q U I C K S O R T ( A , q + 1 , r ) QUICKSORT(A, q+1, r) QUICKSORT(A,q+1,r)
其中数组的划分,即 P A R T I T I O N PARTITION PARTITION部分是关键。其伪代码为:
P A R T I T I O N ( A , p , r ) PARTITION(A, p, r) PARTITION(A,p,r)
x = A [ r ] x = A[r] x=A[r]
i = p − 1 i = p-1 i=p−1
f o r j = p t o r − 1 for \ \ j=p \ \ to \ \ r-1 for j=p to r−1:
i f A [ j ] < = x if \ \ A[j] <= x if A[j]<=x:
i = i + 1 i = i + 1 i=i+1
e x c h a n g e A [ i ] w i t h A [ j ] exchange \ A[i] \ with \ A[j] exchange A[i] with A[j]
e x c h a n g e A [ i + 1 ] w i t h A [ r ] exchange \ A[i+1] \ with \ A[r] exchange A[i+1] with A[r]
r e t u r n i + 1 return \ i+1 return i+1
根据伪代码写出程序
def partition(A, p, r):
"""返回A[r]的原址位置,从0开始算起"""
x = A[r]
i = p - 1
for j in range(p, r):
if A[j] <= x:
i += 1
A[i], A[j] = A[j], A[i]
A[i+1], A[r] = A[r], A[i+1]
return i + 1
def quicksort(A, p, r):
"""快速排序算法"""
if p < r:
q = partition(A, p, r)
quicksort(A, p, q-1)
quicksort(A, q+1, r)
if __name__ == "__main__":
B = [10, 8, 4, 5, 2, 6, 4, 9]
quicksort(B, 0, len(B)-1)
print(B) # [2, 4, 4, 5, 6, 8, 9, 10]
4. 堆排序
堆排序具有与归并排序一样的时间复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn),跟插入排序和快速排序一样的是他们都具有原址排序性质。
堆同栈、二叉树一样是一种数据结构,而且堆可以构造一个优先队列,关于优先队列的概念,这里不再赘述。
堆可以近似的看成一个近似的二叉树,不同于二叉树的是,堆分为最大堆和最小堆,最大堆指其中的每个子树他们的根节点的数值都大于等于他们的其余子节点,所以最大堆的根节点是存储的是数组中最大的数,我们就可以利用最大堆的这个性质进行排序。最小堆的概念类似。
p
s
ps
ps:最大堆的根节点的左右子节点并无大小关系。
1.维护堆的性质
在引出堆排序前,先介绍其所需要的子程序,我们需要一个算法 M A X _ H E A P I F Y MAX\_HEAPIFY MAX_HEAPIFY去维护最大堆的性质。它的输入为一个数组A和一个下标 i i i,在调用算法 M A X _ H E A P I F Y MAX\_ HEAPIFY MAX_HEAPIFY时,我们假定根节点为 L E F T ( i ) LEFT(i) LEFT(i)和 R I G H T ( i ) RIGHT(i) RIGHT(i)的二叉树都是最大堆。具体算法如下:
M A X _ H E A P I F Y ( A , i ) MAX\_HEAPIFY(A, i) MAX_HEAPIFY(A,i)
l = L E F T ( i ) l=LEFT(i) l=LEFT(i)
r = R I G H T ( i ) r=RIGHT(i) r=RIGHT(i)
i f l < = A . h e a p s i z e a n d A [ l ] > A [ i ] if \ l <= A.heapsize \ and \ A[l] > A[i] if l<=A.heapsize and A[l]>A[i]
l a r g e s t = l largest=l largest=l
e l s e else else l a r g e s t = i largest=i largest=i
i f r < = A . h e a p s i z e a n d A [ r ] > A [ l a r g e s t ] if \ r <= A.heapsize \ and \ A[r] > A[largest] if r<=A.heapsize and A[r]>A[largest]
l a r g e s t = r largest=r largest=r
i f l a r g e s t ! = i if \ largest \ != \ i if largest != i
e x c h a n g e A [ i ] w i t h A [ l a r g e s t ] exchange \ A[i] \ with A[largest] exchange A[i] withA[largest]
M A X _ H E A P I F Y ( A , l a r g e s t ) MAX\_HEAPIFY(A, largest) MAX_HEAPIFY(A,largest)
通过这种方式,我们就可以把一个根节点不满足最大堆条件但根节点为 L E F T ( i ) LEFT(i) LEFT(i)和 R I G H T ( i ) RIGHT(i) RIGHT(i)的二叉树都是最大堆的堆变成一个最大堆。
2.建堆
下面我们要考虑一个给定无序数组A,建成一个最大堆的算法,利用上面的算法,可以得到建堆算法 B U I L D _ M A X _ H E A P BUILD\_ MAX\_ HEAP BUILD_MAX_HEAP
B U I L D _ M A X _ H E A P ( A ) BUILD\_ MAX\_ HEAP(A) BUILD_MAX_HEAP(A)
A . h e a p _ s i z e = A . l e n g t h A.heap\_ size = A.length A.heap_size=A.length
f o r i = A . l e n g t h d o w n t o 1 for \ i = A.length \ downto \ 1 for i=A.length downto 1
M A X _ H E A P I F Y ( A , i ) MAX\_HEAPIFY(A, i) MAX_HEAPIFY(A,i)
最终我们就得到了由 A A A数组构成的最大堆。
3.堆排序
我们知道最大堆里的最大元素一定在根节点位置,所以我们只要一步步循坏,每次把最大堆的根节点取出,用一个A的末尾位置储存它(所以说是原址排序),最终我们会得到一个有序数组。
H E A P S O R T ( A ) HEAPSORT(A) HEAPSORT(A)
B U I L D _ M A X _ H E A P ( A ) BUILD\_ MAX\_ HEAP(A) BUILD_MAX_HEAP(A)
f o r i = A . l e n g t h d o w n t o 2 for \ i = A.length \ downto \ 2 for i=A.length downto 2
e x c h a n g e A [ 1 ] w i t h A [ i ] exchange \ A[1] \ with \ A[i] exchange A[1] with A[i]
A . h e a p s i z e = A . h e a p s i z e − 1 A.heapsize = A.heapsize -1 A.heapsize=A.heapsize−1
M A X _ H E A P I F Y ( A , 1 ) MAX\_HEAPIFY(A, 1) MAX_HEAPIFY(A,1)
这样我们就得到一个有序的数组 A A A了。
代码如下:
def max_heapify(A, i, size):
l = 2 * i # 左子节点位置
r = 2 * i + 1 # 右子节点位置
if l < size and A[l] > A[i]:
largest = l
else:
largest = i
if r < size and A[r] > A[largest]:
largest = r
if largest != i:
A[i], A[largest] = A[largest], A[i]
max_heapify(A, largest, size)
def build_max_heap(A):
A_heap_size = len(A)
for i in reversed(range(A_heap_size)):
max_heapify(A, i, A_heap_size)
def heapsort(A):
build_max_heap(A)
# print(A)
heap_size = len(A)
for i in reversed(range(1, len(A))):
A[0], A[i] = A[i], A[0]
heap_size -= 1
max_heapify(A, 0, heap_size)
if __name__ == "__main__":
B = [10, 8, 4, 5, 2, 6, 4, 9]
heapsort(B)
print(B) # [2, 4, 4, 5, 6, 8, 9, 10]
5.计数排序
上面介绍的排序都属于比较排序的范畴,指的的是需要两个数进行比较来排序,比较排序有个性质。在最坏情况下,比较排序都需要 O ( n log n ) O(n\log n) O(nlogn)时间复杂度。下面介绍几种线性时间复杂度的排序算法。
计数排序就是一种线性时间排序方法,但它对排序的数组做了约束:假设 n n n个输入元素中的每一个都是在0到 k k k区间内的一个整数,当 k = O ( n ) k=O(n) k=O(n)时,计数排序的时间复杂度为 O ( n ) O(n) O(n),计数排序的基本思想是:对每一个输入元素 x x x确定小于 x x x的元素个数。利用这一个信息就可以把 x x x放到它的输出数组上的位置上了。伪代码如下:
COUNTING-SORT(A, B, K)
let C[0...k] be a new array
for i = 0 to k:
C[i] = 0
for j =1 to A.length:
C[A[j]] = C[A[j]] + 1
for i = 1 to k:
C[i] = C[i] + C[i-1]
for j = A.length downto 1:
B[C[A[j]]] = A[j]
C[A[j]] = C[A[j]] - 1
由计数排序的伪代码部分我们可以直接分析出计数排序的时间复杂度为 O ( n ) O(n) O(n),在实际工作中当 k = O ( n ) k=O(n) k=O(n)的时候我们一般使用计数排序,计数排序还有一个重要性质是稳定的,指的是相等的数在原来数组中相对位置跟排序后的相对位置是相同的。
6.基数排序
基数排序是一种应用在卡片排序机上面的算法,我们是对每个数从低位到高位依次排序,这就要用到上面的计数排序的方法。算法伪代码为:
RADIX-SORT(A, d)
for i=1 to d
use a stable sort to sort array A on digit i(用计数排序即可)
详细的代码这个老哥写的很详细:(https://blog.csdn.net/qq_42984002/article/details/108691540?spm=1001.2014.3001.5502)
暂时先写这么多,以后再更