算法设计与分析期末复习
主要参考:
算法设计与分析(北京航空航天大学MOOC)
算法设计与分析(北京大学MOOC)
华中科技大学 计算机科学与技术学院 算法设计与分析 课堂教学PPT
回溯算法的例子:4后问题、0-1背包问题、货郎问题
解的形式:向量
搜索空间:树,可能是n叉树,子集树、排列树等等,输的节点对应与部分向量,可行解在叶节点
搜索方法:深度优先,宽度有限,…,跳跃式遍历搜索树,找到解
约束条件、回溯判定
可用回溯法求解的问题:
问题的解可以用一个n元组(x1,…,xn)来表示,其中的xi取自于某个有穷集Si,并且这些解必须使得某一规范函数P(x1,…,xn)取极值或满足该规范函数条件。
(1)适用:求解搜索问题和优化问题
(2)搜索空间:树,结点对应部分解向量,可行解在树叶上
(3)搜索过程:采用系统的方法隐含遍历搜索树
(4)搜索策略:深度优先,宽度优先,函数优先,宽深结合等
(5)结点分支判定条件:
满足约束条件–分支扩张解向量,不满足约束条件–回溯到该结点的父结点
显式约束条件:限定每个xi只能从一个给定的集合上取值
隐式约束条件:xi必须彼此相关的情况
(6)结点状态:动态生成
白结点(尚未访问),灰结点(正在访问该结点为根的子树),黑结点(该结点为根的子树遍历完成)
(7)存储:当前路径
状态空间树:
- 问题状态(problem state):树中的每一个结点确定所求解问题的一个问题状态。
- 状态空间(state space):由根结点到其他结点的所有路径则确定了这个问题的状态空间。
- 解状态(solution states):是这样一些问题状态S,对于这些问题状态,由根到S的那条路径确定了这解空间中的一个元组。
- 答案状态(answer states):是这样的一些解状态S,对于这些解状态而言,由根到S的这条路径确定了这问题的一个解。
- 静态树(static trees):树结构与所要解决的问题的实例无关。
- 动态树(dynamic trees):根据不同的实例而使用不同的树结构。
- 活结点:自己已经生成而其所有的儿子结点还没有全部生成的结点。
- E-结点(正在扩展的结点):当前正在生成其儿子结点的活结点。
- 死结点:不再进一步扩展或者其儿子结点已全部生成的生成结点。
构造状态空间树的两个方法:回溯法、分枝-限界方法
- 回溯法------当前E-结点R,生成一个新的儿子C,则C就变成一个新的E-结点,对子树C完全检测后,R结点再次成为E-结点
- 分枝-限界方法------一个E-结点一直保持到变成死结点为止
多米诺性质:
(1)定义解向量和每个分量的取值范围
解向量为
确定xi的取值集合为Xi,i=1,2,…,n
(2)在
(3)确定结点儿子的排列规则
(4)判断是否满足多米诺性质(个人感觉算法实现中华科ppt表述 更为直观)
(5)确定每个结点分支的约束条件
(6)确定搜索策略:深度优先,宽度优先等
(7)确定存储搜索路径的数据结构
递归实现
(北大mooc表述)
- Sk是xk的取值集合
算法 ReBack(k)
if k>n then 是解
else while Sk ≠ 空集 do
xk <- Sk 中最小值
Sk <- Sk-{xk}
计算Sk+1
ReBack(k+1)
另一种表述(华科ppt表述):
(x1, x2, …xi-1)是由根到一个结点的路径
T(x1, x2, …xi-1)是所有xi的集合:对于每一个xi , (x1, x2, …xi)是由根到结点xi的路径(也就是根据某一确定的x1到xi-1所得到的xi取值的集合)
限界函数Bi,如果路径(x1, x2, …xi)不可能延伸到一个答案结点,则Bi(x1, x2, …xi)取假值,否则取真值(也就是添加上xi仍可能是答案,则为真,不可能为答案则为假)
procedure RBACKTRACK(k)
global n,X(1:n)
for 满足下式的每个X(k)
X(k) ∈ T(X(1),...,X(k-1)) and B (X(1),...,X(k)) = true do
if(X(1),...,X(k)) 是一条已抵达答案结点的路径
then print(X(1),...,X(k)) endif
call RBACKTRACK(k+1)
repeat
end RBACKTRACK
迭代实现
Backtrack
输入: n
输出:所有的解
对于 i = 1,2,...,n 确定Xi //确定初始取值
k <- 1
计算Sk
while Sk ≠ 空集 do //满足约束分支搜索
xk <- Sk中的最小值,Sk <- Sk-{xk}
if k是解
if k>1 then k <- k-1 goto (第7行) //回溯
另一种表述:
procedure BACKTRACK(n)
integer k,n; local X(1:n)
k <- 1
while k>0 do
if 还剩有没检验过的X(k)使得
X(k) ∈ T(X(1),...,X(k-1)) and B(X(1),...,X(k)) = true
then
if(X(1),...,X(k)) 是一条已抵达一答案结点的路径
then print(X(1),...,X(k)) endif
k <- k+1
else
k <- k-1
endif
repeat
end BACKTRACK
Monte Carlo方法
从根开始,随机选择一条路径,直到不能分支为止,即从x1,x2,…,依次对xi赋值,每个xi的值是从当时的Si中随机选取,直到向量不能扩张为止
假定搜索树的其他|Si|-1个分支与以上随机选出的路径一样,计数搜索树的点数
重复步骤1和2,将结点数进行概率平均
(如果没看明白,去北大MOOC里看例子讲解)
目的:估计搜索树真正访问结点数
步骤:
随机抽样,选择一条路径
用这条路径代替其他路径
逐层累加树的结点数
多次选择,取结点数的平均值
Monte Carlo
输入:n为皇后数,t为抽样次数
输出:sum,即t次抽样路长平均值
sum <- 0
for i <- 1 to t do //凑杨次数t
m <- Estimate(n) //m为结点数
sum <- sum + m
sum <- sum / t
一次抽样
m为本次取样得到的树节点总数
k为层数
r2位上层结点数
r1位本层结点数
r1 = r2*分枝数
n为数的层数
从树根向下计算,随机选择,直到树叶
伪代码
Estimate(n)
m <- 1; r2 <- 1; k <- 1 //m为一次估计结点总数
while k ≤ n do
if Sk = ∅ then return m //不能继续
r1 <- |Sk|*r2 //r1为扩张后的结点总数
m <- m+r1 //r2位扩张前的结点总数
xk <- 随机选择Sk的元素 //随机选择一步
r2 <- r1
k <- k+1
左侧红线路径为一次抽样,右侧为扩张后的数(用于估计一次结点个数)
通过多次抽样,取平均值估计搜索树的结点数(Monte Carlo方法)
有n个集装箱,需要装上两艘载重分别为c1,c2的轮船,wi为第i个集装箱的重量,且w1+w2+…+wn≤c1+c2,。
问:是否存在一种合理的装载方案把这n个集装箱装上船?如果有,请给出一种方案。
求解思路
输入:W=
算法思想:
令第一艘船的装入量为W1(让第一艘船装的尽量多)
伪代码
算法 Loading(W,c1)
Sort(W);
B <- c1; best <- c1; i <- 1; //B为当前船1的空隙,best为最优解情况下船1的空隙
while i ≤ n do //相当于赋值一个初值
if 装入i后重量不超过c1
then B <- B-w1 ; x[i] <- 1 ; i <- i+1
else x[i] <- 0 ; i<- i+1
if B1 and x[i]=0 do
i<- i-1;
if x[i] = 1 //左子树换到右子树
then x[i] <- 0
B <- B + wi
i <- i+1
时间复杂度 可以达到O(2^n)
怎么判断是否形成了互相攻击的格局?
是否在同一列,是否在同一条对角线
procedure PLACE(k) //判断当前X(k)是否可以放置
global X(1:k); integer i,k //是否会和前k-1行放置的皇后冲突
i <- 1
while i0 do
X(k) <- X(k)+1 //第一个皇后放置的位置
while X(k) <= n and not PLACE(k) do
X(k) <- X(k)+1
repeat
if X(k) <= n //如果可以放置
then if k=n
then print(X)
else k <- k+1;X(k) <- 0 //还没到最后一行,继续下一行
else k <- k-1; //如果不能放置
endif
repeat
end NQUEENS
n个数里面存在某些个数的和等于M
元组大小固定
W(k)是按照从小到大的顺序排好序的(对理解伪代码中的剪枝很重要)
限界函数
伪代码
procedure SUMOFSUB(s,k,r) //当前和s,当前元素位置k,k之后所有数的和
global integer M,n; global real W(1:n); global boolean X(1:n)
real r,s; integer k,j
X(k) <- 1
if s+W(k)=M then //和等于了M,找到结果
print(X(j),j <- 1 to k)
else
if s+W(k)+W(k+1)<=M then //算上Wk和Wk+1也不够
call SUMOFSUB(s+W(k),k+1,r-W(k))//那么就算上Wk,继续搜索
endif
endif
if s+r-W(k)>=M and s+W(k+1) <= M
then X(k) <- 0
call SUMOFSUB(s,k+1,r-W(k))
endif
end SUMOFSUB
伪代码第12行解释:剪枝
如果算上k之后所有的还不够,那就不继续向下搜索了
由于是从小到大排序好的,如果k+1也超过了,那么后面的全都超过了,也不继续搜索
两个条件都满足的条件下才进行继续搜索