排序的分类
基准 | 解释 |
---|---|
按比较次数 | O ( n log ( n ) ) O(n\log(n)) O(nlog(n))、 O ( n 2 ) O(n^2) O(n2)、 O ( n ) O(n) O(n) |
按交换次数 | |
按内存占用 | 需要多少额外空间 |
按是否递归 | 比如快排是递归算法,选择、插入排序是非递归算法 |
按稳定性 | 是否保持相同 key 排序前后的相对次序 |
按适应性 | 是否受到初始顺序的影响,有些算法对预排序的数组有较好的性能 |
按是否需要内存之外的存储 | 内排序、外排序 |
排序的复杂度
排序算法 | 额外空间复杂度 | 最好时间间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O ( 1 ) O(1) O(1) | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | 是 |
选择排序 | O ( 1 ) O(1) O(1) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | 否 |
插入排序 | O ( 1 ) O(1) O(1) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | 是 |
希尔排序 | O ( 1 ) O(1) O(1) | O ( n ) O(n) O(n) | O ( n log 2 ( n ) ) O(n\log^2(n)) O(nlog2(n)) | - | 否 |
归并排序 | O ( n ) O(n) O(n) | O ( n log ( n ) ) O(n\log(n)) O(nlog(n)) | O ( n log ( n ) ) O(n\log(n)) O(nlog(n)) | O ( n log ( n ) ) O(n\log(n)) O(nlog(n)) | 是 |
堆排序 | O ( 1 ) O(1) O(1) | O ( n log ( n ) ) O(n\log(n)) O(nlog(n)) | O ( n log ( n ) ) O(n\log(n)) O(nlog(n)) | O ( n log ( n ) ) O(n\log(n)) O(nlog(n)) | 否 |
快排 | O ( log ( n ) ) O(\log(n)) O(log(n)) | O ( n log ( n ) ) O(n\log(n)) O(nlog(n)) | O ( n 2 ) O(n^2) O(n2) | O ( n log ( n ) ) O(n\log(n)) O(nlog(n)) | - |
树排序 | O ( n ) O(n) O(n) | - | O ( n 2 ) O(n^2) O(n2) | O ( n log ( n ) ) O(n\log(n)) O(nlog(n)) | - |
python 实现
def swap(A, x, y):
A[x], A[y] = A[y], A[x]
冒泡排序
def BubbleSort(A):
for i in range(len(A)):
for k in range(len(A) - 1, i, -1):
if (A[k] < A[k - 1]):
swap(A, k, k-1)
改进: 如果第一次遍历,没有发生交换,说明已经排好序了,直接结束,对最好情况的时间复杂度为 O ( n ) O(n) O(n)
def BubbleSort(A):
swapped = True
for i in range(len(A)):
if not swapped:
return
for k in range(len(A) - 1, i, -1):
if (A[k] < A[k - 1]):
swap(A, k, k-1)
swapped = True
选择排序
def SelectionSort(A):
for i in range(len(A)):
least = i
for k in range(i + 1, len(A)):
if A[k] < A[least]:
least = k
swap(A, least, i)
插入排序
def InsertionSort(A):
for i in range(1, len(A)):
tmp = A[i]
k = i
while k > 0 and tmp < A[k - 1]:
A[k] = A[k - 1]
k -= 1
A[k] = tmp
希尔排序
def ShellSort(A):
sublistcount = len(A) // 2
while sublistcount > 0:
for startposition in range(sublistcount):
gapInsertionSort(A, startposition, sublistcount)
# print("After increments of size", sublistcount, "The list is", A)
sublistcount = sublistcount // 2
def gapInsertionSort(A, start, gap):
for i in range(start + gap, len(A), gap):
currentvalue = A[i]
position = i
while position >= gap and A[position - gap] > currentvalue:
A[position] = A[position - gap]
position = position - gap
A[position] = currentvalue
归并排序
def MergeSort(A):
if len(A) > 1:
mid = len(A) // 2
lefthalf = A[:mid]
righthalf = A[mid:]
MergeSort(lefthalf)
MergeSort(righthalf)
i = j = k = 0
while i < len(lefthalf) and j < len(righthalf):
if lefthalf[i] < righthalf[j]:
A[k] = lefthalf[i]
i = i + 1
else:
A[k] = righthalf[j]
j = j + 1
k = k + 1
while i < len(lefthalf):
A[k] = lefthalf[i]
i = i + 1
k = k + 1
while j < len(righthalf):
A[k] = righthalf[j]
j = j + 1
k = k + 1
堆排序
快排
如果不计空间成本,下面是最直观的实现,完美诠释分治的思想
def partition(A):
pivot, A = A[0], A[1:]
low = [x for x in seq if x <= pivot]
high = [x for x in seq if x > pivot]
return low, pivot, high
def quickSort(A):
if len(A) <= 1:
return A
low, pivot, high = partition(A)
return quickSort(low) + [pivot] + quickSort(high)
下面是个中规中矩的快排,随机选取分割点
import random
def QuickSort(A, low, high):
if low < high:
pivot = Partition(A, low, high)
QuickSort(A, low, pivot - 1)
QuickSort(A, pivot + 1, high)
def Partition(A, low, high):
pivot = low + random.randrange(high - low + 1)
swap(A, pivot, high)
for i in range(low, high):
if A[i] <= A[high]:
swap(A, i, low)
low += 1
swap(A, low, high)
return low
树排序
使用二叉排序树,参见 二叉树排序算法
线性时间排序算法
计数排序
如果已知数组 A 的最大值小于 k,那么可以在 O ( n + k ) O(n+k) O(n+k) 时间内完成排序,空间复杂度 O ( k ) O(k) O(k)
def counting_sort(A, k):
C = [0] * k
for a in A:
C[a] += 1
i = 0
for a in range(k):
for c in range(C[a]):
A[i] = a
i += 1
return A
print(counting_sort( [1, 4, 7, 2, 1, 3, 2, 1, 4, 2, 3, 2, 1], 8 ))
桶排序
桶排序和计数排序类似,如果已知元素的范围, 如: [ m i n , m a x ] [min, max] [min,max],那么
- 先把这个区间 [ m i n , m a x ] [min, max] [min,max] 分成 K K K 个子区间(桶),此时这些区间的顺序是已知的
- 遍历数组,把元素这个放进这 K K K 个桶
- 对每个桶内元素排序
- 依次读取各个桶内的元素
桶排序的复杂度分析
总元素有 N N N 个,每个桶内的元素平均有 N / K N/K N/K 个
如果对每个桶采用复杂度为 O ( n 2 ) O(n^2) O(n2) 的排序算法,即 O ( N 2 / K 2 ) O(N^2/K^2) O(N2/K2),那么 K K K 个桶就需要 O ( N 2 / K ) O(N^2/K) O(N2/K) 的时间
最后读取所有元素,需要 O ( N ) O(N) O(N)
所以总的时间代价为: T ( N ) = O ( N 2 / K ) + O ( N ) T(N) = O(N^2/K) + O(N) T(N)=O(N2/K)+O(N)
平平无奇🤨
基数排序
def RadixSort(A):
RADIX = 10
maxLength = False
tmp, placement = -1, 1
while not maxLength:
maxLength = True
buckets = [list() for _ in range(RADIX)]
for i in A:
tmp = i // placement
buckets[tmp % RADIX].append(i)
if maxLength and tmp > 0:
maxLength = False
a = 0
for b in range(RADIX):
buck = buckets[b]
for i in buck:
A[a] = i
a += 1
# 进入下一位
placement *= RADIX