算法
思想
分治法
概念:
将规模为n的问题化为k个独立的子问题,分别求解每个子问题,然后把子问题的解进行综合,从而得到原问题的解
步骤:
- 将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
- 若子问题规模足够小则直接求解,否则递归求解各个子问题
- 将子问题的解合并为原问题的解
动态规划
概念:
动态规划是通过把原问题分解成相对简单的子问题的方式来解决复杂问题的方法。它的基本思想是将待求解问题分解成子问题,然后依据子问题的解以得出原问题的解
步骤:
- 将问题表示成多步判断
- 确定是否满足优化原则
- 确定子问题的重叠性
- 列出递推方程和边界条件
- 求解子问题
- 记录中间结果
- 得到原问题的解
两个基本要素:
最优子结构性质:一个问题的最优解包含其子问题的最优解
重叠子问题性质:子问题可能需要重复计算
贪心算法
概念:
贪心算法是在当前情况下做出的最优决定,它只考虑眼前,获得的是局部最优解,并且希望通过每次获得局部最优解最后找到全局的最优解
步骤:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
两个基本要素:
贪心选择性质:所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到
最优子结构性质:一个问题的最优解包含其子问题的最优解
回溯法
概念:
回溯法是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回到上一步,重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的状态点称为“回溯点”
步骤:
- 针对所给问题,定义问题的解空间
- 确定易于搜索的解空间结构
- 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索
两大剪枝函数:
使用约束函数,剪去不满足约束条件的路径
使用限界函数,剪去不能得到最优解的路径
分治算法
裴波那契数列
一般法
Fib(n)
f[0] = f[1] = 1
for i <- 2 to n
f[i] = f[i-1] + f[i-2]
return f[i]
时间复杂度:O(n)
矩阵求解
Power(M, n) //求矩阵M的n次幂
if n = 1
return M
else
if n % 2 == 1
return M * (Power(M, (n-1)/2)^2)
else
return Power(M, n/2)^2
时间复杂度:O(log(n))
快速排序
QuickSort(A, p, r) //A为数组,p、r为数组下标
if p<r
q <- Partition(A,p,r) //确定划分位置
A[p] <-> A[q]
QuickSort(A,p,q-1) //划分[p...q-1]
QuickSort(A,q+1,r) //划分[q+1...r]
Partition(A, p, r) //A为数组,p、r为头尾标号
x <- A[p] //以第一个元素为参考值
i <- p+1
j <- r
while true
while A[j] >= x //从后往前找到第一个比参考值小的
j <- j-1
while A[i] <= x //从前往后找到第一个比参考值大的
i <- i+1
if i < j
A[i] <-> A[j]
else
return j //当i>=j时退出
end
时间复杂度:O(nlog(n))
找最大和最小
FindMaxMin(A, n) //A为数组,n为数组大小
s <- 0
if n % 2 == 1 //如果n是奇数,将最小值和最大值的初值都设为第一个元素的值
Max[0] <- A[0]
Min[0] <- A[0]
s <- 1
i <- s
j <- s
while j < n-1 //将元素两两分为一组并比较,得到n/2个较小和n/2个较大
if arr[j] < arr[j+1]
Max[i] <- A[j+1]
Min[i] <- A[j]
else
Max[i] <- A[j]
Min[i] <- A[j+1]
i <- i+1
j <- j+2
max <- FindMax(Max)
min <- FindMin(Min)
return max, min
FindMax(A, n)
max <- A[0]
for i <- 1 to n-1
if A[i] > max
max <- A[i]
return max
FindMin(A, n)
max <- A[0]
for i <- 1 to n-1
if A[i] < min
min <- A[i]
return min
时间复杂度:O(n)
W(n)=⌈3n/2⌉-2
平面最近点对
MinDistance(P, X, Y) //P为点集,X、Y分别为横、纵坐标数组
if Size(P) <= 3
d <- MinDis(P,X,Y) //如果P中点数小于等于3,则直接计算
else
Sort(X) //排序X,Y
Sort(Y)
Pl, Pr, Xl, Xr, Yl, Yr <- Partition(P,X,Y) //做垂直线将P划分,Pl点在左边,Pr点在右边
disl <- MinDidtance(Pl,Xl,Yl) //求解Pl中的最小距离
disr <- MinDidtance(Pr,Xr,Yr) //求解Pr中的最小距离
d <- min{dl, dr}
d <- Update(Pl, Pr, Xl, Xr, Yl, Yr, d) //检查垂直线两边距离d范围内,是否有点对的距离小于d
return d
如图所示,Pi是在分割线左边且距离分割线小于d的点,检查过程中需要检查矩形区域内的所有点,小方格的对角线长为5d/6,右侧每个小方格至多1个点,因此每个点至多比较6个点
时间复杂度:O(nlog2n)
动态规划算法
投资问题
m元钱,n项投资,fi(x)表示将x元投入项目i的收益,如何分配m元钱使得投资收益最大
x | f1(x) | f2(x) | f3(x) | f4(x) |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
1 | 11 | 0 | 2 | 20 |
2 | 12 | 5 | 10 | 21 |
3 | 13 | 10 | 30 | 22 |
4 | 14 | 15 | 32 | 23 |
5 | 15 | 20 | 40 | 24 |
递推关系:
设Fk(x)表示x元钱投入前k个项目的最大收益
Fk(x)=max{Fk-1(x-xk)+fk(xk)} 1<k<n, 0<=xk<=x
F1(x)=f1(x)
Invest(f, m, n) //f[x,y]表示将x元投入项目y的收益
F <- 0 //F[x,y]表示将x元投入前y个项目的最大收益
X <- 0 //X[x,y]用来记录将x元投入前y个项目产生最大收益时,投入第y个项目的钱数
for i <- 1 to m
F[i,1] <- f[i,1]
X[i,1] <- i
for j <- 2 to n
for i <- 1 to m
for k <- 0 to i
t <- F[i-k,j-1] + f[k,j]
if t > F[i,j]
F[i,j] <- t
X[i,j] <- k
return F, X
二维表:
时间复杂度:O(nm2)
空间复杂度:O(mn)
矩阵连乘
A为矩阵序列,Ai为Pi-1*Pi阶矩阵,确定乘法顺序使得元素相乘的总次数最少
递推关系:
设m[i,j]表示Ai…j的最少的相乘次数
MatrixChain(P, n)
m <- 0
s <- 0 //s记录分割点
for l <- 2 to n //l为计算的矩阵链长
for i <- 1 to n-l+1
j <- i+l-1 //计算m[i,j]
for k <- i to j-1
t <- m[i,k] + m[k+1] + P[i-1]*P[k]*P[j]
if t < m[i,j]
m[i,j] <- t
s[i,j] <- k
return m, s
时间复杂度:O(n3)
空间复杂度:O(n2)
最长公共子序列
X序列长度为m,Y序列长度为n
递推关系:
C[i,j]为x1-xi,y1-yj的最长公共子序列
LCS(X, Y, m, n)
C <- 0
B <- null //B为记录
for i <- 1 to m
for j <- 1 to n
if X[i] = Y[j]
C[i,j] <- C[i-1,j-1] + 1
B[i,j] <- "↖"
else if C[i,j-1] >= C[i-1,j]
C[i,j] <- C[i,j-1]
B[i,j] <- "←"
else
C[i,j] <- C[i-1,j]
B[i,j] <- "↑"
return C, B
时间复杂度:O(mn)
空间复杂度:O(mn)
最大子段和
A(a1…an)为整数序列
递推关系:
bi表示最后一项为ai的序列构成的最大的子段和
B[i]=max{A[i],B[i-1]+A[i]} 1<=i<=n
解为max{0, b1, … , bn}
MaxSum(A, n)
sum <- 0
B <- 0
B[1] <- A[1]
for i <- 2 to n
if B[i-1] > 0
B[i] <- B[i-1] + A[i]
else
B[i] <- A[i]
if B[i] > sum
sum <- B[i]
right <- i
return sum, right
时间复杂度:O(n)
空间复杂度:O(n)
贪心算法
删数问题
给定n位正整数a,去掉其中任意k个数字后,剩下的数字按原次序排列组成一个新的正整数。对于给定的n位正整数a和正整数k,设计一个算法找出剩下数字组成的新数最小的删数方案
https://blog.csdn.net/weixin_46891900/article/details/121482115
a[i]代表正整数a中从左到右数的第i位数字
A[i]用来记录组成的新数
DeleteNum(a, k, n)
A <- a
l <- n //l用来记录新数的位数
for i <- k to 1
for j <- 1 to l
if j = l or A[j] > A[j+1]
remove(A, j) //从A中删除第j位数字
l <- l-1
break
return A
时间复杂度:O(nk)
最优合并问题
https://wenku.baidu.com/view/34933e63862458fb770bf78a6529647d27283425.html?wkts=1698888996138
a为待合并序列,n为待合并序列的个数
MaxMerge(int a[],int n)
b <- a
max <- 0
for i <- 1 to n
sort(b+i, b+n, desc) //对未操作部分降序排序
b[i+1] <- b[i] + b[i+1]
max <- max + b[i+1] - 1
return max
MinMerge(int a[],int n)
b <- a
min <- 0
for i <- 1 to n
sort(b+i, b+n, asc) //对未操作部分升序排序
b[i+1] <- b[i] + b[i+1]
min <- min + b[i+1] - 1
return min
最优分解问题
https://www.cnblogs.com/halfrot/p/14617196.html
n为被分解的数
Decompose(n)
if n = 1
result <- 1
else if n = 2 or n = 3
result <- 2
else if n = 4
result <- 3
else
sum <- 0
i <- 2
j <- 1
while n > sum+i
sum <- sum+i
R[i-1] <- i
i <- i+1
t <- i-1
for j <- n-sum to 1
R[t] <- R[t]+1
t <- t-1
if t = 0
t <- i-1
result <- 0
for i ∈ R
result <- result*R[i]
return result
单源最短路径
dist[i]标记从起点到点i的距离
path[i]标记到点i时,经过的前一个点
Dijkstra(G,V,W,s) //G为图,V为顶点集,W[i][j]为边<i,j>的权重,如果没有边值为∞,s为起点顶点
S <- {s}
for i ∈ V - S
dist[i] <- W[s][i]
if W[s][i] ≠ ∞
path[i] <- s
while V - S ≠ ∅
i <- FindShortestVertex(V-S, s) //从V-S中找出dist标记距离最短的顶点
S <- S ∪ {i}
//更新距离与路径标记
for j ∈ V - S
if dist[j] > dist[i]+W[i][j]
dist[j] <- dist[i]+W[i][j]
path[j] <- i
return dist, path
设顶点数为n
时间复杂度:O(n2)
活动安排问题
S为活动,si,fi为活动i的开始和结束时间
n为活动的数量
Arrange(S, s, f, n)
SortByFinish(S, f) //按活动结束时间从小到大排序
A <- {1}
t <- 1
for i <- 2 to n
if f[t] <= s[i]
A <- A ∪ {i}
t <- i
return A
时间复杂度:O(nlog(n))
哈夫曼编码
Huffman(C)
pQ <- C //将C中元素存入优先级队列pQ中,优先级为频率递增的顺序
while |C| ≠ 1
z <- Allocate() //生成根结点
z.left <- pQ.pop() //从优先级队列pQ中取出频率最低的树,作为z的左子树
z.right <- pQ.pop()
z.data <- z.left.data + z.right.data
pQ.push(z) //放入树z
return pQ
时间复杂度:O(nlog(n))
参考书籍:《算法设计与分析(第2版)》