这里是poj1915上的一道在棋盘上搜索走步路径的题目:
代码如下(使用BFS):
/* * POJ 1915 * 使用广度优先搜索寻找最佳路径 * 本题可以优化的地方:如果只是为了获得最小的到达目标节点的步数, * 那么可以直接在Node的结构体中定义step域,然后在搜索过程中逐步更新step */ #include #include #include #include using namespace std; #define MAXSIZE 301 #define NOT ! int size; int startX,startY,endX,endY; int minStep; typedef struct Node{ //node结构体 int x; int y; int myindex; int parent; }*PNode; int direction[8][2]={{-2,-1},{-1,-2},{-2,1},{-1,2},{1,-2},{2,-1},{1,2},{2,1}}; //骑士可能的前进方向 bool visited[MAXSIZE][MAXSIZE]; //记录当前节点是否被访问过,默认初始化为false queue Queue; Node start,End; //开始节点和结束节点 PNode NodeTable[MAXSIZE*MAXSIZE]={NULL}; //存储节点的表 static int id; //用于记录一次访问到的节点的ID值 void init(){ id=0; visited[startX][startY]=true; start.x=startX; start.y=startY; start.myindex=0; start.parent=-1; Queue.push(start); End.x=endX; End.y=endY; NodeTable[id]=new Node(); //注意一定要先new分配内存空间,再进行赋值 *NodeTable[id]=start; //cout<<"Queue.size()=" <=size || tmp.y<0 || tmp.y>=size) //判断移动后的位置是否任在棋盘内部 continue; if( false==visited[tmp.x][tmp.y]){ id++; tmp.parent=curr.myindex; tmp.myindex=id; Queue.push(tmp); NodeTable[id]=new Node(); *NodeTable[id]=tmp; visited[tmp.x][tmp.y]=true; } else continue; } } return; } void output(){ stack Stack; Node now=End; Stack.push(now); while(now.parent!=-1){ Stack.push(*NodeTable[now.parent]); //通过栈来回溯出路径 now=Stack.top(); minStep++; } cout<<"minStep="<>size>>startX>>startY>>endX>>endY; init(); clock_t time=clock(); BFS(); cout<<"计算用时: "<
通过这道题目我有如下的几点总结:
1、一开始我通过struct结构来表示每一步到达的坐标位置,其中设置了一个struct* parent的指针,而不是改进后的int parent; 但是在实际的调试过程中,出现了非常奇怪的现象,被压入到Queue队列中的节点的parent指针域不断地变动,最后发现原来是curr节点每次都变动的问题,而tmp节点的parent指针始终指向的是curr节点的。
~~~~~~~所以最后干脆使用int parent,省去指针的隐蔽错误!!~~~~~~~~~~~~~~~~~
while(NOT Queue.empty() && NOT Finish ){ Node curr=Queue.front(); //每次重新扩展节点时,curr发生了变化 Queue.pop(); if( endX==curr.x && endY==curr.y){ Finish=true; End.parent=curr.parent; Node* t=End.parent; while( t!=NULL){ minStep++; t=t->parent; } cout<<"mimStep="<=size || tmp.y<0 || tmp.y>=size) //判断移动后的位置是否任在棋盘内部 continue; if( false==visited[tmp.x][tmp.y]){ tmp.parent=&curr; //tmp节点的parent域始终指向的是curr Queue.push(tmp); visited[tmp.x][tmp.y]=true; } else continue; }
2、一开始发生错误的时候,不要急着直接按原问题的规模来调试,可以先用小规模问题来测试,这样往往更容易发现程序到底错在哪里。
3、上面程序可能存在的不足就是NodeTable占用了比较大的空间,不过也不会很大,因为一旦搜索到目标节点就停止了。这里使用数组其实主要是起到还原路径的作用。
4、在上面的程序中:
声明了:
PNode NodeTable[MAXSIZE*MAXSIZE]={NULL}; //存储节点的表
而在实际向表中插入数据的时候,必须先通过new操作符创建内存空间,然后进行赋值:
NodeTable[id]=new Node(); //注意一定要先new分配内存空间,再进行赋值
*NodeTable[id]=start;
不能直接用: *NodeTable[id]=start;
双向广度搜索算法:
主要的算法思想是这样的:
有些问题按照广度优先搜索法则扩展结点的规则,既适合顺序,也适合逆序,于是我们考虑在寻找目标结点或路径的搜索过程中,初始结点向目标结点和目标结点向初始结点同时进行扩展—,直至在两个扩展方向上出现同一个子结点,搜索结束,这就是双向搜索过程。出现的这个同一子结点,我们称为相交点,如果确实存在一条从初始结点到目标结点的最佳路径,那么按双向搜索进行搜索必然会在某层出现“相交”,即有相交点,初始结点一相交点一目标结点所形成的一条路径即是所求路径。
理论上双向BFS可以在时间和空间上做到单向BFS的 1/2 次方。所以速度比单向的bfs快一点。
下面是poj1915的另一个程序版本:双向广度搜索
/* * POJ1915 * 使用双向广度搜索最佳路径 * 双向广度搜索算法有两种形式:1、交替搜索 2、选择节点个数较少的方向扩展 * * 下面的程序使用交替搜索策略 */ #include #include #include #include using namespace std; #define NOT ! #define MAXSIZE 801 int visited[MAXSIZE][MAXSIZE]; //0:未访问 1:前向BFS访问 2:后向BFS访问 typedef struct Node{ int x; int y; int parent; int myindex; }*PNode; const int direction[8][2]={{-2,-1},{-1,-2},{-2,1},{-1,2},{1,-2},{2,-1},{1,2},{2,1}}; //骑士可能的前进方向 queue front,back; //两个方向上搜索的队列 Node org,dest,half1,half2; //起始节点和目标节点,相交节点 int size,startX,startY,destX,destY; int minStep; //记录最短路径 PNode NodeTable[MAXSIZE*MAXSIZE]={NULL}; //记录查找路径过程中的节点 static int id; /* * 记录查找过程中的节点 */ void InsertNodeTable(int id,Node n){ NodeTable[id]=new Node(); *NodeTable[id]=n; } /* * 输入程序数据并进行初始化 */ void input(){ cout<<"input size、start position、end position:"<>size>>startX>>startY>>destX>>destY; org.x=startX; org.y=startY; org.parent=-1; dest.x=destX; dest.y=destY; dest.parent=-1; memset((void*)visited,0,sizeof(visited)); visited[startX][startY]=1; id=0; org.myindex=id; InsertNodeTable(id,org); visited[destX][destY]=2; id=1; org.myindex=id; InsertNodeTable(id,dest); front.push(org); back.push(dest); } /* * 判断移动后的位置是否合法 */ inline bool isOk(int x,int y){ if( x<0 || x>=size || y<0 || y>=size) return false; else return true; } /* * 双向广度搜索 */ void Bi_bfs(){ bool finish=false; Node curr,tmp; int i,j,frontSize,backSize; int n1=0,n2=0; while( NOT finish){ frontSize=front.size(); for(j=0;j
下面总结一些双向广度搜索的特点:
1、因为是在两个方向上,同时逆向的搜索,所以需要维护两个数据结构是两个个OPEN表和CLOSE表.因为每个方向上的搜索都要对搜索过程进行记录。这里OPEN表就是一般BFS中的队列数据结构,具有先进先出的特点,而CLOSED列表就相当于对已检测节点做标记的记录数组。
2、在双向广度搜索中,可以交替的扩展每一个搜索方向上的节点,当然如果进一步优化:可以选择扩展节点个数较少的方向。
3、在扩展过程中对访问数组使用1、2进行标记两个不同方向上搜索到的节点,另外每次扩展的时候,都是把当前层的节点全部扩展完,这样再递增搜索深度的变量你n1、n2,这样最后总的搜索最优步数就是 n1+n2 。如果要还原路径,只要在两个方向上分别记录相交节点half1、half2就可以了。
下面是POJ2046 Gap的题目,主要的方法还是使用广度优先搜索,下面用三种方法进行了实现:第一种使用set容器进行状态的判重,第二种方法使用hash进行判重,第三种方法使用A*算法+hash进行判重。
/* * POJ 2046 * 解题思路: * 1、搜索策略:BFS * 2、状态描述:通过string来表示每一个状态,这样搜索的效率会更高,很有启发性 * 3、由于在搜索过程中是存在重复状态的,这里使用set来完成重复状态的检测 * 这样的算法复杂度为log(n),比使用hash检测差一点O(1) */ #include #include #include #include #include #include using namespace std; #define NOT ! typedef struct map{ //描述状态的结构体 int step; string state; }Map; Map org,dest; //起始状态和目标状态 queue