一、隐式图搜索
作用在解答树,是一种广度优先遍历的方法。解答树,或者更一般的状态图,在某些问题中规模本身是无限的,但是通常能嗅到如“最简单”,“最短”,“最少”等字眼,这就意味着广度优先遍历。
再回忆广度优先遍历的实现,由一个队列保存新发现的节点,并且有这样的距离关系:
新发现的节点与源节点的距离=其父节点与源节点的距离+1
广度优先遍历的实现:
1 #用字典保存一个图的邻接表形式 2 g = {'A': ['B', 'C'], 3 'B': ['A', 'D', 'E'], 4 'C': ['A', 'F', 'G'], 5 'D': ['B', 'H'], 6 'E': ['B', 'H'], 7 'F': ['C', 'G'], 8 'G': ['C', 'F'], 9 'H': ['D', 'E']} 10 11 pi = {node: 0 for node in g.keys()} #父节点 12 d = {node: 0 for node in g.keys()} #广度遍历的层次 13 visited = {node: 0 for node in g.keys()} #访问记录 14 15 def BFS(start): 16 visited[start] = 1 17 global q 18 q = [] #队列,保存新发现的节点 19 q.append(start) 20 21 while len(q) > 0: 22 v = q.pop(0) 23 for w in g[v]: 24 if visited[w] == 0: 25 visited[w] = 1 26 pi[w] = v 27 d[w] = d[v] + 1 #层次=父节点+1 28 29 q.append(w) 30 visited[v] = 2 31 32 def main(): 33 BFS('A') 34 print pi
例一、最优程序问题
有一台只有一个数据栈的计算机,支持ADD, SUB, MUL, DIV, DUP五种运算。设栈顶元素分别为a, b, c,五种运算的效果如下:
ADD: a, b依次出栈,就算a+b,结果压栈
SUB: a, b依次出栈,就算b-1,结果压栈
MUL: a, b依次出栈,就算a*b,结果压栈
DIV: a, b依次出栈,就算a/b,结果压栈
DUP: a出栈,再连续把两个a压栈
遇到一下情况,发生错误:执行DIV操作时,栈顶元素为0;执行ADD, SUB, MUL, DIV操作时栈中只有一个元素
问题:输入(a,b),设计一个最短的程序,使得执行前栈中只有元素a,执行后栈顶元素是b
分析:
粗略看,这里有一个解答树,是一个五叉树。但是如果没有明显的叶子节点,无法回溯。有注意到最短这个关键词,因此本题用广度优先的方法。
问题的难点在于怎么表示这个状态图呢?状态图中的节点有这样几个属性:pre(历史操作),下一步操作,stack(当前的栈状态)。
遍历过程由源节点出发,在每一个当前节点,计算下一步操作改变栈状态后,扩展下一层节点入队列。退出条件无疑是当前节点计算下一步操作后等于目标值。
程序:
import sys import copy name_dict = {0: "ADD", 1: "SUB", 2: "MUL", 3: "DIV", 4: "DUP"} op_dict = {0: lambda a, b: a + b, 1: lambda a, b: a - b, 2: lambda a, b: a * b, 3: lambda a, b: b / a} class Node: def __init__(self): self.pre = [] #前缀操作 self.op = -1 #下一次操作 self.stack = [] #当前的栈情况 def op(): n.pre.append(name_dict[n.op]) if n.op != 4: a = n.stack.pop() b = n.stack.pop() c = op_dict[n.op](a, b) n.stack.append(c) else: a = n.stack.pop() n.stack.append(a) n.stack.append(a) def main(): global n1 global n2 global n global q for line in sys.stdin: line = line.rstrip() s = line.split(" ") n1 = int(s[0]) n2 = int(s[1]) n = Node() n.op = 4 n.stack.append(n1) q = [] q.append(copy.deepcopy(n)) while (len(q) > 0): n = q[0] q.pop(0) op() if n.stack[-1] == n2: break for i in range(4): if len(n.stack) < 2: continue if i == 3 and n.stack[-1] == 0: continue n.op = i q.append(copy.deepcopy(n)) n.op = 4 q.append(copy.deepcopy(n)) for opp in n.pre: print opp print if __name__ == '__main__': main()
二、拓扑排序
拓扑排序是深度优先遍历的一个例子。拓扑排序专门作用与有向无环图(DAG),可以用来描述事务图的因果关系,具体是经过拓扑排序的节点箭头指向均是向右
注意点:判断有向图是否成环,即判断是否有反向边.
例一、假设有4个变量abcd,若a<b, c<b, d<c,求在这些条件下可能的从小到大的排序
分析:这种从小到大的排序明显的方向性
#include<stdio.h> int g[4][4]; int f[4]; int visit[4]; int counter = 3; int dfs(int); int main() { int x; g[0][1] = 1; g[2][1] = 1; g[3][2] = 1; g[1][3] = 1; for (x = 0; x < 4; x++) { if (visit[x] == 0) { if (dfs(x) == -1) { printf("this is not a DAG\n"); return -1; } } } for (x = 0; x < 4; x++) { printf("%d ", f[x]); } return 0; } int dfs(int v) { int w; visit[v] = -1; for (w = 0; w < 4; w++) { if (g[v][w] == 1) { if (visit[w] == -1) { //排除反向边的情况 //拓扑排序只有在有向无环图中才有意义 return -1; }else if (visit[w] == 0) { if (dfs(w) == -1) { return -1; } } } } visit[v] = 1; f[counter] = v; counter--; }
但是,深度优先遍历最重要的应用还是回溯与递归