目录
一、重点概念(容易忽视的定义)
二、一些问题Q & A
三、部分伪代码模板
四、部分证明的思路模板
解空间:实例I的满足显式约束条件的所有元组,构成I的解空间,即所有xi合法取值的元组的集合——可行解。
状态空间树:解空间的树结构称为状态空间树(state space tree)
(回溯法要回画部分状态空间树)
问题状态:树中的每一个结点代表问题的一个状态,称为问题状态(problem state) 。
状态空间:由根结点到其他结点的所有路径确定了这个问题的状态空间(state space) 。
解状态:是这样一些问题状态S,对于这些问题状态,由根到S的那条路径确定了这个问题解空间中的一个元组(solution states) 。
答案状态:是这样的一些解状态S,对于这些解状态而言,由根到 S的这条路径确定了问题的一个解(满足隐式约束条件的解)(answer states) 。
活结点:自己已经生成,但其儿子结点还没有全部生成并且有待生成的结点。
E-结点(expansion node):当前正在生成其儿子结点的活结点。
死结点:不需要再进一步扩展或者其儿子结点已全部生成的结点。
限界函数:在结点生成的过程中,定义一个限界函数,用来杀死还没有生成全部儿子结点的一些活结点——这些活结点已无法满足限界函数的条件,因此不可能导致问题的答案。
结点成本的估计函数 c(x)=f(h(x))+g(x)
g^(X) :是由X到达一个答案结点所需成本的估计函数。
LC-检索:选择 c^(.)值最小的活结点作为下一个E-结点的状态空间树检索方法。
最小成本的上界:定义U为最小成本解的成本上界,对具有c^(X)>U 的所有活结点可以被杀死,从而可以进一步使算法加速,减少求解的盲目性。
前驱子图为Gπ=(Vπ,Eπ),算法终止时,Gπ是一棵最短路径树。
松弛操作,对于每个结点v,维持一个属性v.d,记录从源点s到结点v的最短路径权重的上界,称v.d为s到v的最短路径估计。
贪心选择性质:可以通过做出局部最优(贪心)选择来构造全局最优解的性质。
横跨切割:如果一条边(u,v)∈E的一个端点在集合S中,另一个端点在集合V-S中,则称该条边横跨切割(S,V-S)。
尊重:如果边集A中不存在横跨该切割的边,则称该切割尊重集合A。
轻量级边:在横跨一个切割的所有边中,权重最小的边称为轻量级边。
O(g(n)):存在正实数c和n0,使得对所有n≥n0,0≤f(n)≤cg(n)
Ω(g(n)):存在正实数c,n0,使得对所有n≥n0,0≤cg(n)≤f(n)
θ(g(n)):存在正实数c1,c2,n0,使得对所有n≥n0,0≤c1g(n)≤f(n)≤c2g(n)
子问题图:用于描述子问题与子问题之间的依赖关系。
LIFO和FIFO分枝-限界法存在的问题?
对下一个E-结点的选择规则过于死板。对于有可能快速检索到一个答案结点的结点没有给出任何优先权。
计算结点成本函数的困难?
计算结点X的代价通常要检索子树X才能确定,因此计算C(X)的工作量和复杂度与解原始问题是相同的。
如何避免单纯考虑g^(X)造成的纵深检查?
引进h(X)改进成本估计函数
当有多个答案结点时,LC是否一定找得到具有最小成本的答案结点呢?
否。
因为可能存在这样的结点X和Y:c(X)>c(Y),但c^(X) < c^(Y)。
解决方式:对每一对c(X)<c(Y)的结点X和Y,有c^(X) < c ^(Y) 使得LC能够找到一个最小成本的答案
贪心算法的设计思想
贪心算法是这样一种方法:分步骤实施,它在每一步仅作出当时看起来最佳的选择,即局部最优的选择,并寄希望这样的选择最终能导致全局最优解。
LC-检索的基本思想
寻找一种排序函数C(.),该函数能够让答案结点尽早生成。
最短路径三角不等式 δ(s,v)≤δ(s,u)+w(u,v)
假设P是从源结点s到结点v的一条最短路径,则P的权重不会比任何从s到v的其他路径权重大。路径P的权重不会比这样一条特定路径的权重大:从源结点s到结点u的一条最短路径再加上边(u,v)而达到结点v的这条路径。
描述:
①从结点v开始,首先访问结点v,给v标上已访问标记。
② 访问邻接于v且目前尚未被访问的所有结点,此时结点v被检测,而v的这些邻接结点是新的未被检测的结点。将这些结点依次放置到一个称为未检测结点表的队列中。
③ 若未检测结点表为空,则算法终止;否则
④ 取未检测结点表的表头结点作为下一个待检测结点,重复上述过程。直到Q为空,算法终止
伪代码:
procedure BFS(v)
//宽度优先搜索,它从结点v开始,所有已访问结点被标记VISITED(i)=1
VISITED(v) <- 1 //VISITED(1:n)是一个标志数组,初始值为VISITED(i)=0,1≤i≤n
u <- v
将Q初始化为空 //Q是未检测结点的队列
loop
for 邻接于u的所有结点w do
if VISITED(w)=0 then //w未被访问
call ADDQ(w,Q) //ADDQ将w加入到队列Q的末端
VISITED(w) <- 1 //同时标识w已被访问
endif
repeat
if Q 为空 then return endif
call DELETEDQ(u,Q) //DELETEDQ取出队列Q的表头,并赋给变量u
repeat
end BFS
宽度优先周游
procedure BFT(G,n)
//G的宽度优先搜索
int VISITED(n)
for i <- 1 to n do VISITED(i) <- 0 repeat
for i <- 1 to n do //反复调用BFS
if VISITED(i)=0 then call BFS(i) endif
repeat
end BFT
向前边:BFS中由u到达未访问结点w的边(u,w)称为向前边。
若G是连通图,则BFS终止时,T构成一棵生成树,称为图G的宽度优先生成树。
描述
从结点v开始,首先访问v,给v标上已访问标记;然后中止对v的检测,并从邻接于v且尚未被访问的结点的中找出一个结点w开始新的检测。在w被检测后,再恢复对v的检测。当所有可到达的结点全部被检测完毕后,算法终止。
伪代码
procedure DFS(v)
//已知n结点的图G=(V,E)以及初值置零的数组VISITED(1:n)
VISITED(v) <- 1
for 邻接于v的每个结点w do
if VISITED(w)=0 then
call DFS(w)
endif
repeat
END DFS
D_Search:深度搜索就是将BFS中的队列换成栈,和DFS还是有些不一样的地方
描述
按深度优先的方法从开始结点进行搜索
开始结点是第一个活结点,也是 E*-*结点。
如果能从这个E*-结点移动到一个新结点,那么这个新结点将变成活结点和新的E-结点 (旧的E-*结点仍是一个活结点)。
如果不能移到一个新结点,当前的E*-结点就“死”了,然后返回到最近被考察的活结点(回溯),这个活结点重新变成E-*结点。
当找到了答案或者穷尽了所有的活结点时,搜索过程结束
迭代伪代码
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
递归伪代码
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
分枝限界法和回溯法的区别在于:
回溯法:深度优先,一般用于寻找所有解
分枝限界:广度优先,一般用于寻找最优解
procedure LC(T,c^)
//为找答案结点检索T,c^为结点成本估计函数
if T是答案结点 then 输出T; return endif //T为答案结点,输出T
E <- T
将活结点表初始化为空
loop
for E的每个儿子X do
if X是答案结点 then 输出从X到T的路径 ; return endif
call ADD(x) //X是新的活动结点,ADD将X加入活结点表中
PARENT(X) <- E //指示到根的路径
repeat
if 不再有活结点 then print("no answer code") ; stop endif
call LEAST(E) //从活结点表中找c^最小的活结点,赋给X,并从活结点中删除
repeat
end LC
对设计的贪心算法,证明能够找到最优解(证明贪心解是最优解)(贪心选择性质)
通常先考查某个子问题的最优解,然后用贪心选择替换某个其它选择来修改此解,从而得到一个相似但更小的子问题。
通过逐步替换证明贪心解不差于假设的最优解
数学归纳法 k -> k+1
最优子结构
当前解最优,那它的子问题也必须是最优解。
反证:如果子问题有更优解,那么该问题也能得到更优的解。