一、算法及基础
- 排序
- 冒泡、插入、归并、桶排序,稳定
- 选择、希尔、堆、快速、组合,不稳定
- 伪代码
- 循环后跟do
- if后跟then
- 算法:对特定问题求解步骤的一种描述,是若干条指令的有穷序列
- 特性
- 输入(0个或多个)
- 输出(至少一个)
- 确定性(无歧义)
- 有限性
- 特性
- 程序:是算法用某种程序设计语言的具体实现
- 算法求解一般过程
- 理解问题
- 设计算法:选择数据结构,算法策略
- 证明正确性
- 分析算法
- 设计程序
- 算法分析:事后统计、事前分析
- O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(2n)<O(n!)<O(n^n)
- 渐进上界 O
- 渐近下界 Ω
- 渐近精确界 Θ
二、分治
- 基本思想:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之
- 正确性证明:
- 循环不变量 (初始化、迭代、终止)
- 结构归纳
- 二分查找
- 时间复杂度 O(logn)
- 空间复杂度 非递归O(1) 递归O(logn)
- 循环日程赛
- n=2^k个选手,递归将选手分为两半
ROUND-ROBIN-CALENDAR-REC(A,n)
if n == 1
then A[1][1] = 1
return
ROUND-ROBIN-CALENDAR-REC(A,n/2)
COPY(n,A)
COPY(n,A)
m = n/2
for i = 1 to m
for j = 1 to m
do A[i][j+m] = A[i][j] + m; //左 加上m 到右上
A[i+m][j] = A[i][+m]; // 右上 到 左下
A[i+m][j+m] = A[i][j]; // 左上到右下
- 时间复杂度:O(n^2)
- 空间复杂度:O(logn)
- 快排
- 第一部分元素均不大于基准元素,第二个是基准元素,第三部分元素均不小于基准元素
- 基准元素的选取
- 取第一个
- 取最后一个
- 取中间位置
- 三者取中
- 随机
QUICK-SORT(A,low,high)
if low < high
then mid = PARTITION(A,low,high) // 划分序列
QUICK-SORT(A,low,mid-1)
QUICK-SORT(A,mid+1,high)
PARTITION(A,low,high)
i = low
j = high
mid = A[low]
while i < j
do
while i < j && A[j] >= mid
do j--
while i < j && A[i] <= mid
do i++
if i < j
then tmp = A[i]
A[i] = A[j]
A[j] = tmp
A[low] = A[i]
A[i] = mid
return i
- 时间复杂度:最好,平均 O(nlogn) 最坏O(n^2)
- 空间复杂度:最好,平均 O(logn) 最坏O(n)
三、动态规划
- 基本性质
- 最优子结构 :问题最优解包含其子问题的最优解。为动态规划的基础。
- 子问题重叠:解决冗余
- 基本要素:自底向上
- 求解步骤
- 分析最优解性质,是否具备最优子结构
- 递归定义最优值 (状态转移方程)
- 自底向上求出最优值
- 构造最优解
- 矩阵连乘
//m[1..n][1..n]二维数组记录最优值,s[][]记录最优解
DP(n,P)
for i = 1 to n
do m[i] = 0
for l = 2 to n // 区间长度 m[i][j] j-i+1 = l
do for i = 0 to n-l+1 // 起点
do j = i+l-1 // 终点
m[i][j] = m[i][i]+m[i+1][j]+p[i-1]p[i][j]
s[i][j] = i
for k = i+1 to j-1 // 分割点k
do q = m[i][k] + m[k+1][j] + P[i-1]P[k]P[j]
if q < m[i][j]
then m[i][j] = q
s[i][j] = k
return m and s
// 时间复杂度 O(n^3)
// 空间复杂度 O(n^2)
// 构造最优解 A[]代表矩阵
PRINt(s,i,j)
if i == j
then print "A[" + i + "]"
else print "("
PRINT(s,i,s[i][j])
PRINT(s,s[i][j]+1,j]
print ")"
// 时间复杂度 O(n)
// 空间复杂度 O(n)
- 备忘录方法 为动态规划的变形
- 备忘录采用自顶向下的递归方式
- 最长公共子序列(LCS)
// dp[][]记录最优值,state[][]记录最优解
LCS(X,Y)
m = length(X)
n = length(Y)
for i = 1 to m
do dp[i][0] = 0
for j = 1 to n
do dp[0][j] = 0
for i = 1 to m
do for j = 1 to n
do if X[i] == Y[j] // 来自左上角
then dp[i][j] = dp[i-1][j-1] + 1
state[i][j] = 1
else if dp[i-1][j] >= dp[i][j-1] // 来自上边
then dp[i][j] = dp[i-1][j]
state[i][j] = 3
else dp[i][j] = dp[i][j-1] // 来自左边
state[i][j] = 2
// 时间复杂度 O(mn) ==> O(n^2)
//构造最优解
PRINT(state,X,i,j)
if i == 0 or j == 0
then return
else if state[i][j] = 1
then PRINT(state,X,i-1,j-1)
PRINT(X[i])
else if state[i][j] = 2
then PRINT(state,X,i,j-1)
else PRINT(state,X,i-1,j)
// 时间复杂度 O(m+n) ==> O(n)
- 0-1背包
- w为重量,v为价值,W为容量
DP(w,v,W)
n = length(w)
for i = 1 to n
do dp[i][0] = 0
for j = 1 to W
do dp[0][j] = 0
for i = 1 to n
do for j = 1 to W
do if w[i] > j // 装不下
then dp[i][j] = dp[i-1][j]
else dp[i][j] = max{dp[i-1][j],dp[i-1][j-w[i]] + v[i]}
return dp
// 构造最优解
PRINT(dp,w,W)
n = length(w)
j = W
for i = n to 1
do if dp[i][j] != dp[i-1][j]
then x[i] = 0
else x[i] = 1
j -= w[i]
return x
- 改进 跳跃点
- 贪心 按单位价值递减排序
三、贪心
- 性质
- 贪心选择性质
- 最优子结构性质
- 解题步骤
- 分解 相互独立
- 解决 贪心选择
- 合并
- 与动态规划区别
- 动态规划依赖相关子问题的解,贪心仅考虑当前状态
- 动态规划自底向上 ,贪心自顶向下
- 会场安排
- 选择最早结束时间且不与当前会议重叠
// 开始时间B,结束时间E
GREEDY(B,E)
SORT(B,E) // 按结束时间E排序
n = length(B)
A = {1} // 选择1
k = 1
for m = 2 to n
do if B[m] > E[k]
then A = A ∪ {m}
k = m
return A
// 时间复杂度 O(nlogn)
// 空间复杂度 O(logn)
- 单源最短路径(SSSP)
- Dijkstra,w[][]记录权值,dist[]记录从源点到其他顶点的长度,pre[]记录路径,前直节点
// 优先队列Q
DIJLSTRA(G,w,s)
d[s] = 0
for each v ∈ G.V - {s}
do d[v] = ∞
S = 空集
Q = G.v
while Q != 空集
do u = min(Q)
S = S ∪ {u}
for each v ∈ G.Adj[u]
do if d[v] > d[u] + w[u][v]
then d[v] = d[u]+w[u][v]
// 时间复杂度 O(n^2)
// 空间复杂度 O(n)
- 最小生成树
- Prim算法
MST-PRIM(G, w, r) //r为根结点
for each u∈G.V //初始化
u.key = ∞ //u.key为连接u和树中结点所有边中最小边的权重
u.p = NIL //u.p为结点u的父结点
r.key = 0
Q = G.V //优先队列。后述的U=V-Q
while Q ≠ Φ
u = EXTRACT-MIN(Q)
for each v∈G.Adj[u]
if(v∈Q and w(u, v) < v.key
v.p = u
v.key = w(u, v) //DECREASE-KEY
//时间复杂度 O(n^2)
// 空间复杂度 O(n)
四、回溯 (dfs)
能进则进,不进则换,不换则退
- 0-1背包
backtrack-knapsack-01 (t, w, v, W)
if cw + w[t] <= W //搜索左子树
x[t]=1
cw += w[t] //当前背包中物品的总重量
cp += v[t] //当前背包中物品的总价值
backtrack-knapsack-01(t+1,w,v,W-cw);
cw -= w[t]
cp -= v[t]
if bound(t+1,W-cw,cp,w,v) > bestp //搜索右子树
x[t]=0
backtrack-knapsack-01(t+1,w, v, W-cw)
bound(i, cleft, cp, w, v) //i为第i层, cleft为背包的剩余容量,cp为当前背包中物品的总价值
b = cp
while i <= n && w[i] <= cleft
cleft -= w[i]
b += v[i]
i ++
if i <= n //装满背包
b += v[i] / w[i] * cleft
return b
//时间复杂度:O(n2^n)
//空间复杂度:O(n)
五、分支限界(bfs)
- 0-1背包
q-knapsack-01(w, v, W)
i=1, cw=0, cp=0, up=0, bestp=0, q=φ, stNode=NULL
while TRUE{
if cw+w[i] < W //左结点
bestp=(cp+v[i]>bestp)?cp+v[i]:bestp
enqueue (q, new QNode(cw+w[i], cp+v[i], up, true, i+1)
up = bound(i+1)
if up > bestp //右结点
enqueue(q, new QNode(cw, cp, up, false, i+1)
QNode node = dequeue(q)
if node==NULL
break
if node != NULL
cw = node.cw
cp = node.cp
up = node.up
i = node.level
if i == n+1
stNode = node.ptr
if stNode != NULL
for j=n to 1 by -1
bestx[j] = stNode.isLeft ? 1:0
stNode = stNode.parent
}
output(bestx)
return cp
// 时间复杂度:O(n2^n)
//空间复杂度:O(2^n)
六、概率算法
- 数值概率算法、蒙特卡罗算法、拉斯维加斯算法、舍伍德算法
- 线性同余法是产生伪随机数的常用方法
- 数值概率算法
- 计算Π的值
- 圆的面积/正方形面积 = Π/4 = k/n ==> Π = 4k/n
- 计算定积分
- I = m/n
- 计算Π的值
- 舍伍德算法
- 设法消除最坏情形行为与特定实列之间的关联性
- 快排
- 选择基准元素时引入随机性
- 拉斯维加斯算法
- 不会得到不正确的解
- 蒙特卡罗算法
- 求准确解,但未必正确