BFS.2
前言:bfs的证明
- 为什么我们可以用bfs来求出边权相同的单源最短路
- 首先,队列有两个性质:
- 两端性:队列当中只【且最多】存在距离起点x和x+1的点
- 单调性:队列里面的点一定先是x才到x+1
- 证明:起点是sx,sy
- 同时把x的点扩展之后,只会增加x+1的距离,所以说,队列当中两端性和单调性不变
- 所有点只会入队一次,这时候一定是最小值,且永远不会改变
- 假设:当前所有出队的元素最小值已经确定
- 当且x值不是最小值【x可被队列当中某个点来进行更新】
- 这时候我们知道,剩下的点要么还没有入队,要不然就已经在队列当中
- 此时在由队列当中扩展出来的值一定不会严格单调小于这个最小值
【证明方法】:数理归纳,反证法
1.多元BFS
- 如果说求所有点到一堆起点的最短路,我们可以使用“虚拟原点”,然后由虚拟原点向起点连一条边权为0的边
- 在bfs中不需要
- 也就是在队列的第一层先存所有都是起点的点
- 我们就一定要利用两端性,单调性,以及入队确定性来证明其正确性就可【也就是放在第一层和st数组一定能干出来最小值】
2.双端队列操作
- 某些队列需要在队头进行插入
- 状态的存取:哈希法/康托展开
- c++11就可以map
- 这一场有点艰难需要重点说明和总结:
-
#include <cstdio> #include <algorithm> #include <cstring> #include <queue> #include <iostream> #include <unordered_map>//作为unorder——map如果出现错误就需要先调试编译环境,注意头文件转到工具 - >编译选项 - >代码生成 /优化- >代码生成->语言标准(-std) 选择GNU C ++ 11。 using namespace std; char g[3][4]; queue<string> q; unordered_map<string,int> dis; unordered_map< string , pair<char,string> > pre;//这里要注意理解,map的本意就是将数组类型数和数的对应关系转化为不同类型元素的对应关系,换一种说法,我们用string作为下标存了一个pair型的数据,第一个char用来存储操作,第二个用来回溯 void set(string state) { //cout<<state<<endl; for(int i=0;i<4;i++) g[0][i]=state[i]; for(int i=7,j=0;j<4;i--,j++)g[1][j]=state[i]; }//注意类型转化,对于这种需要字符串或者hash来表示状态的题目,我们一般需要一个set和get函数来实现转化和得到状态 //还要注意,一定要小心有没有返回值,否则就会报错 string get() { string res; for(int i=0;i<4;i++) res+=g[0][i]; for(int i=3;i>=0;i--) res+=g[1][i]; return res; } string move0(string state) { //printf("2323\n"); set(state); for(int i=0;i<4;i++) swap(g[0][i],g[1][i]); //for(int i=0;i<4;i++) cout<<g[1][i]; return get(); } string move1(string state) { set(state); int v0=g[0][3],v1=g[1][3]; for(int i=3;i>=0;i--) { g[0][i]=g[0][i-1]; g[1][i]=g[1][i-1]; } g[0][0]=v0;g[1][0]=v1; return get(); } string move2(string state) { set(state); int v=g[0][1]; g[0][1]=g[1][1]; g[1][1]=g[1][2]; g[1][2]=g[0][2]; g[0][2]=v; return get(); } void bfs(string start,string end) { if(start==end) return; q.push(start); //memset(dis,-1.sizeof(dis)); dis[start]=0; while(!q.empty()) { string t=q.front();//×¢Òâstack²ÅÓÐfront q.pop(); string m[3];//在bfs直接创建三种对应的状态,防止重复覆盖导致错误 m[0]=move0(t); //printf("34324\n"); m[1]=move1(t); m[2]=move2(t); //for(int i=0;i<3;i++)cout<<m[i]<<endl; for(int i=0;i<3;i++) { string str=m[i]; if(dis[str]==0) { dis[str]=dis[t]+1; pre[str]={char('A'+i),t};//注意:理解好pre后,如果需要输出方案的话,一定要记得更新的时候不仅仅是更新距离 if(str==end) break;//判断搜索终止 q.push(str); } } } }//这里应该写成有返回值的来进行剪枝 int main() { int x; string start,end; for(int i=0;i<8;i++) { scanf("%d",&x); end+=char(x+'0');//end+的意思其实就是在相应的end[i]里面塞了一个字符 } for(int i=0;i<8;i++)start+=char(i+'1'); bfs(start,end); printf("%d\n",dis[end]); string res; while(end!=start)//»ØËÝ { res+=pre[end].first; end=pre[end].second;//回溯上一个状态 } reverse(res.begin(),res.end());//翻转整个res字符串 cout<<res; return 0; }
真是恐怖呢
- 先开框架,再填血肉,注意返回值,循环变量,范围,细节等等
4.双端队列:
- 只要能够转化成dij算法,那么一定就是正确的
- 边权为0和1的时候,我们尝试每次从队头取,然后如果扩展出来的边权是0,那么就把它放在队头,x+1放在队尾
- 那么这样就可以保证两端性和单调性
- 本质仍是dij算法
- 本题需要引入一种新的转化思想:
- 首先分析题目性质:为什么是斜着走,说明有些点我们这辈子都到达不了
- 那么我们就知道了,我们不可能出现交叉的路径,每一个路径最多被旋转一次
- 换一种说法,原本路径的边权是0,需要旋转才能到达的两点边权是1,
- 那么这样,就转化成01双端队列问题
- 但是要注意:每个点可能会更新多次,也就是特殊的dij算法
- 在实现上面的细节:
- 注意使用deque的话就要用push_back,pop_back,..
- 剩下的就是点阵上面点和格子的坐标转化就好了
浙公网安备 33010602011771号