双向广搜

双向广搜

(2011-08-31 16:45:24)

  Eight   

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1043

讲到双向广搜,那就不能不讲经典的八数码问题,有人说不做此题人生不完整 。

所谓双向广搜,就是初始结点向目标结点和目标结点向初始结点同时扩展,直至在两个扩展方向上出现同一个结点,搜索结束。它适用的问题是,扩展结点较多,而目标结点又处在深沉,如果采用单纯的广搜解题,搜索量巨大,搜索速度慢是可想而知的,同时往往也会出现内存空间不够用的情况,这时双向广搜的作用就体现出来了。双向广搜对单纯的广搜进行了改良或改造,加入了一定的“智能因数”,使搜索能尽快接近目标结点,减少了在空间和时间上的复杂度。

当在讲题前,不得不先给大家补充一点小知识,大家都知道搜索的题目其中难的一部分就是事物的状态,不仅多而且复杂,要怎么保存每时刻的状态,又容易进行状态判重呢,这里提到了一种好办法   ------康托展开(只能针对部分问题)

 

康托展开

康托展开式:

X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0!

其中,a为整数,并且0<=ai<i(1<=i<=n)。

例:

问:1324是{1,2,3,4}排列数中第几个大的数?

解:第一位是1小于1的数没有,是0个 0*3! 第二位是3小于3的数有1和2,但1已经在第一位了,所以只有一个数2 1*2! 。第三位是2小于2的数是1,但1在第一位,所以有0个数 0*1! ,所以比1324小的排列有0*3!+1*2!+0*1!=2个,1324是第三个大数。

好吧,先看下代码实现:

int factory[]={1,1,2,6,24,120,720,5040,40320,362880}; // 0..n的阶乘

int Gethash(char eight[])

{

int k=0;

for(int i=0;i<9;i++)    // 因为它有八位数(针对八数码问题)

{

int t=0;

for(int j=i+1;j<9;j++)

if(eight[j]<eight[i])

t++;

k+=(t*factory[9-i-1]);

}

return k;   // 返回该数是第几大

}

好的,现在再来看看双向广搜模版:

void TBFS()

{

bool found=false;

memset(visited,0,sizeof(visited));  // 判重数组

while(!Q1.empty())  Q1.pop();   // 正向队列

while(!Q2.empty())  Q2.pop();  // 反向队列

//======正向扩展的状态标记为1,反向扩展标记为2

visited[s1.state]=1;   // 初始状态标记为1

visited[s2.state]=2;   // 结束状态标记为2

Q1.push(s1);  // 初始状态入正向队列

Q2.push(s2);  // 结束状态入反向队列

while(!Q1.empty() || !Q2.empty())

{

if(!Q1.empty())

BFS_expand(Q1,true);  // 在正向队列中搜索

if(found)  // 搜索结束

return ;

if(!Q2.empty())

BFS_expand(Q2,false);  // 在反向队列中搜索

if(found) // 搜索结束

return ;

}

}

void BFS_expand(queue<Status> &Q,bool flag)

