确定性:组成算法的每条指令是清晰的,无歧义的
有限性:算法中每条指令的执行次数是有限的,执行每条指令的时间也是有限的
分别用 N N N、 I I I和 A A A表示算法要解的问题的规模、算法的输入和算法本身,用 C C C表示复杂性,有 C = F ( N , I , A ) C = F(N , I , A) C=F(N,I,A)
通常 A A A隐含在复杂性函数名当中
T max ( N ) = max I ∈ D N T ( N , I ) = max I ∈ D N ∑ i = 1 k t i e i ( N , I ) = ∑ i = 1 k t i e i ( N , I ∗ ) = T ( N , I ∗ ) T_{\max}(N) = \max\limits_{I \in D_{N}}{T(N , I)} = \max\limits_{I \in D_{N}}{\displaystyle\sum\limits_{i = 1}^{k}{t_{i} e_{i}(N , I)}} = \displaystyle\sum\limits_{i = 1}^{k}{t_{i} e_{i}(N , I^{*})} = T(N , I^{*}) Tmax(N)=I∈DNmaxT(N,I)=I∈DNmaxi=1∑ktiei(N,I)=i=1∑ktiei(N,I∗)=T(N,I∗)
D N D_{N} DN是规模为 N N N的合法输入的集合, I ∗ I^{*} I∗是 D N D_{N} DN中使 T ( N , I ∗ ) T(N , I^{*}) T(N,I∗)达到 T max ( N ) T_{\max}(N) Tmax(N)的合法输入
T min ( N ) = min I ∈ D N T ( N , I ) = min I ∈ D N ∑ i = 1 k t i e i ( N , I ) = ∑ i = 1 k t i e i ( N , I ~ ) = T ( N , I ~ ) T_{\min}(N) = \min\limits_{I \in D_{N}}{T(N , I)} = \displaystyle\min\limits_{I \in D_{N}}{\displaystyle\sum\limits_{i = 1}^{k}{t_{i} e_{i}(N , I)}} = \displaystyle\sum\limits_{i = 1}^{k}{t_{i} e_{i}(N , \widetilde{I})} = T(N , \widetilde{I}) Tmin(N)=I∈DNminT(N,I)=I∈DNmini=1∑ktiei(N,I)=i=1∑ktiei(N,I )=T(N,I )
I ~ \widetilde{I} I 是 D N D_{N} DN中使 T ( N , I ~ ) T(N , \widetilde{I}) T(N,I )达到 T min ( N ) T_{\min}(N) Tmin(N)的合法输入
T a v g ( N ) = ∑ I ∈ D N P ( I ) T ( N , I ) = ∑ I ∈ D N P ( I ) ∑ i = 1 k t i e i ( N , I ) T_{\mathrm{avg}}(N) = \displaystyle\sum\limits_{I \in D_{N}}{P(I) T(N , I)} = \displaystyle\sum\limits_{I \in D_{N}}{P(I)} \displaystyle\sum\limits_{i = 1}^{k}{t_{i} e_{i}(N , I)} Tavg(N)=I∈DN∑P(I)T(N,I)=I∈DN∑P(I)i=1∑ktiei(N,I)
P ( I ) P(I) P(I)是在算法的应用中出现输入 I I I的概率
当 N N N单调增大且趋于 ∞ \infty ∞时, T ( N ) T(N) T(N)一般也将单调增大且趋于 ∞ \infty ∞
对于 T ( N ) T(N) T(N),如果存在 T ~ ( N ) \widetilde{T}(N) T (N),当 N → ∞ N \rightarrow \infty N→∞时,使得 ( T ( N ) − T ~ ( N ) ) / T ( N ) → 0 (T(N) - \widetilde{T}(N)) / T(N) \rightarrow 0 (T(N)−T (N))/T(N)→0,就说 T ~ ( N ) \widetilde{T}(N) T (N)是 T ( N ) T(N) T(N)当 N → ∞ N \rightarrow \infty N→∞时的渐进性态,或称 T ~ ( N ) \widetilde{T}(N) T (N)为算法 A A A当 N → ∞ N \rightarrow \infty N→∞的渐进复杂性
T ~ ( N ) \widetilde{T}(N) T (N)是 T ( N ) T(N) T(N)中略去低阶项留下的主项,比 T ( N ) T(N) T(N)简单
O ( f ) + O ( g ) = O ( max ( f , g ) ) O(f) + O(g) = O(\max(f , g)) O(f)+O(g)=O(max(f,g))
O ( f ) + O ( g ) = O ( f + g ) O(f) + O(g) = O(f + g) O(f)+O(g)=O(f+g)
O ( f ) O ( g ) = O ( f g ) O(f) O(g) = O(fg) O(f)O(g)=O(fg)
如果 g ( N ) = O ( f ( N ) ) g(N) = O(f(N)) g(N)=O(f(N)),则 O ( f ) + O ( g ) = O ( f ) O(f) + O(g) = O(f) O(f)+O(g)=O(f)
如果存在正的常数 C C C和自然数 N 0 N_{0} N0,使得当 N ≥ N 0 N \geq N_{0} N≥N0时有 f ( N ) ≥ C g ( N ) f(N) \geq C g(N) f(N)≥Cg(N),则称函数 f ( N ) f(N) f(N)当 N N N充分大时下有界,且 g ( N ) g(N) g(N)是它的一个下界,记为 f ( N ) = Ω ( g ( N ) ) f(N) = \Omega(g(N)) f(N)=Ω(g(N))
当 f ( N ) f(N) f(N)对自然数的不同无穷子集有不同的表达式,且有不同的阶时,不能很好地刻画 f ( N ) f(N) f(N)的下界,例如 f ( N ) = { 100 , N 为正偶数 6 N 2 , N 为正奇数 f(N) = \begin{cases} 100 , & N 为正偶数 \\ 6 N^{2} , & N 为正奇数 \end{cases} f(N)={100,6N2,N为正偶数N为正奇数,按上述定义,只能得到 f ( N ) = Ω ( 1 ) f(N) = \Omega(1) f(N)=Ω(1),这是一个平凡的下界,对算法分析没有什么价值
NP
完全性理论P
类问题P
类问题NP
类问题NP
类问题NP
完全问题——NPC
问题NPC
问题能在多项式时间内得到解决,那么NP
中的每个问题都可以在多项式时间内求解NPC
问题是布尔表达式的可满足性问题,即 C o o k Cook Cook定理:布尔表达式的可满足性问题 S A T SAT SAT是NP
完全的NP
完全问题树中任一结点表示的问题可以在多项式时间内变换为它的任一后裔节点表示的问题NPC
问题合取范式的可满足性问题 C N F − S A T CNF-SAT CNF−SAT
三元合取范式的可满足性问题 3 − S A T 3-SAT 3−SAT
团问题 C L I Q U E CLIQUE CLIQUE
顶点覆盖问题 V E R T E X − C O V E R VERTEX-COVER VERTEX−COVER
子集和问题 S U B S E T − S U M SUBSET-SUM SUBSET−SUM
哈密顿回路问题 H A M − C Y C L E HAM-CYCLE HAM−CYCLE
旅行售货员问题 T S P TSP TSP
F ( n ) = 1 5 [ ( 1 + 5 2 ) n + 1 − ( 1 − 5 2 ) n + 1 ] F(n) = \cfrac{1}{\sqrt{5}} \left[\left(\cfrac{1 + \sqrt{5}}{2}\right)^{n + 1} - \left(\cfrac{1 - \sqrt{5}}{2}\right)^{n + 1}\right] F(n)=51 (21+5)n+1−(21−5)n+1
A ( n , m ) = { 2 n = 1 , m = 0 1 n = 0 , m ≥ 0 n + 2 n ≥ 2 , m = 0 A ( A ( n − 1 , m ) , m − 1 ) n , m ≥ 1 A(n , m) = \begin{cases} 2 & n = 1 , m = 0 \\ 1 & n = 0 , m \geq 0 \\ n + 2 & n \geq 2 , m = 0 \\ A(A(n - 1 , m) , m - 1) & n , m \geq 1 \end{cases} A(n,m)=⎩ ⎨ ⎧21n+2A(A(n−1,m),m−1)n=1,m=0n=0,m≥0n≥2,m=0n,m≥1
Python
实现def ackerman(n, m):
if n == 1 and m == 0:
return 2
elif n == 0 and m >= 0:
return 1
elif n >= 2 and m == 0:
return n + 2
elif n >= 1 and m >= 1:
return ackerman(ackerman(n - 1, m), m - 1)
res = ackerman(3, 3)
print(res)
集合 X X X中的元素的全排列记为 P e r m ( X ) Perm(X) Perm(X), ( r i ) P e r m ( X ) (r_{i}) Perm(X) (ri)Perm(X)表示在全排列 P e r m ( X ) Perm(X) Perm(X)的每个排列前加上前缀 r i r_{i} ri得到的排列
R R R的全排列可递归定义为
当 n = 1 n = 1 n=1时, P e r m ( R ) = ( r ) Perm(R) = (r) Perm(R)=(r),其中 r r r是集合 R R R中唯一的元素
当 n > 1 n > 1 n>1时, P e r m ( R ) Perm(R) Perm(R)由 ( r 1 ) P e r m ( R 1 ) (r_{1}) Perm(R_{1}) (r1)Perm(R1), ( r 2 ) P e r m ( R 2 ) (r_{2}) Perm(R_{2}) (r2)Perm(R2), ⋯ \cdots ⋯, ( r n ) P e r m ( R n ) (r_{n}) Perm(R_{n}) (rn)Perm(Rn)构成
递归地产生所有前缀是num[0:k - 1]
,且后缀是num[k:m]
的全排列的所有排列
Python
实现def permute(nums):
if len(nums) <= 1:
return [nums]
result = []
for i in range(len(nums)):
m = nums[i]
remaining_nums = nums[:i] + nums[i + 1:]
sub_permutations = permute(remaining_nums)
for p in sub_permutations:
result.append([m] + p)
return result
nums = [1, 2, 3]
permutations = permute(nums)
for p in permutations:
print(p)
q ( n , m ) = { 1 , n = 1 , m = 1 q ( n , n ) , n < m 1 + q ( n , n − 1 ) , n = m q ( n , m − 1 ) + q ( n − m , m ) , n > m > 1 q(n , m) = \begin{cases} 1 , & n = 1 , m = 1 \\ q(n , n) , & n < m \\ 1 + q(n , n - 1) , & n = m \\ q(n , m - 1) + q(n - m , m) , & n > m > 1 \end{cases} q(n,m)=⎩ ⎨ ⎧1,q(n,n),1+q(n,n−1),q(n,m−1)+q(n−m,m),n=1,m=1n<mn=mn>m>1
Python
实现def integer_partition(n, m):
if n < 1 or m < 1:
return 0
if n == 1 or m == 1:
return 1
if n < m:
return integer_partition(n, n)
if n == m:
return integer_partition(n, m - 1) + 1
return integer_partition(n, m - 1) + integer_partition(n - m, m)
n = 6
res = integer_partition(n, n)
print(f'The number of partitions for {n} is: {res}')
Hanoi
塔问题Python
实现def hanoi(n, source, target, auxiliary):
if n > 0:
# 将 n - 1 个盘子从源柱移动到辅助柱
hanoi(n - 1, source, auxiliary, target)
# 将第 n 个盘子从源柱移动到目标柱
print(f'将盘子 {n} 从 {source} 移动到 {target}')
# 将 n - 1 个盘子从辅助柱移动到目标柱
hanoi(n - 1, auxiliary, target, source)
n = 3
hanoi(n, 'A', 'B', 'C')
T ( n ) = { O ( 1 ) n = 1 k T ( n / m ) + f ( n ) n > 1 T(n) = \begin{cases} O(1) & n = 1 \\ k T(n / m) + f(n) & n > 1 \end{cases} T(n)={O(1)kT(n/m)+f(n)n=1n>1
T ( n ) = n log m k + ∑ j = 0 log m n − 1 k j f ( n / m j ) T(n) = n^{\log_{m}{k}} + \displaystyle\sum\limits_{j = 0}^{\log_{m}{n - 1}}{k^{j} f(n / m^{j})} T(n)=nlogmk+j=0∑logmn−1kjf(n/mj)
a[0:n - 1]
,在这 n n n个元素中找出一特定元素 x x xPython
实现def binary_search(arr, target):
low = 0
high = len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
arr = [1, 3, 5, 7, 9]
target = 5
res = binary_search(arr, target)
if res != -1:
print(f'目标元素 {target} 在数组中的索引为 {res}')
else:
print('目标元素不在数组中')
while
循环被执行了 O ( log n ) O(\log{n}) O(logn)次,循环体内运算需要 O ( 1 ) O(1) O(1)时间,因此整个算法在最坏情况下的时间复杂性为 O ( log n ) O(\log{n}) O(logn)T ( n ) = { O ( 1 ) n = 1 4 T ( n / 2 ) + O ( n ) n > 1 T(n) = \begin{cases} O(1) & n = 1 \\ 4 T(n / 2) + O(n) & n > 1 \end{cases} T(n)={O(1)4T(n/2)+O(n)n=1n>1
T ( n ) = O ( n 2 ) T(n) = O(n^{2}) T(n)=O(n2)
T ( n ) = { O ( 1 ) n = 1 3 T ( n / 2 ) + O ( n ) n > 1 T(n) = \begin{cases} O(1) & n = 1 \\ 3 T(n / 2) + O(n) & n > 1 \end{cases} T(n)={O(1)3T(n/2)+O(n)n=1n>1
T ( n ) = O ( n log 3 ) = O ( n 1.59 ) T(n) = O(n^{\log{3}}) = O(n^{1.59}) T(n)=O(nlog3)=O(n1.59)
Python
实现def karatsuba_multiply(x, y):
# 如果乘数之一为 0, 则直接返回 0
if x == 0 or y == 0:
return 0
# 将乘数转换为字符串, 并获取它们的位数
x_str = str(x)
y_str = str(y)
n = max(len(x_str), len(y_str))
# 达到基本情况时, 使用传统的乘法
if n == 1:
return x * y
# 将乘数补齐到相同的位数
x_str = x_str.zfill(n)
y_str = y_str.zfill(n)
# 将乘数划分为两部分
m = n // 2
high1, low1 = int(x_str[:m]), int(x_str[m:])
high2, low2 = int(y_str[:m]), int(y_str[m:])
# 递归地计算三个乘法
z0 = karatsuba_multiply(low1, low2)
z1 = karatsuba_multiply((low1 + high1), (low2 + high2))
z2 = karatsuba_multiply(high1, high2)
# 计算结果
res = (z2 * 10 ** (2 * m)) + ((z1 - z2 - z0) * 10 ** m) + z0
return res
x = 123456789012345678901234567890
y = 987654321098765432109876543210
res = karatsuba_multiply(x, y)
print(f'{x} * {y} = {res}')
Strassen
矩阵乘法∣ C 11 C 12 C 21 C 22 ∣ = ∣ A 11 A 12 A 21 A 22 ∣ ∣ B 11 B 12 B 21 B 22 ∣ \begin{vmatrix} C_{11} & C_{12} \\ C_{21} & C_{22} \end{vmatrix} = \begin{vmatrix} A_{11} & A_{12} \\ A_{21} & A_{22} \end{vmatrix} \begin{vmatrix} B_{11} & B_{12} \\ B_{21} & B_{22} \end{vmatrix} C11C21C12C22 = A11A21A12A22 B11B21B12B22
C 11 = A 11 B 11 + A 12 B 21 C 12 = A 11 B 12 + A 12 B 22 C 21 = A 21 B 11 + A 22 B 21 C 22 = A 21 B 12 + A 22 B 22 C_{11} = A_{11} B_{11} + A_{12} B_{21} \\ C_{12} = A_{11} B_{12} + A_{12} B_{22} \\ C_{21} = A_{21} B_{11} + A_{22} B_{21} \\ C_{22} = A_{21} B_{12} + A_{22} B_{22} C11=A11B11+A12B21C12=A11B12+A12B22C21=A21B11+A22B21C22=A21B12+A22B22
T ( n ) = { O ( 1 ) n = 2 8 T ( n / 2 ) + O ( n 2 ) n > 2 T(n) = \begin{cases} O(1) & n = 2 \\ 8 T(n / 2) + O(n^{2}) & n > 2 \end{cases} T(n)={O(1)8T(n/2)+O(n2)n=2n>2
T ( n ) = O ( n 3 ) T(n) = O(n^{3}) T(n)=O(n3)
Strassen
算法Strassen
算法只用了 7 7 7次乘法运算,但增加了加减法的运算次数M 1 = A 11 ( B 12 − B 22 ) M 2 = ( A 11 + A 12 ) B 22 M 3 = ( A 21 + A 22 ) B 11 M 4 = A 22 ( B 21 − B 11 ) M 5 = ( A 11 + A 22 ) ( B 11 + B 22 ) M 6 = ( A 12 − A 22 ) ( B 21 + B 22 ) M 7 = ( A 11 − A 21 ) ( B 11 + B 12 ) M_{1} = A_{11} (B_{12} - B_{22}) \\ M_{2} = (A_{11} + A_{12}) B_{22} \\ M_{3} = (A_{21} + A_{22}) B_{11} \\ M_{4} = A_{22} (B_{21} - B_{11}) \\ M_{5} = (A_{11} + A_{22}) (B_{11} + B_{22}) \\ M_{6} = (A_{12} - A_{22}) (B_{21} + B_{22}) \\ M_{7} = (A_{11} - A_{21}) (B_{11} + B_{12}) M1=A11(B12−B22)M2=(A11+A12)B22M3=(A21+A22)B11M4=A22(B21−B11)M5=(A11+A22)(B11+B22)M6=(A12−A22)(B21+B22)M7=(A11−A21)(B11+B12)
C 11 = M 5 + M 4 − M 2 + M 6 C 12 = M 1 + M 2 C 21 = M 3 + M 4 C 22 = M 5 + M 1 − M 3 − M 7 C_{11} = M_{5} + M_{4} - M_{2} + M_{6} \\ C_{12} = M_{1} + M_{2} \\ C_{21} = M_{3} + M_{4} \\ C_{22} = M_{5} + M_{1} - M_{3} - M_{7} C11=M5+M4−M2+M6C12=M1+M2C21=M3+M4C22=M5+M1−M3−M7
Strassen
算法用了 7 7 7次对于 n / 2 n / 2 n/2阶矩阵乘积的递归调用和 18 18 18次 n / 2 n / 2 n/2阶矩阵的加减运算T ( n ) = { O ( 1 ) n = 2 7 T ( n / 2 ) + O ( n 2 ) n > 2 T(n) = \begin{cases} O(1) & n = 2 \\ 7 T(n / 2) + O(n^{2}) & n > 2 \end{cases} T(n)={O(1)7T(n/2)+O(n2)n=2n>2
T ( n ) = O ( n log 7 ) ≈ O ( n 2.81 ) T(n) = O(n^{\log{7}}) \approx O(n^{2.81}) T(n)=O(nlog7)≈O(n2.81)
Python
实现import numpy as np
def strassen_matrix_multiply(a, b):
n = a.shape[0]
# 如果输入矩阵的维度小于等于阈值, 使用传统的矩阵乘法
if n <= 128:
return np.dot(a, b)
# 将输入矩阵划分为四个子矩阵
mid = n // 2
a11 = a[:mid, :mid]
a12 = a[:mid, mid:]
a21 = a[mid:, :mid]
a22 = a[mid:, mid:]
b11 = b[:mid, :mid]
b12 = b[:mid, mid:]
b21 = b[mid:, :mid]
b22 = b[mid:, mid:]
# 递归地计算七个矩阵乘法
m1 = strassen_matrix_multiply(a11, b12 - b22)
m2 = strassen_matrix_multiply(a11 + a12, b22)
m3 = strassen_matrix_multiply(a21 + a22, b11)
m4 = strassen_matrix_multiply(a22, b21 - b11)
m5 = strassen_matrix_multiply(a11 + a22, b11 + b22)
m6 = strassen_matrix_multiply(a12 - a22, b21 + b22)
m7 = strassen_matrix_multiply(a11 - a21, b11 + b12)
# 计算结果矩阵的四个子矩阵
c11 = m5 + m4 - m2 + m6
c12 = m1 + m2
c21 = m3 + m4
c22 = m5 + m1 - m3 - m7
# 组合四个子矩阵形成结果矩阵
c = np.zeros((n, n))
c[:mid, :mid] = c11
c[:mid, mid:] = c12
c[mid:, :mid] = c21
c[mid:, mid:] = c22
return c
a = np.random.randint(0, 10, (4, 4))
b = np.random.randint(0, 10, (4, 4))
res = strassen_matrix_multiply(a, b)
print('矩阵a:')
print(a)
print('\n矩阵b:')
print(b)
print('\n乘积矩阵:')
print(res)
T ( k ) = { O ( 1 ) k = 0 4 T ( k − 1 ) + O ( 1 ) k > 0 T(k) = \begin{cases} O(1) & k = 0 \\ 4 T(k - 1) + O(1) & k > 0 \end{cases} T(k)={O(1)4T(k−1)+O(1)k=0k>0
T ( k ) = O ( 4 k ) T(k) = O(4^{k}) T(k)=O(4k)
Python
实现def chessboard_cover(board, tr, tc, dr, dc, size):
"""
:param board: 棋盘
:param tr: 棋盘左上角行号
:param tc: 棋盘左上角列号
:param dr: 特殊方格行号
:param dc: 特殊方格列号
:param size: 棋盘大小
"""
global tile_count
# 基本情况: 棋盘大小为 1, 直接放置骨牌
if size == 1:
return
t = tile_count
tile_count += 1
# 将棋盘分成 4 个子棋盘
s = size // 2
# 左上子棋盘
if dr < tr + s and dc < tc + s:
chessboard_cover(board, tr, tc, dr, dc, s)
else:
board[tr + s - 1][tc + s - 1] = t
chessboard_cover(board, tr, tc, tr + s - 1, tc + s - 1, s)
# 右上子棋盘
if dr < tr + s and dc >= tc + s:
chessboard_cover(board, tr, tc + s, dr, dc, s)
else:
board[tr + s - 1][tc + s] = t
chessboard_cover(board, tr, tc + s, tr + s - 1, tc + s, s)
# 左下子棋盘
if dr >= tr + s and dc < tc + s:
chessboard_cover(board, tr + s, tc, dr, dc, s)
else:
board[tr + s][tc + s - 1] = t
chessboard_cover(board, tr + s, tc, tr + s, tc + s - 1, s)
# 右下子棋盘
if dr >= tr + s and dc >= tc + s:
chessboard_cover(board, tr + s, tc + s, dr, dc, s)
else:
board[tr + s][tc + s] = t
chessboard_cover(board, tr + s, tc + s, tr + s, tc + s, s)
size = 8
board = [[0] * size for _ in range(size)]
special_row = 3
special_col = 4
board[special_row][special_col] = -1
tile_count = 0
chessboard_cover(board, 0, 0, special_row, special_col, size)
for row in board:
print(row)
Python
实现def merge_sort(arr):
# 基本情况: 当数组长度为 1 或 0 时, 直接返回
if len(arr) <= 1:
return arr
# 将数组分成两半
mid = len(arr) // 2
left = arr[:mid]
right = arr[mid:]
# 递归地对左右两半进行排序
left_sorted = merge_sort(left)
right_sorted = merge_sort(right)
# 合并已排序的左右两半
merged = merge(left_sorted, right_sorted)
return merged
def merge(left_sorted, right_sorted):
merged = []
i = j = 0
# 比较左右两个数组的元素, 按顺序合并到结果数组中
while i < len(left_sorted) and j < len(right_sorted):
if left_sorted[i] <= right_sorted[j]:
merged.append(left_sorted[i])
i += 1
else:
merged.append(right_sorted[j])
j += 1
# 将剩余的元素添加到结果数组中
while i < len(left_sorted):
merged.append(left_sorted[i])
i += 1
while j < len(right_sorted):
merged.append(right_sorted[j])
j += 1
return merged
arr = [5, 3, 8, 4, 2, 1, 6, 7]
sorted_arr = merge_sort(arr)
print(f'sorted_arr:', sorted_arr)
T ( n ) = { O ( 1 ) n ≤ 1 2 T ( n / 2 ) + O ( n ) n > 1 T(n) = \begin{cases} O(1) & n \leq 1 \\ 2 T(n / 2) + O(n) & n > 1 \end{cases} T(n)={O(1)2T(n/2)+O(n)n≤1n>1
T ( n ) = O ( n log n ) T(n) = O(n \log{n}) T(n)=O(nlogn)
Python
实现def merge_sort(arr):
# 将列表中的每个元素转换为单个元素的子列表
sublists = [[val] for val in arr]
# 依次合并相邻的子列表, 直到只剩下一个排序好的列表
while len(sublists) > 1:
merged_sublists = []
# 两两合并相邻的子列表
for i in range(0, len(sublists), 2):
sublist_1 = sublists[i]
sublist_2 = sublists[i + 1] if i + 1 < len(sublists) else []
merged = merge(sublist_1, sublist_2)
merged_sublists.append(merged)
sublists = merged_sublists
# 返回最终的排序结果
return sublists[0] if sublists else []
def merge(left_sorted, right_sorted):
merged = []
i = j = 0
# 比较左右两个列表的元素, 按顺序合并到结果列表中
while i < len(left_sorted) and j < len(right_sorted):
if left_sorted[i] <= right_sorted[j]:
merged.append(left_sorted[i])
i += 1
else:
merged.append(right_sorted[j])
j += 1
# 将剩余的元素添加到结果列表中
while i < len(left_sorted):
merged.append(left_sorted[i])
i += 1
while j < len(right_sorted):
merged.append(right_sorted[j])
j += 1
return merged
arr = [5, 3, 8, 4, 2, 1, 6, 7]
sorted_arr = merge_sort(arr)
print(f'sorted_arr:', sorted_arr)
Python
实现def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[0]
less = [x for x in arr[1:] if x <= pivot]
greater = [x for x in arr[1:] if x > pivot]
return quick_sort(less) + [pivot] + quick_sort(greater)
arr = [3, 1, 5, 2, 4]
sorted_arr = quick_sort(arr)
print(f'sorted_arr', sorted_arr)
T ( n ) = { O ( 1 ) n ≤ 1 T ( n − 1 ) + O ( n ) n > 1 T(n) = \begin{cases} O(1) & n \leq 1 \\ T(n - 1) + O(n) & n > 1 \end{cases} T(n)={O(1)T(n−1)+O(n)n≤1n>1
T ( n ) = n 2 T(n) = n^{2} T(n)=n2
T ( n ) = { O ( 1 ) n ≤ 1 2 T ( n / 2 ) + O ( n ) n > 1 T(n) = \begin{cases} O(1) & n \leq 1 \\ 2 T(n / 2) + O(n) & n > 1 \end{cases} T(n)={O(1)2T(n/2)+O(n)n≤1n>1
T ( n ) = O ( n log n ) T(n) = O(n \log{n}) T(n)=O(nlogn)
Python
实现import random
def partition(nums, low, high):
pivot_index = random.randint(low, high)
pivot = nums[pivot_index]
# 将pivot元素移动到列表的最右边
nums[pivot_index], nums[high] = nums[high], nums[pivot_index]
# 通过交换操作, 将小于 pivot 的元素移动到左边, 大于 pivot 的元素移动到右边
i = low
for j in range(low, high):
if nums[j] < pivot:
nums[i], nums[j] = nums[j], nums[i]
i += 1
# 将 pivot 元素放置到正确的位置
nums[i], nums[high] = nums[high], nums[i]
return i
def quick_select(nums, low, high, k):
if low == high:
return nums[low]
# 划分数组, 并获取 pivot 元素的索引
pivot_index = partition(nums, low, high)
j = pivot_index - low + 1
# 如果 pivot 元素的索引等于 k, 则返回该元素
if j == k:
return nums[pivot_index]
# 如果 pivot 元素的索引大于 k, 则在左侧继续查找
elif j > k:
return quick_select(nums, low, pivot_index - 1, k)
# 如果 pivot 元素的索引小于 k, 则在右侧继续查找
else:
return quick_select(nums, pivot_index + 1, high, k - j)
def find_kth_smallest(nums, k):
if k < 1 or k > len(nums):
raise ValueError('Invalid value of k')
return quick_select(nums, 0, len(nums) - 1, k)
nums = [3, 1, 5, 2, 4]
k = 2
res = find_kth_smallest(nums, k)
print(f'第 {k} 小的元素为', res)
如果能在线性时间内找到一个划分基准,使得按这个基准划分出的两个子数组的长度都至少为原数组长度的 ε \varepsilon ε倍( 0 < ε < 1 0< \varepsilon < 1 0<ε<1是某个常数),那么在最坏情况下用 O ( n ) O(n) O(n)时间就可以完成选择任务
将 n n n个输入元素划分成 ⌈ n / 5 ⌉ \left\lceil n / 5 \right\rceil ⌈n/5⌉个组,每组 5 5 5个元素(除可能有一个组不是 5 5 5个元素外),用任意一种排序算法,将每组中的元素排好序,并取出每组的中位数,共 ⌈ n / 5 ⌉ \left\lceil n / 5 \right\rceil ⌈n/5⌉个
递归调用找出这 ⌈ n / 5 ⌉ \left\lceil n / 5 \right\rceil ⌈n/5⌉个元素的中位数,如果 ⌈ n / 5 ⌉ \left\lceil n / 5 \right\rceil ⌈n/5⌉是偶数,就找它的两个中位数中较大的一个,然后以这个元素作为划分基准
设所有元素互不相同,找出的基准 x x x至少比 3 ⌊ ( n − 5 ) / 10 ⌋ 3 \left\lfloor (n - 5) / 10 \right\rfloor 3⌊(n−5)/10⌋个元素大,至少比 3 ⌊ ( n − 5 ) / 10 ⌋ 3 \left\lfloor (n - 5) / 10 \right\rfloor 3⌊(n−5)/10⌋个元素小,当 n ≥ 75 n \geq 75 n≥75时, 3 ⌊ ( n − 5 ) / 10 ⌋ ≥ n / 4 3 \left\lfloor (n - 5) / 10 \right\rfloor \geq n / 4 3⌊(n−5)/10⌋≥n/4,所以按此基准划分所得的两个子数组的长度都至少缩短 1 / 4 1 / 4 1/4
T ( n ) ≤ { C 1 , n < 75 C 2 n + T ( n / 5 ) + T ( 3 n / 4 ) , n ≥ 75 T(n) \leq \begin{cases} C_{1} , & n < 75 \\ C_{2} n + T(n / 5) + T(3n / 4) , & n \geq 75 \end{cases} T(n)≤{C1,C2n+T(n/5)+T(3n/4),n<75n≥75
T ( n ) = O ( n ) T(n) = O(n) T(n)=O(n)
Python
实现import statistics
def find_median_of_medians(arr):
# 将数组划分为大小为 5 的子数组
subarrays = [arr[i:i + 5] for i in range(0, len(arr), 5)]
# 计算每个子数组的中位数
medians = [statistics.median(subarray) for subarray in subarrays]
# 如果元素数量小于等于 5, 直接返回中位数
if len(medians) <= 5:
return statistics.median(medians)
# 递归调用中位数的中位数算法
return find_median_of_medians(medians)
def linear_time_select(arr, k):
# 找到中位数的中位数
median_of_medians = find_median_of_medians(arr)
# 将数组划分为三个部分
less = [x for x in arr if x < median_of_medians]
equal = [x for x in arr if x == median_of_medians]
greater = [x for x in arr if x > median_of_medians]
# 根据划分后的数组长度选择下一步操作
if k <= len(less):
# 在较小的部分递归查找第 k 小元素
return linear_time_select(less, k)
elif k <= len(less) + len(equal):
# 第 k 小元素等于中位数的中位数
return median_of_medians
else:
# 在较大的部分递归查找第 k 小元素
return linear_time_select(greater, k - len(less) - len(equal))
arr = [3, 1, 5, 2, 4, 9, 7, 8, 6]
k = 5
res = linear_time_select(arr, k)
print(f'第 {k} 小的元素为', res)
Python
实现import sys
def closest_pair(points):
points.sort() # 按照横坐标排序
min_dist = sys.maxsize # 初始化最小距离为一个很大的数
closest = None # 初始化最接近点对为 None
for i in range(len(points) - 1):
dist = abs(points[i] - points[i + 1]) # 计算相邻点对的距离
if dist < min_dist:
min_dist = dist
closest = (points[i], points[i + 1])
return closest
points = [2, 4, 1, 5, 8, 9, 3]
res = closest_pair(points)
print('最接近的点对:', res)
T ( n ) = { O ( 1 ) , n < 4 2 T ( n / 2 ) + O ( n ) , n ≥ 4 T(n) = \begin{cases} O(1) , & n < 4 \\ 2 T(n / 2) + O(n) , & n \geq 4 \end{cases} T(n)={O(1),2T(n/2)+O(n),n<4n≥4
T ( n ) = O ( n log n ) T(n) = O(n \log{n}) T(n)=O(nlogn)
Python
实现import math
# 计算两点之间的欧几里德距离
def dist(p1, p2):
return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
# 分治法求解最接近点对问题
def closest_pair(points):
# 如果点集中的点个数小于等于 3 个, 直接计算并返回最小距离对
if len(points) <= 3:
min_dist = float('inf')
min_pair = None
for i in range(len(points)):
for j in range(i + 1, len(points)):
d = dist(points[i], points[j])
if d < min_dist:
min_dist = d
min_pair = (points[i], points[j])
return min_pair
# 将点集按照 x 坐标排序
sorted_points = sorted(points, key=lambda p: p[0])
# 将点集分成左右两部分
mid = len(sorted_points) // 2
left_points = sorted_points[:mid]
right_points = sorted_points[mid:]
# 递归求解左右两部分的最接近点对
left_min_pair = closest_pair(left_points)
right_min_pair = closest_pair(right_points)
# 取左右两部分最接近点对的最小距离
if left_min_pair is None:
min_dist = dist(right_min_pair[0], right_min_pair[1])
min_pair = right_min_pair
elif right_min_pair is None:
min_dist = dist(left_min_pair[0], left_min_pair[1])
min_pair = left_min_pair
else:
left_dist = dist(left_min_pair[0], left_min_pair[1])
right_dist = dist(right_min_pair[0], right_min_pair[1])
if left_dist < right_dist:
min_dist = left_dist
min_pair = left_min_pair
else:
min_dist = right_dist
min_pair = right_min_pair
# 在横跨左右两部分的点中寻找更近的点对
mid_x = sorted_points[mid][0]
strip = []
# 将点集按照 y 坐标排序
sorted_points = sorted(points, key=lambda p: p[1])
for point in sorted_points:
if abs(point[0] - mid_x) < min_dist:
strip.append(point)
for i in range(len(strip)):
for j in range(i + 1, min(i + 7, len(strip))):
d = dist(strip[i], strip[j])
if d < min_dist:
min_dist = d
min_pair = (strip[i], strip[j])
return min_dist, min_pair
points = [(2, 3), (12, 30), (40, 50), (5, 1), (12, 10), (3, 4)]
min_dist, min_pair = closest_pair(points)
print(f'最接近的点对为: {min_pair}, 点对距离为 {min_dist}')
Python
实现def generate_schedule(k):
n = 1
for i in range(1, k + 1):
n *= 2
schedule = [[0] * (n + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
schedule[1][i] = i
m = 1
for s in range(1, k + 1):
n //= 2
for t in range(1, n + 1):
for i in range(m + 1, 2 * m + 1):
for j in range(m + 1, 2 * m + 1):
schedule[i][j + (t - 1) * m * 2] = schedule[i - m][j + (t - 1) * m * 2 - m]
schedule[i][j + (t - 1) * m * 2 - m] = schedule[i - m][j + (t - 1) * m * 2]
m *= 2
return schedule
k = 3
schedule = generate_schedule(k)
for item in schedule:
print(item)
Python
实现def generate_schedule(num_teams):
# 如果队伍数为奇数, 添加一个虚拟队伍来凑成偶数
if num_teams % 2 != 0:
num_teams += 1
num_rounds = num_teams - 1 # 总轮数
half_teams = num_teams // 2 # 每轮比赛的队伍数
teams = list(range(1, num_teams + 1))
schedule = []
for round in range(num_rounds):
matches = []
for i in range(half_teams):
match = (teams[i], teams[num_teams - i - 1])
matches.append(match)
schedule.append(matches)
# 重新排列队伍, 固定第一支队伍, 其他队伍按顺序循环移动
teams.insert(1, teams.pop())
return schedule
num_teams = 8
schedule = generate_schedule(num_teams)
round_num = 1
for matches in schedule:
print(f'Round {round_num}:')
for match in matches:
print(f'Team {match[0]} vs Team {match[1]}')
print()
round_num += 1