题目传送门
矩阵中经典的宽搜题,这里我主要是想讲哈希表(散列表)的应用而已。
下面几个知识点都有大神讲解过,有兴趣的同学,请认真阅读,如果你懒得看,我后面也会简单讲解。
关于sprintf()函数的用法
关于哈希表的用法
关于双向搜索
题目大意:
1、一个3*3的矩阵,给出开始状态和结束状态,请您推箱子(0是那个箱子)
2、输出推箱子的步数
解题思路:
1、这是宽搜入门的经典题,经典程度可以媲美 深搜的八皇后问题
2、首先,要熟悉掌握字符串和int 类型之间的自由切换,这里用了一个 sprintf()函数,就是把数字,输出到字符串中。
3、宽搜的框架,也是必要的先决条件;
4、这题最难的地方是判重,在普通地图中,我们用一个bool 数组表示这个点是否到达过,用O(1)的时间就可以判重;
但这题如果我开bool 数组,用9位数作为数组下标,要开一个 规模 10 亿的数组,空间爆了~
仔细观察发现,这个10 亿的数组,很多空间我们是用不到的(因为每个数字每次只能出现1次),如果用离散化的思维:
我们可以把空间压缩在100W内,那么数组就不爆空间了,yeah!
5、那么问题来了:怎么压?
直接mod 100W~(就是这个暴力,这就是本题hash函数的核心)
6、那么问题又来了,大规模缩小的话,mod完了,还是会有重复,怎么办?
本题用一个 next数组 来做链表,就搞定了。
额,哈希的简单应用,并没有那么难。
上代码1:(这是单搜的代码,后面是双搜的代码)
#include
#include
const int mx=1000005,inf=1000003;
int t[mx];
int h[mx];//h的下标就是(哈希值),h数组存的是(这个哈希值,最后一次在队列中出现的位置)
int next[mx];//next的下标是(队列中的位置),值是(哈希值相同的上一个位置);
int s,e=123804765;
char st[11],ed[11];
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
char l[mx][11];//队列
int hash(int x)//求队列中(x位置)的 哈希值
{
int su=0;
for(int i=0;i<9;i++)
{
su=su*10+(l[x][i]-'0');
}
return su%inf;//就是直接暴力 mod
}
int pd(int x)//判断 (现在的x位置)是否在队列中出现过
{
int s=hash(x);//求出 哈希值
int k=h[s];//看看哈希值 对应位置的 指针
while(k>0)// k表示 队列中(以前出现过 这个值 )的位置
{
if(strcmp(l[k],l[x])==0) return 0;//重复了
k=next[k];//没重复,链表的跳跃
}
next[x]=h[s];//记录新的 (最后一个链表位置)
h[s]=x; // h数组也更新
return 1;
}
int bfs()// 宽搜的框架,不懂请点右上角红叉
{
int tou=1,wei=2;
t[1]=0;// t数组记录步数(本题是从 0 开始)
memset(h,0,sizeof(h));
s=hash(1); h[s]=1;//哈希数组 初始化
while(tou=0&&nx<3&&ny>=0&&ny<3)
{
strcpy(l[wei],l[tou]); //推出新的状态
l[wei][nk]=l[tou][k];
l[wei][k]=l[tou][nk];
t[wei]=t[tou]+1; //步数+1
if(strcmp(l[wei],ed)==0) return t[wei];//到终点了
if(pd(wei)>0) wei++;//判重,如果符合,就进队
}
}
tou++;
}
return 0;
}
int main()
{
scanf("%s",l[1]); //直接将开始状态读到队列上
sprintf(ed,"%d",e);//结束状态转换为 字符串,方便匹配
if(strcmp(l[1],ed)==0) { printf("0\n");return 0; }//特判 0 步 的状态
printf("%d\n",bfs());
return 0;
}
双向搜索要多开个数组记录方向,我用f 数组,0表示从st出发,1表示从ed 出发,判重函数作了修改:
情况1:新点与队列中某点重复+两点的方向不同=两条路交叉了,答案!
情况2:新点与队列中某点重复+两点的方向相同=这个点以前来过,重复走,没意义~
情况3:新点与队列中无点重复=新加进队列中。
代码2:双向宽搜(理论上,节省一半的运算量)
#include
#include
const int mx=1000005,inf=1000003;
int h[mx],nx[mx],t[mx],f[mx];
char l[mx][11];
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int hash(int x)//暴力求哈希值
{
int ans=0;
for(int i=0;i<9;i++) ans=ans*10+(l[x][i]-'0');
return ans%inf;
}
int pd(int x,int fa) //fa是父亲节点,需要知道方向和步数
{
int xx=hash(x);
int k=h[xx];
if(k>0)//说明有人有相同的 哈希值
{
if(strcmp(l[x],l[k])==0)//x 与 k 重复
{
if(f[fa]==f[k]) return -1;// 情况3:自己方向走的重复路:没事情发生
return t[fa]+t[k]+1; //情况1:不是同边来的相遇,是交汇处,答案有了
}
k=nx[k];
}
nx[x]=h[xx]; h[xx]=x; //情况2:无重复,新进队
return 0;
}
int bfs()
{
memset(h,0,sizeof(h));
int tou=1,wei=3;
t[1]=t[2]=0;
f[1]=0;f[2]=1;//判断该点来的方向
int s1=hash(1); h[s1]=1;
int s2=hash(2); h[s2]=2;
while(tou=0&&nx<3&&ny>=0&&ny<3)
{
int nz=nx*3+ny;
strcpy(l[wei],l[tou]);
l[wei][z]=l[tou][nz];l[wei][nz]=l[tou][z];
int su=pd(wei,tou); //这里是双搜的核心,用到父亲节点的信息
if(su>0) return su;//如果不同边来的,重合:有答案了
if(su==0) //如果同边来的,不重复:可以进队
{
f[wei]=f[tou]; t[wei]=t[tou]+1;wei++;
}
}
}
tou++;
}
}
int main()
{
scanf("%s",l[1]);strcpy(l[2],"123804765");
if(strcmp(l[1],l[2])==0) { printf("0\n");return 0; }
printf("%d\n",bfs());
return 0;
}