{

s=Q.front();  // 从队列中得到头结点s

Q.pop()

for( 每个s 的子节点 t )

{

t.state=Gethash(t.temp)  // 获取子节点的状态

if(flag)   // 在正向队列中判断

{

if (visited[t.state]!=1)// 没在正向队列出现过

if(visited[t.state]==2)  // 该状态在反向队列中出现过

{

各种操作;

found=true;

return;

}

visited[t.state]=1;   // 标记为在在正向队列中

Q.push(t);  // 入队

else    // 在正向队列中判断

{

if (visited[t.state]!=2) // 没在反向队列出现过

if(visited[t.state]==1)  // 该状态在正向向队列中出现过

{

各种操作;

found=true;

return;

}

visited[t.state]=2;  // 标记为在反向队列中

Q.push(t);  // 入队

}

好的,现在开始说说八数码问题

其实,Eight有一个很重要的判断,那就是逆序数的判断。如果i>j,并且ai<aj,那么定义(i,j)为一个逆序对,而对于一个状态排列中所含的逆序对的个数总和就是逆序数。而本题的逆序数的奇偶性的判断是至关重要的:

如果x在同一行上面移动那么1~8的逆序数不变

如果x在同一列上面移动,每次逆序数增加偶数个或者减少偶数个

因为目标结点的状态的逆序数为0,为偶数,所以每次访问到的状态的逆序数也必须为偶数,保持奇偶性性质,否则就不必保存该状态。

#include<iostream>

#include<queue>

using namespace std;

#define N 10

#define MAX 365000

char visited[MAX];

int father1[MAX];  // 保存正向搜索当前状态的父亲状态结点

int father2[MAX];  // 保存反向搜索当前状态的父亲状态结点

int move1[MAX];    // 正向搜索的方向保存

int move2[MAX];   //  反向搜索的方向保存

struct Status   // 结构

{

char eight[N];  // 八数码状态

int space;     // x 位置

int state;    // hash值,用于状态保存与判重

};

queue<Status> Q1;  // 正向队列

queue<Status> Q2;  // 反向队列

Status s,s1,s2,t;

bool found;  // 搜索成功标记

int state;   // 正反搜索的相交状态

int factory[]={1,1,2,6,24,120,720,5040,40320,362880};  // 0..n的阶乘

int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};

int Gethash(char eight[])  // 康托展开(获取状态,用于判重)

{

int k=0;

for(int i=0;i<9;i++)

{

int t=0;

for(int j=i+1;j<9;j++)

if(eight[j]<eight[i])

t++;

k+=(t*factory[9-i-1]);

}

return k;

}

int ReverseOrder(char eight[])  // 求状态的逆序数

{

int i,j,num=0;

for(i=0;i<9;i++)

{

for(j=0;j<i;j++)

{

if(int(eight[i])==9)

{

break;

}

if(int(eight[j])==9)

continue;

if(int(eight[j])>int(eight[i]))

num++;

}

}

num=num%2;

return num;

}

void BFS_expand(queue<Status> &Q,bool flag)  // 单向广度搜索

{

int k,x,y;

s=Q.front();

Q.pop();

k=s.space;

x=k/3;

y=k%3;

for(int i=0;i<4;i++)

{

int xx=x+dir[i][0];

int yy=y+dir[i][1];

if(xx>=0 && xx<=2 && yy>=0 && yy<=2)

{

t=s;

t.space=xx*3+yy;   // 计算x位置

swap(t.eight[k],t.eight[t.space]);  // 交换两个数位置

t.state=Gethash(t.eight);

if(flag)  // 在正向队列中判断

{

if(visited[t.state]!=1 && ReverseOrder(t.eight)==0)  // 未在正向队列出现过并且满足奇偶性

{

move1[t.state]=i;  // 保存正向搜索的方向

father1[t.state]=s.state; // 保存正向搜索当前状态的父亲状态结点

if(visited[t.state]==2)   //  当前状态在反向队列中出现过

{

state=t.state;  // 保存正反搜索中相撞的状态(及相交点)

found=true;    // 搜索成功

return;

}

visited[t.state]=1;   // 标记为在正向队列中

Q.push(t);  // 入队

}

}

else  // 在反向队列中判断

{

if(visited[t.state]!=2 && ReverseOrder(t.eight)==0)   // 未在反向队列出现过并且满足奇偶性

{

move2[t.state]=i;  // 保存反向搜索的方向

father2[t.state]=s.state; // 保存反向搜索当前状态的父亲状态结点

if(visited[t.state]==1)  //  当前状态在正向队列中出现过

{

state=t.state;  // 保存正反搜索中相撞的状态(及相交点)

found=true;   // 搜索成功

return;

}

visited[t.state]=2;  // 标记为在反向队列中

Q.push(t);   // 入队

}

}

}

}

return ;

}

void TBFS()            // 双向搜索

{

memset(visited,0,sizeof(visited));

while(!Q1.empty())

Q1.pop();

while(!Q2.empty())

Q2.pop();

visited[s1.state]=1;   // 初始状态

father1[s1.state]=-1;

visited[s2.state]=2;   // 目标状态

father2[s2.state]=-1;

Q1.push(s1);

Q2.push(s2);

while(!Q1.empty() || !Q2.empty())

{

if(!Q1.empty())

BFS_expand(Q1,true);

if(found)

return ;

if(!Q2.empty())

BFS_expand(Q2,false);

if(found)

return ;

}

}

void PrintPath1(int father[],int move[])   // 从相交状态向初始状态寻找路径

{

int n,u;

char path[1000];

n=1;

path[0]=move[state];

u=father[state];

while(father[u]!=-1)

{

path[n]=move[u];

n++;

u=father[u];

}

for(int i=n-1;i>=0;--i)

{

if(path[i] == 0)

printf("u");

else if(path[i] == 1)

printf("d");

else if(path[i] == 2)

printf("l");

else

printf("r");

}

}

void PrintPath2(int father[],int move[])   // 从相交状态向目标状态寻找路径

{

int n,u;

char path[1000];

n=1;

path[0]=move[state];

u=father[state];

while(father[u]!=-1)

{

path[n]=move[u];

n++;

u=father[u];

}

for(int i=0;i<=n-1;i++)

{

if(path[i] == 0)

printf("d");

else if(path[i] == 1)

printf("u");

else if(path[i] == 2)

printf("r");

else

printf("l");

}

}

int main()

{

int i;

char c;

while(scanf(" %c",&c)!=EOF)

{

if(c==‘x‘)

{

s1.eight[0]=9;

s1.space=0;

}

else

s1.eight[0]=c-‘0‘;

for(i=1;i<9;i++)

{

scanf(" %c",&c);

if(c==‘x‘)

{

s1.eight[i]=9;

s1.space=i;

}

else

s1.eight[i]=c-‘0‘;

}

s1.state=Gethash(s1.eight);

for(int i=0;i<9;i++)

s2.eight[i]=i+1;

s2.space=8;

s2.state=Gethash(s2.eight);

if(ReverseOrder(s1.eight)==1)

{

cout<<"unsolvable"<<endl;

continue;

}

found=false;

TBFS();

if(found)   // 搜索成功

{

PrintPath1(father1,move1);

PrintPath2(father2,move2);

}

else

cout<<"unsolvable"<<endl;

cout<<endl;

}

return 0;

}

双向广搜,布布扣,bubuko.com

时间: 2024-12-26 15:26:02

双向广搜的相关文章

codevs 1225:八数码难题【双向广搜】

这里是传送门 这道题用普通BFS是可以做的,但是很明显没得过,效率太低了.效率更高的算法A*和双向广搜都可取,这写一下双向广搜的. 注意题目中的判重很重要,可以转化成九位数用hash来解决这个问题. #include <set> #include <string> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define

nyoj 523 双向广搜

题目链接: http://acm.nyist.net/JudgeOnline/problem.php?pid=523 #include<iostream> #include<cstdio> #include<queue> using namespace std; /* 用普通搜索TLE,已知起点和终点,可以考虑双向广搜或A*算法加速搜索 双向广搜,一个方向从出发点向终点搜索,一个方向从终点向出发点搜索,搜索到相同的结点时,即找到最短路径. */ const int N

双向广搜 POJ 3126 Prime Path

POJ 3126  Prime Path Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 16204   Accepted: 9153 Description The ministers of the cabinet were quite upset by the message from the Chief of Security stating that they would all have to change th

HDU 3085 Nightmare Ⅱ (双向广搜)

题意:有M,G两人和鬼魂(Z)在n*m的方格内,M每秒走3步,G每秒走一步,鬼魂每秒走2步,问是否能 不遇到鬼魂下两人相遇,鬼魂可以穿墙(X),人不可以.初始鬼魂有2个. #include<stdio.h> #include<string.h> #include<string> #include<queue> #include<map> #include<iostream> #include<algorithm> #def

双向广搜 开始!!!

先简单的了解一下,双向广搜很好理解,就是从两端一起搜,如果遇到之前已经搜到过的状态,就相当于已经有解了,这样就会节省一半的内存和时间,并且代码复杂度并不高.只需要在正常的基础上多开一个域,保存这个点是从起始状态还是终止状态拓展的.当然双向广搜中状态的判断需要一些技巧,现在还没有总结出什么. 八数码问题: 很经典的一种双向广搜,因为只有九个数,所以用康托展开作为hash值.拓展时,如果这个hash值已经访问过,就判断,如果是两条路过来的就输出,否则就继续做:对于没有访问过的hash值就可以添加到队

双向广搜 codevs 3060 抓住那头奶牛

codevs 3060 抓住那头奶牛 USACO 时间限制: 1 s 空间限制: 16000 KB 题目等级 : 黄金 Gold 题目描述 Description 农夫约翰被告知一头逃跑奶牛的位置,想要立即抓住它,他开始在数轴的N 点(0≤N≤100000),奶牛在同一个数轴的K 点(0≤K≤100000).约翰有两种移动方式:1 分钟内从x 点移动到x+1 或x-1:1 分钟内从x 点移动到2x.假设奶牛不会移动,约翰抓住它需要多少时间? 输入描述 Input Description 一行两个

双向广搜的DIJKSTRA算法--简易的北京地铁导航实现

本学期的课程设计,实现最短路的算法,于是采用了DIJKSTRA算法,并用双向广搜优化了. 实现了简易的北京地铁导航.于是把代码分享出来. (核心代码是find_min(),Dijkstra()部分) 转载或者用到里面的代码请注明博主姓名以及出处! (注:只输入了图片里的地铁站信息,所用到的文件最下面有下载,因为这些文件是我和同学一条一条的录入的,所以如果你用到请务必注明这些文件的出处) 代码: /**************************************************

HDU 1195 Open the Lock (双向广搜)

题意:给你初始4个数字和目标4个数字,问是否能由初始经过变换到目标数字: 变换规则:每个数字可以加1(9+1=1)或减1(1-1=9),或交换相邻的数字(最左和最右不是相邻的). 双向广搜:分别对初始和目标数字进行广搜,vis数组用1和2标记两种已搜索的数字,用mp数组记录状态的步数. 当从前往后搜可以到达2或从后往前搜可以到达1状态则就可以了... #include<stdio.h> #include<string.h> #include<string> #inclu

HDU 1401 Solitaire (双向广搜)

题意:在二维8*8的方格,给定4个初始点和4个最终点,问在8步内是否能从初始点走到最终点, 规则:每个点能上下左右移动,若4个方向已经有点则可以跳到下一个点. 双向广搜:同时对初始点和最终点广搜4步,对每一步记录状态,初始点为'1',最终点为'2', 若在限定时间内初始点的状态能到达'2',或最终点的状态能到达'1',则为YES!要记得排序.. #include<stdio.h> #include<string.h> #include<queue> #include&l