【算法分析与设计】广度优先搜索
2012-02-22 21:12
344 查看
图论的广度优先搜索经常用于对解空间的搜索,尤其是求某个解,且这个解具有最短步骤的时候,广度优先搜索是极佳的选择。
1.首先应该注意的是解空间的组织,搜索算法的解通常被安排成多个步骤,每个步骤一条边,而从起点到终点的一条路径就构成了一个解。
2.解空间树的存储问题,由于解空间的树,往往并不是二叉树,所以左右孩子的组织方式是不太适合的,所以一般采用记录父结点的方式,那么从终点到起点就一定可以找到一条路径,打印路径一般采用递归打印。这种方式正好也适合广度优先算法一般只找寻某个解的特点。
3.如果每个结点都按所有可能进行扩展,那么最后的解空间无疑是指数级的,这个时候剪枝就显得尤为重要,对于不可能的结点一定要确保剪枝动作的执行,这样才能得到一个相对可行的搜索算法。
算法的框架如下:
begin
建立队列Q
初始状态入队
while(Q非空)
取出队首结点e
if ( e是可行解状态 )
返回e作为结果
if( e不可能是解的某个状态)
剪枝动作,转到取下一个队首结点
for 0->k(所有的可能状态)
生成新的状态结点插入队尾
end
下面以POJ1606:jugs为例说明广度优先搜索的应用:
时间限制:
1000ms
内存限制:
65536kB
描述
In the movie "Die Hard 3", Bruce Willis and Samuel L. Jackson were confronted with the following puzzle. They were given a 3-gallon jug and a 5-gallon jug and were asked to fill the 5-gallon jug with exactly 4 gallons. This problem generalizes that puzzle.
You have two jugs, A and B, and an infinite supply of water. There are three types of actions that you can use: (1) you can fill a jug, (2) you can empty a jug, and (3) you can pour from one jug to the other. Pouring from one jug to the other stops when the
first jug is empty or the second jug is full, whichever comes first. For example, if A has 5 gallons and B has 6 gallons and a capacity of 8, then pouring from A to B leaves B full and 3 gallons in A.
A problem is given by a triple (Ca,Cb,N), where Ca and Cb are the capacities of the jugs A and B, respectively, and N is the goal. A solution is a sequence of steps that leaves exactly N gallons in jug B. The possible steps are
fill A
fill B
empty A
empty B
pour A B
pour B A
success
where "pour A B" means "pour the contents of jug A into jug B", and "success" means that the goal has been accomplished.
You may assume that the input you are given does have a solution.
输入
Input to your program consists of a series of input lines each defining one puzzle. Input for each puzzle is a single line of three positive integers: Ca, Cb, and N. Ca and Cb are the capacities of jugs A and B, and N is the goal. You can assume 0 < Ca <= Cb
and N <= Cb <=1000 and that A and B are relatively prime to one another.
输出
Output from your program will consist of a series of instructions from the list of the potential output lines which will result in either of the jugs containing exactly N gallons of water. The last line of output for each puzzle should be the line "success".
Output lines start in column 1 and there should be no empty lines nor any trailing spaces.
样例输入
样例输出
综合来看,设计一个广度优先搜索算法的步骤是:
1.定义状态结点。通常以记录父节点来构造解空间树。
2.确定状态是否是所求解的函数,isAnswer() 太直接的时候可直接写,省略函数
3.确定状态是否需要剪枝的函数,isUnwanted() 通常需要仔细考虑什么情况需要剪枝
4.生成新状态结点的函数,doTransfer() 有时可能比较简单,不需要专门的函数
5.广度优先搜索的核心函数BFS()
算法的更宏观思想是:
初始结点入队
队列非空就做下列动作
取出队首状态结点
如果已经是解,返回
如果需要剪枝,剪枝
如果可以扩展,扩展并入队
1.首先应该注意的是解空间的组织,搜索算法的解通常被安排成多个步骤,每个步骤一条边,而从起点到终点的一条路径就构成了一个解。
2.解空间树的存储问题,由于解空间的树,往往并不是二叉树,所以左右孩子的组织方式是不太适合的,所以一般采用记录父结点的方式,那么从终点到起点就一定可以找到一条路径,打印路径一般采用递归打印。这种方式正好也适合广度优先算法一般只找寻某个解的特点。
3.如果每个结点都按所有可能进行扩展,那么最后的解空间无疑是指数级的,这个时候剪枝就显得尤为重要,对于不可能的结点一定要确保剪枝动作的执行,这样才能得到一个相对可行的搜索算法。
算法的框架如下:
begin
建立队列Q
初始状态入队
while(Q非空)
取出队首结点e
if ( e是可行解状态 )
返回e作为结果
if( e不可能是解的某个状态)
剪枝动作,转到取下一个队首结点
for 0->k(所有的可能状态)
生成新的状态结点插入队尾
end
下面以POJ1606:jugs为例说明广度优先搜索的应用:
时间限制:
1000ms
内存限制:
65536kB
描述
In the movie "Die Hard 3", Bruce Willis and Samuel L. Jackson were confronted with the following puzzle. They were given a 3-gallon jug and a 5-gallon jug and were asked to fill the 5-gallon jug with exactly 4 gallons. This problem generalizes that puzzle.
You have two jugs, A and B, and an infinite supply of water. There are three types of actions that you can use: (1) you can fill a jug, (2) you can empty a jug, and (3) you can pour from one jug to the other. Pouring from one jug to the other stops when the
first jug is empty or the second jug is full, whichever comes first. For example, if A has 5 gallons and B has 6 gallons and a capacity of 8, then pouring from A to B leaves B full and 3 gallons in A.
A problem is given by a triple (Ca,Cb,N), where Ca and Cb are the capacities of the jugs A and B, respectively, and N is the goal. A solution is a sequence of steps that leaves exactly N gallons in jug B. The possible steps are
fill A
fill B
empty A
empty B
pour A B
pour B A
success
where "pour A B" means "pour the contents of jug A into jug B", and "success" means that the goal has been accomplished.
You may assume that the input you are given does have a solution.
输入
Input to your program consists of a series of input lines each defining one puzzle. Input for each puzzle is a single line of three positive integers: Ca, Cb, and N. Ca and Cb are the capacities of jugs A and B, and N is the goal. You can assume 0 < Ca <= Cb
and N <= Cb <=1000 and that A and B are relatively prime to one another.
输出
Output from your program will consist of a series of instructions from the list of the potential output lines which will result in either of the jugs containing exactly N gallons of water. The last line of output for each puzzle should be the line "success".
Output lines start in column 1 and there should be no empty lines nor any trailing spaces.
样例输入
3 5 4 5 7 3
样例输出
fill B pour B A empty A pour B A fill B pour B A success fill A pour A B fill A pour A B empty B pour A B success
/*全局变量*/ int ca = 0; int cb = 0; int N = 0; /******************************************* 这不是一种好的程序设计方式,但在单个解题时, 有时参数直接在全局,可以减少很多参数传递 *******************************************/ /*状态结点,使用BFS通常需要根据情况设立状态结点*/ struct state { int a; int b; int act; //上一步通过什么操作到达这一步的 struct state* pre;//上一个状态的位置,也可以认为是记录父结点 }; /*********************************************** 这种父节点的记录方式,可以简化许多不必要的存储 但剪枝运算和打印路径的运算会略增加复杂度, 但由于剪枝元算检查路径的时候最多遍历一条路径 而解空间树的高度通常是不可能太高的(32层时,解空间已经是n^32级), 所以这种带检查的剪枝运算不会成为算法的瓶颈 而打印路径只遍历一次路径,更加不可能成为算法的瓶颈 当然也可以将state* pre改为一个数组或者向量 *************************************************/ /************************************************* 这道题目中每步可能采取的动作,其实只是为存储动作的 时候不必要存储字符串,只需要存储一个编号即可 同时也方便打印路径的时候用 **************************************************/ char action[6][10]= { "fill A", "fill B", "empty A", "empty B", "pour A B", "pour B A" }; /************************************* 可以理解为新状态结点的生成动作, 视题目不同而繁简不同 **************************************/ void doAction(state *src,state *dest,int act) { dest->pre = src; dest->act = act; switch(act) { case 0:dest->a = ca; dest->b = src->b;break; case 1:dest->a = src->a; dest->b = cb; break; case 2:dest->a = 0; dest->b = src->b;break; case 3:dest->a = src->a; dest->b = 0; break; case 4:if( cb-src->b >= src->a ){dest->a = 0; dest->b = src->a + src->b;} else {dest->a = src->a -(cb-src->b); dest->b = cb;} break; case 5:if( ca-src->a >= src->b ){dest->a = src->a+src->b; dest->b=0;} else {dest->b = src->b-(ca-src->a); dest->a = ca;} break; } //printf("%d %d %s %d %d\n",src->a,src->b,action[act],dest->a,dest->b); } /******************************** 这道题目的剪枝动作所需要的检查操作 *********************************/ bool check(state *st)//检查st前面的状态是否出现过和st一样的 { state *tmp = st->pre; while(tmp) { if(tmp->a == st->a && tmp->b==st->b) return true; else tmp = tmp->pre; } return false; } /*递归的路径打印函数*/ void print_path(state* st) { if(st->pre ==NULL) return; print_path(st->pre); printf("%s\n",action[st->act]); } int count = 0;//测试统计用的,用来比较剪枝前后搜索动作的数量 /*核心的广度优先搜索函数*/ void BFS(state* &root,int target) { std::deque<state*> state_que; state_que.push_back(root); while(!state_que.empty()) { state *front = state_que.front(); state_que.pop_front(); //printf("%d %d\n",front->a,front->b); if(front->b == target) { root = front; return;} if(check(front)) continue; for(int i=0; i<6; ++i) if(front->act != i) { state* next = new state; doAction(front,next,i); state_que.push_back(next); count++; } } } int main() { while(scanf("%d%d%d",&ca,&cb,&N)!=EOF) { state *root=new state; root->a = 0; root->b = 0; root->act = -1; root->pre = NULL; BFS(root,N); print_path(root); printf("success\n"); } return 0; }
综合来看,设计一个广度优先搜索算法的步骤是:
1.定义状态结点。通常以记录父节点来构造解空间树。
2.确定状态是否是所求解的函数,isAnswer() 太直接的时候可直接写,省略函数
3.确定状态是否需要剪枝的函数,isUnwanted() 通常需要仔细考虑什么情况需要剪枝
4.生成新状态结点的函数,doTransfer() 有时可能比较简单,不需要专门的函数
5.广度优先搜索的核心函数BFS()
算法的更宏观思想是:
初始结点入队
队列非空就做下列动作
取出队首状态结点
如果已经是解,返回
如果需要剪枝,剪枝
如果可以扩展,扩展并入队
相关文章推荐
- 【算法设计与分析】蚂蚁
- 大数据算法MOOC笔记2:大数据算法定义、分析与设计
- 第四周算法分析与设计:Swap Nodes in Pairs
- 算法分析与设计课程(4):【leetcode】Wildcard Matching
- 算法分析与设计——Tsp(2)
- 算法分析与设计基础(1)汉诺塔问题
- 算法分析与设计丨第一周丨LeetCode(2)——Different Ways to Add Parentheses(Medium)
- 算法分析与设计丨第十八周丨LeetCode(21)——Binary Tree Maximum Path Sum(Hard)
- 算法设计若干通用策略和分析技术
- 算法分析与设计——LeetCode:2.Add Two Numbers
- 算法分析与设计——贪心法
- 算法分析与设计的一些题目
- Stanford公开课之算法:设计与分析——(第四周) Programming Question-4
- [算法分析与设计] leetcode 每周一题: 335. Self Crossing
- 南邮算法分析与设计实验2 动态规划法
- 算法设计与分析之不定期更新的日常3+贪心
- [算法设计与分析]3.3.2标志量的妙用(冒泡算法改进+三个数的最小公约数)
- 【算法分析与设计】——八皇后问题的详解
- YUV / RGB 格式分析及快速查表算法设计
- 书名: 算法分析与设计