BFS与DFS与IDA*

九宫重排-蓝桥杯

这是一个典型的BFS基础题目。关于这道题目,我想记录的并不是如何写BFS,而是如何判断题目需要使用BFS求解。同时,类比另外的两种搜索方式进行的思考。

总而言之,今天分析的主题是:BFS与DFS与迭代加深三种搜索方式的异同点。(下文中,对于其他的搜索方式一概不论。)

先说这道题目吧。最开始审题的时候,有几点引起了我的关注:

1、“已知九宫的初态和终态”: 说明这是搜索问题

2、“最少经过多少步”: 这可以排除DFS,只有BFS与迭代加深可以办到

3、“如果无论多少步都无法到达,则输出-1”:这是我的疑惑点????????

之所以有第三个的疑惑,是因为我曾经认为全局的9!种可能都是可以推到出来的。但是我错了(lll¬ω¬)

经过我的考虑,我一开始写了一个迭代加深搜索,但是我没有办法解决-1的输出问题。代码如下:

  1 #include<cstring>
  2 #include<cstdio>
  3 using namespace std;
  4 int maxd;
  5 bool okdir[9][4];//[位置][方向]=是否可行
  6 int todir[4]={1,-1,3,-3};
  7 char b[10];//初始状态
  8 const int dx[]={0,0,1,-1};
  9 const int dy[]={1,-1,0,0};
 10 bool solved=false;
 11 int vis[9];
 12 /*
 13 这里借鉴了紫书里的相似题目
 14 使用了ID映射法来降维和操作
 15 */
 16 int initial()//初始化okdir
 17 {
 18     for(int i=0;i<9;i++) 
 19     {
 20         int x=i/3,y=i%3;
 21         for(int k=0;k<4;k++)
 22         {
 23             int px=x+dx[k],py=y+dy[k];
 24             if(px>=0&&py>=0&&px<3&&py<3)okdir[i][k]=true;
 25             else okdir[i][k]=false;
 26         }
 27     }
 28     return 0;
 29 }
 30 
 31 char aim[10];//目标状态
 32 int diff(char* A)
 33 {
 34     int num=-1;//如果存在n处不同,那么最好可以走n-1步就能完成 
 35     for(int i=0;i<9;i++)
 36     {
 37         if(A[i]!=aim[i])num++;
 38     }
 39     //printf("%s
",A);
 40     return num;
 41 }
 42 
 43 bool bfs(char* A,int point,int nowstep)//point是空格所在的以一维位置坐标 
 44 {
 45     int p=diff(A);
 46     if(p==-1){solved=true;return true;}
 47     if(p+nowstep>maxd)return false;
 48     //
 49     for(int i=0;i<4;i++)
 50     {
 51         if(!okdir[point][i])continue;
 52         int aimpoint=point+todir[i];
 53         A[point]=A[aimpoint];
 54         A[aimpoint]='.';
 55         //printf("psdd->%s
",A);
 56         if(dfs(A,aimpoint,nowstep+1))return true;
 57         A[aimpoint]=A[point];
 58         A[point]='.';
 59     }//对每一个方向进行调试
 60 /*这里产生了巨大的重复,因为没有记录遍历过的状态
 61 这一点在最开始没有考虑到实在是不熟练导致的!!
 62 */
 63     return false;
 64 }
 65 
 66 int main()
 67 {
 68     initial();
 69     scanf("%s",b);
 70     b[9]='';
 71     scanf("%s",aim);
 72     int pointer;
 73     for(int i=0;i<9;i++)
 74     {
 75         if(b[i]=='.')
 76         {
 77             pointer=i;
 78             break;
 79         }
 80     }
 81     //printf("b->%s
aim->%s
point->%d
",b,aim,pointer);
 82     //return 0;
 83     maxd=0;
 84     while(1)
 85     {
 86         if(maxd>20)
 87         {
 88             printf("-1
");
 89             return 0;
 90         }//这是后来加的,是为了蒙混过始终超时33%的OJ
 91         if(bfs(b,pointer,0))break;
 92         maxd++;
 93     }
 94     if(solved)printf("%d
",maxd);
 95     else printf("-1
");
 96     return 0;
 97 }
 98 
 99 /*
100 0 1 2
101 3 4 5
102 6 7 8
103 12345678. //位置上的信息 
104 012345678//位置 
105 */
Error Code

接下来我看了一些讲解,其中有这样的讲解——为什么要BFS?

① 从题目中可以看到 "开始状态" 和 "最终状态" 的字眼,并且从一个状态转移到另外的一个状态需要执行一次操作,最终需要求解的是从一个状态转移到另外一个状态需要执行的最少次数,
这些都是bfs能够解决问题的典型特征,所以我们可以使用bfs来进行解决,这道题目也类似于走出迷宫的最少步数的问题,除了使用bfs来进行,对于这种可能性不太确定的问题, 我们还可以使用深度优先搜索来进行解决,但是使用深搜解决时间复杂度可能更高 ② bfs首先要有一个队列,所以我们可以使用声明一个队列,首先需要解决的是队列的初始化的问题,因为对于bfs解决的问题来说,队列中的初始状态是核心,它的下一个状态就是由上一个状态经过
一次操作得到的,所以来说首先解决队列的初始化问题,由题目中可以知道,九宫格中能够将空格与空格相邻的数字进行交换,所以我们需要知道的有如下几方面的信息 a:当前空格的位置在什么地方 b:与空格相邻的的位置

对搜索特点的不清晰和对题目情景理解不彻底是造成这次失误的主要原因。

改良后的代码是:

 1 #include<cstring>
 2 #include<cstdio>
 3 #include<string>
 4 #include<queue>
 5 #include<iostream>
 6 #include<set>
 7 using namespace std;
 8 struct node
 9 {
10     string state;
11     int step;
12 };
13 bool okdir[9][4];
14 int todir[]={1,-1,3,-3};
15 const int dx[]={0,0,1,-1};
16 const int dy[]={1,-1,0,0};
17 
18 int initial()
19 {
20     for(int i=0;i<9;i++) 
21     {
22         int x=i/3,y=i%3;
23         for(int k=0;k<4;k++)
24         {
25             int px=x+dx[k],py=y+dy[k];
26             if(px>=0&&py>=0&&px<3&&py<3)okdir[i][k]=true;
27             else okdir[i][k]=false;
28         }
29     }
30     return 0;
31 }
32 
33 string bim;
34 string aim;
35 set<string> cyc;
36 queue<node> psd;
37 int bfs()
38 {
39     node p;
40     p.state=bim;
41     p.step=0;
42     psd.push(p);
43     cyc.insert(bim);
44     //
45     while(!psd.empty())
46     {
47         p=psd.front();psd.pop();
48         int point;
49         string A=p.state;
50         for(int i=0;i<A.size();i++) if(A[i]=='.'){point=i;break;}
51         //
52         for(int i=0;i<4;i++)
53         {
54             if(!okdir[point][i])continue;
55             int aimpoint=point+todir[i];
56             A[point]=A[aimpoint];
57             A[aimpoint]='.';
58             //cout<<A<<endl;
59             //
60             p.state=A; p.step++;
61             if(A==aim){printf("%d
",p.step);return 0;}
62             if(cyc.find(A)==cyc.end()){psd.push(p);cyc.insert(A);}
63             A[aimpoint]=A[point];
64             A[point]='.';
65             p.step--;
66         }
67     }
68     printf("-1
");
69     return 0;
70 }
71 
72 int main()
73 {
74     initial();
75     cin>>bim;
76     cin>>aim;
77     if(aim==bim)
78     {
79         printf("0
");
80         return 0;
81     }
82     //
83     bfs();
84     return 0;
85 }
View Code

那么,三大搜索的特点与使用条件是什么呢?

1、https://blog.csdn.net/github_38818603/article/details/81288659

2、DFS和BFS的时间复杂度是相同的

3、优缺点

深搜优缺点
优点
1、能找出所有解决方案
2、优先搜索一棵子树,然后是另一棵,所以和广搜对比,有着内存需要相对较少的优点
缺点
1、要多次遍历,搜索所有可能路径,标识做了之后还要取消。
2、在深度很大的情况下效率不高

广搜优缺点
优点
1、对于解决最短或最少问题特别有效,而且寻找深度小
2、每个结点只访问一遍,结点总是以最短路径被访问,所以第二次路径确定不会比第一次短
缺点
3、内存耗费量大(需要开大量的数组单元用来存储状态)

以上是DFS与BFS的对比。


我(博主)个人对这个搜索的理解就是以BFS的思想写DFS。
具体来说就是,首先深度优先搜索k层,若没有找到可行解,再深度优先搜索k+1层,直到找到可行解为止。由于深度是从小到大逐渐增大的,所以当搜索到结果时可以保证搜索深度是最小的。这也是迭代加深搜索在一部分情况下可以代替广度优先搜索的原(还比广搜省空间)。
原文链接:https://blog.csdn.net/hzaukotete/article/details/81226556

迭代加深(Iterative deepening)搜索,实质上就是限定下界的DFS。
在迭代加深搜索的算法中,连续的深度优先搜索被引入,每一个深度约束逐次加1,直到搜索到目标为止。
迭代加深搜索算法就是仿广度优先搜索的深度优先搜索。既能满足深度优先搜索的线性存储要求,又能保证发现一个最小深度的目标结点。从实际应用来看,迭代加深搜索的效果比较好,并不比广度优先搜索慢很多,但是空间复杂度却与深度优先搜索相同,比广度优先搜索小很多,在一些层次遍历的题目中,迭代加深不失为一种好方法!
 
 
以上是其他思想。都认为迭代加深搜索是一个加了限制的DFS。
OK
P.S.我总感觉我的IDA*解法能够在添加一些标志量和set判重后完成运行求解。但是那样似乎又和普通的BFS没太大的区别。反而变得“人不人鬼不鬼”的似了。
原文地址:https://www.cnblogs.com/savennist/p/12427743.html