PKU1077 八数码,双向广搜+康托展开
http://poj.org/problem?id=1077
思路并不难,只是调的过程中有些问题,懒得写,从高手那转来的
双向广度优先搜索算法是对广度优先算法的一种扩展。广度优先算法从起始节点以广度优先的顺序不断扩展,
直到遇到目的节点;而双向广度优先算法从两个方向以广度优先的顺序同时扩展,一个是从起始节点开始扩展,另
一个是从目的节点扩展,直到一个扩展队列中出现另外一个队列中已经扩展的节点,也就相当于两个扩展方向出现了
交点,那么可以认为我们找到了一条路径。双向广度优先算法相对于广度优先算法来说,由于采用了从两个跟开始扩展
的方式,搜索树的深度得到了明显的减少,所以在算法的时间复杂度和空间复杂度上都有较大的优势!双向广度优先算
法特别适合于给出了起始节点和目的节点,要求他们之间的最短路径的问题。另外需要说明的是,广度优先的顺序能够保证
找到的路径就是最短路径!
基于以上思想,我们给出双向广度优先算法编程的基本框架如下:
数据结构:
Queue q1, q2; //两个队列分别用于两个方向的扩展(注意在一般的广度优先算法中,只需要一个队列)
int head[2], tail[2]; //两个队列的头指针和尾指针
算法流程:
一、主控函数:
void solve()
{
1. 将起始节点放入队列q1,将目的节点放入队列q2
2. 当 两个队列都未空时,作如下循环
1) 如果队列q1里的未处理节点比q2中的少(即tail[0]-head[0] < tail[1]-head[1]),则扩展(expand())队列q1
2) 否则扩展(expand())队列q2 (即tail[0]-head[0] >= tail[1]-head[1]时)
3. 如果队列q1未空,循环扩展(expand())q1直到为空
4. 如果队列q2未空,循环扩展(expand())q2知道为空
}
二、扩展函数:
int expand(i) //其中i为队列的编号(表示q0或者q1)
{
取队列qi的头结点H
对头节点H的每一个相邻节点adj,作如下循环
1 如果adj已经在队列qi之前的某个位置出现,则抛弃节点adj
2 如果adj在队列qi中不存在[函数 isduplicate(i)]
1) 将adj放入队列qi
2) 如果adj 在队列(q(1-i)),也就是另外一个队列中出现[函数 isintersect()]
输出 找到路径
}
三、判断新节点是否在同一个队列中重复的函数
int isduplicate(i, j) //i为队列编号,j为当前节点在队列中的指针
{
遍历队列,判断是否存在【线性遍历的时间复杂度为O(N),如果用HashTable优化,时间复杂度可以降到O(1)]
}
四、判断当前扩展出的节点是否在另外一个队列出现,也就是判断相交的函数:
int isintersect(i,j) //i为队列编号,j为当前节点在队列中的指针
{
遍历队列,判断是否存在【线性遍历的时间复杂度为O(N),如果用HashTable优化,时间复杂度可以降到O(1)]
}
Source Code
Problem: 1077 User: 297752873
Memory: 10984K Time: 16MS
Language: C Result: Accepted
Source Code
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define QueueSize 400000
int f[8]={1,2,6,24,120,720,5040,40320};
int dir[4]={-3,3,-1,1};
int visited[1000000],ans[1000000];
int found;
struct eight
{
char str[10];//目前的状态
int k;//空格的位置
int fa;//父节点,便于输出
int fx;//从上个结点过来的方向0上,1下,2左,3右
}s[1000000],b;//所有的标志,以及存储刚开始的状态
int get(char str[]) //计算全排列哈希值
{
int i, j;
int ret = 0, cnt;
for(i = 1; i < 9; ++i)
{
cnt = 0;
for(j = 0; j < i; ++j)
if(str[j] > str[i])
cnt++;
ret += cnt*f[i-1];
}
return ret;
}
typedef struct
{
int front;
int rear;
int count;
int data[1000000];
}CirQueue;
CirQueue qs,qe;//使用队列,队列里保存的是各状态的下标
void InitQueue(CirQueue *Q)//建队
{
Q->front=Q->rear=0;
Q->count=0;
}
int QueueEmpty(CirQueue *Q)//判队是否空
{
return Q->count==0;
}
void EnQueue(CirQueue *Q,int x)//入队
{
Q->count++;
Q->data[Q->rear]=x;
Q->rear=(Q->rear+1)%QueueSize;
}
int DeQueue(CirQueue *Q)//出队,并删除队首元素
{
int temp;
temp=Q->data[Q->front];
Q->count--;
Q->front=(Q->front+1)%QueueSize;
return temp;
}
void print(int a,int b,int flag,int fx)//a是正向左边的点,b是反向右边的点,不相连 ,flag时是正向搜到的
{
int top=0;
if(flag)//正向搜索时就将要输出的那个方向放到栈里
{
ans[top++]=fx;
while(a!=s[a].fa)
{
ans[top++]=s[a].fx;
a=s[a].fa;
}
while(top--)
{
switch(ans[top])
{
case 0:printf("u");break;
case 1:printf("d");break;
case 2:printf("l");break;
case 3:printf("r");break;
default:break;
}
}
while(b!=s[b].fa)
{
switch(s[b].fx)
{
case 0:printf("d");break;
case 1:printf("u");break;
case 2:printf("r");break;
case 3:printf("l");break;
default:break;
}
b=s[b].fa;
}
}
else {//方向搜索时就将下一个方向反向直接输出
while(a!=s[a].fa)
{
ans[top++]=s[a].fx;
a=s[a].fa;
}
while(top--)
{
switch(ans[top])
{
case 0:printf("u");break;
case 1:printf("d");break;
case 2:printf("l");break;
case 3:printf("r");break;
default:break;
}
}
switch(fx)
{
case 0:printf("d---");break;
case 1:printf("u");break;
case 2:printf("r");break;
case 3:printf("l");break;
default:break;
}
while(b!=s[b].fa)
{
switch(s[b].fx)
{
case 0:printf("d");break;
case 1:printf("u");break;
case 2:printf("r");break;
case 3:printf("l");break;
default:break;
}
b=s[b].fa;
}
}
printf("
");
}
void BFS_expand(CirQueue *Q,int flag)
{
int ss=DeQueue(Q),i,x,k;//ss是队首元素
char a[10],temp;
for(i=0;i<4;i++)
{
if(i==0&&s[ss].k<3)continue;
if(i==1&&s[ss].k>=6)continue;
if(i==2&&s[ss].k%3==0)continue;
if(i==3&&s[ss].k%3==2)continue; //因为这个本应二维存储的图案,要处理好边缘问题,这里纠结了好久
k=s[ss].k+dir[i];//方向
if(k<0||k>8) continue;//如果超出范围,就找寻下个结点
strcpy(a,s[ss].str);
temp=a[k];a[k]=a[s[ss].k];a[s[ss].k]=temp; // 获取子节点的状态
x=get(a);//子节点的哈希值
if(flag) // 在正向队列中判断
{
if(visited[x]!=1)// 没在正向队列出现过
{
if(visited[x]==2) // 该状态在反向队列中出现过
{
print(ss,x,flag,i);
found=1;
return ;
}
visited[x]=1; // 标记为在在正向队列中
s[x].k=k;
s[x].fx=i;
strcpy(s[x].str,a);
s[x].fa=ss;
EnQueue(Q,x);// 入队
}
}
else // 在反向队列中判断
{
if (visited[x]!=2)// 没在反向队列出现过
{
if(visited[x]==1) // 该状态在正向队列中出现过
{
print(x,ss,flag,i);
found=1;
return ;
}
visited[x]=2; // 标记为在在反向队列中
s[x].k=k;
s[x].fx=i;
strcpy(s[x].str,a);
s[x].fa=ss;
EnQueue(Q,x);// 入队
}
}
}
}
void DBFS()//双广
{
int x=get(b.str),y=get("123456780"); //x是正向,输入的,y则是目的状态,固定的
if(strcmp(b.str,"123456780")==0)
{
printf("lr
");
found=1;
return ;
}
memset(visited,0,sizeof(visited)); // 判重数组,初始化为0
InitQueue(&qs);
InitQueue(&qe);
//======正向扩展的状态标记为1,反向扩展标记为2
visited[x]=1; // 初始状态标记为1
visited[y]=2; // 结束状态标记为2
s[x].k=b.k;strcpy(s[x].str,b.str);s[x].fa=x;
s[y].k=8;strcpy(s[y].str,"123456780");s[y].fa=y;
EnQueue(&qs,x);// 初始状态入正向队列
EnQueue(&qe,y);// 结束状态入反向队列
while(!QueueEmpty(&qs)&&!QueueEmpty(&qe))
{
if(qs.count>qe.count)//队中结点少的先行,可提高效率
BFS_expand(&qe,0); // 在正向队列中搜索
else BFS_expand(&qs,1);
if(found) // 搜索结束
return ;
}
while(QueueEmpty(&qs)==0)
{
BFS_expand(&qs,1);
if(found)return;
}
while(0==QueueEmpty(&qe))
{
BFS_expand(&qe,0);
if(found)return;
}
}
int main()
{
int i,m,j;
char a[100];
gets(a);
j=0;
for(i=0;i<strlen(a);i++)
{
if(a[i]==' ')continue;
if(a[i]=='x')
{
a[j]='0';
m=j;
j++;
}else a[j++]=a[i];
}
a[j]=' ';
strcpy(b.str,a);
b.k=m;
found=0;//标志变量初始化
//printf("m=%d %s
",b.k,b.str);
DBFS();
if(found==0)
printf("unsolvable
");
//system("pause");
return 0;
}