用BFS和DFS解决圆盘状态搜索问题

人工智能课程的实验(我的解法其实更像是算法课程的实验)

用到的算法:深度优先搜索、宽度优先搜索(状态扩展的不同策略)

数据结构:表示状态的结构体、多维数组

(可能是最近做算法竞赛题的影响,这次并不像以前那样依赖类和面向对象了,而是用最简单(几乎没有封装)的数据表示方法和大量的全局变量来存储数据,用面向过程的写法,以快速解决某一问题为目的设计程序。安全性和可扩展性势必降低,有些技巧的使用也让代码变得难懂;但是代码简洁,节省运行的时间和空间开销,这应该就是算法竞赛更加看重的吧)

这次用了C++写了控制台版,打印状态三元组;又用C#写了图形界面版,打印出圆盘的状态。

二者的算法是完全一致的,执行结果也一致,只是语法和输出的一些处理不同。

下面是运行结果截图,以深度优先为例

 

下面以C++版为例介绍实现

结构体和全局变量的定义

1 struct State
2 2 {
3 3     int a, b, c;//表示圆盘朝南方向的数字
4 4     State(){}
5 5     State(int na, int nb, int nc) :a(na), b(nb), c(nc){}
6 6 };
7 7
8 8 int vis[5][5][5];//用来记录每个状态是否被访问过
9 9 int num;//用来记录第几个状态

DFS(用栈实现)

 1 int dfs()
 2 {
 3     stack<State> sta;
 4     sta.push(State(4,4,4));
 5     vis[4][4][4] = 1;
 6     while (sta.size())
 7     {
 8         State cur = sta.top();
 9         sta.pop();
10         cout << ++num << ". ("<<cur.a<<","<<cur.b<<","<<cur.c<<")"<<endl;
11         if (cur.a == 1 && cur.b == 4 && cur.c == 3)
12             return 1;//命中
13         if (cur.c - 1>0 && vis[cur.a][cur.b][cur.c-1] == 0)
14         {
15             vis[cur.a][cur.b][cur.c-1] = 1;
16             sta.push(State(cur.a, cur.b, cur.c-1));
17         }
18         if (cur.b - 1>0 && vis[cur.a][cur.b-1][cur.c] == 0)
19         {
20             vis[cur.a][cur.b-1][cur.c] = 1;
21             sta.push(State(cur.a, cur.b-1, cur.c));
22         }
23         if (cur.a - 1>0 && vis[cur.a-1][cur.b][cur.c] == 0)
24         {
25             vis[cur.a-1][cur.b][cur.c] = 1;
26             sta.push(State(cur.a-1, cur.b, cur.c));
27         }
28     }
29     return 0;
30 }

BFS(用队列实现)

 1 int bfs()
 2 {
 3     queue<State> que;
 4     que.push(State(4, 4, 4));
 5     vis[4][4][4] = 1;
 6     while (que.size())
 7     {
 8         State cur = que.front();
 9         que.pop();
10
11         cout << ++num << ". ("<<cur.a<<","<<cur.b<<","<<cur.c<<")"<<endl;
12         if (cur.a == 1 && cur.b == 4 && cur.c == 3)
13             return 1;//命中
14         if (cur.a - 1>0 && vis[cur.a - 1][cur.b][cur.c] == 0)
15         {
16             vis[cur.a-1][cur.b][cur.c] = 1;
17             que.push(State(cur.a-1, cur.b, cur.c));
18         }
19         if (cur.b - 1>0 && vis[cur.a][cur.b - 1][cur.c] == 0)
20         {
21             vis[cur.a][cur.b-1][cur.c] = 1;
22             que.push(State(cur.a, cur.b-1, cur.c));
23         }
24         if (cur.c - 1>0 && vis[cur.a][cur.b][cur.c - 1] == 0)
25         {
26             vis[cur.a][cur.b][cur.c-1] = 1;
27             que.push(State(cur.a, cur.b, cur.c-1));
28         }
29     }
30     return 0;
31 }

  你会发现DFS和BFS的不同仅在于对某一状态(节点)进行扩展时,其兄弟结点被暂存的顺序。这个顺序决定了节点扩展的顺序,即将“树”这样的“半线性结构”转化为“线性结构”的不同策略。

  因为这两种搜索策略都是从一开始就知道要对每一个未命中的节点进行怎样的处理,即搜索过程中产生的结果对搜索策略没有影响,所以这两种都属于“盲目式搜索”。相对而言,人工智能领域发展出“启发式搜索”,即在进行待扩展节点的选取时,要根据一个“估价函数”来选取“最有希望”的节点分支进行下一步扩展。

老师的习题解析中对DFS进行了改进,在扩展某个节点时判断的是它的子节点,这样可以尽早找到目标。个人认为这相当于在局部做了广度优先。

改进后的访问过的状态由17个降到5个,但其实加上判断过又未命中的兄弟节点,是降到了11个。如下

改进版DFS的代码

 1 int dfs_a()
 2 {
 3     stack<State> sta;
 4     sta.push(State(4,4,4));
 5     vis[4][4][4] = 1;
 6     while (sta.size())
 7     {
 8         State cur = sta.top();
 9         sta.pop();
10         cout << ++num << ". ("<<cur.a<<","<<cur.b<<","<<cur.c<<")"<<endl;
11
12         if (cur.c - 1>0 && vis[cur.a][cur.b][cur.c-1] == 0)
13         {
14             vis[cur.a][cur.b][cur.c-1] = 1;
15             if (cur.a == 1 && cur.b == 4 && cur.c-1 == 3)
16             {
17                 cout << ++num << ". ("<<cur.a<<","<<cur.b<<","<<cur.c-1<<")"<<endl;
18                 return 1;//命中
19             }
20             sta.push(State(cur.a, cur.b, cur.c-1));
21         }
22         if (cur.b - 1>0 && vis[cur.a][cur.b-1][cur.c] == 0)
23         {
24             vis[cur.a][cur.b-1][cur.c] = 1;
25             if (cur.a == 1 && cur.b-1 == 4 && cur.c == 3)
26             {
27                 cout << ++num << ". ("<<cur.a<<","<<cur.b-1<<","<<cur.c<<")"<<endl;
28                 return 1;//命中
29             }
30             sta.push(State(cur.a, cur.b-1, cur.c));
31         }
32         if (cur.a - 1>0 && vis[cur.a-1][cur.b][cur.c] == 0)
33         {
34             vis[cur.a-1][cur.b][cur.c] = 1;
35             if (cur.a-1 == 1 && cur.b == 4 && cur.c == 3)
36             {
37                 cout << ++num << ". ("<<cur.a-1<<","<<cur.b<<","<<cur.c<<")"<<endl;
38                 return 1;//命中
39             }
40             sta.push(State(cur.a-1, cur.b, cur.c));
41         }
42     }
43     return 0;
44 }

以上就是算法的内容。

关于C#图形界面的实现,也很简单。下面是显示每个圆盘状态的函数

 1 //打印状态函数
 2         private void print_circle(State st,int num)
 3         {
 4             Thread.Sleep(300);
 5             Graphics gra = this.groupBox1.Controls[num-1].CreateGraphics();//指定第num个圆盘显示
 6
 7             gra.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
 8
 9             Pen pen = new Pen(Color.Black);//画笔颜色
10
11             gra.DrawEllipse(pen, 0, 0, 100, 100);//画圆的方法,x坐标、y坐标、宽、高
12             gra.DrawEllipse(pen, 10, 10, 80, 80);
13             gra.DrawEllipse(pen, 20, 20, 60, 60);
14
15             Font font1 = new Font("", 7, FontStyle.Bold);
16             Font font2 = new Font("", 10, FontStyle.Bold);
17             Brush bush = new SolidBrush(Color.Red);//字填充的颜色
18             gra.DrawString(num + "", font2, bush, 0, 0);
19             gra.DrawString("A", font1, bush, 50, 20);
20             gra.DrawString("B", font1, bush, 50, 10);
21             gra.DrawString("C", font1, bush, 50, 0);
22             gra.DrawString(st.a+"", font1, bush, 50, 70);
23             gra.DrawString(st.b+"", font1, bush, 50, 80);
24             gra.DrawString(st.c+"", font1, bush, 50, 90);
25         }

关于界面的设计,预先放好足够多的picturebox,将它们放入一个groupbox里,然后根据传递进来的参数找特定的picturebox画图。

  在遍历groupbox时,我发现里面元素的顺序如果不是按顺序拖入的,会非常错乱。。。由于很菜鸟,我百度了很久都没有找到直接修改内部顺序的方法,只好打开groupbox的定义代码,调整添加元素函数的顺序才得以重排。

  以上是我对这个圆盘问题的求解。

  在检查实验时,老师说这个问题更通用的抽象是一个5个参数的“搜索机”,5个参数分别为:初始状态集合、目标状态集合、初始状态、目标状态、状态转移函数。(百度后发现好像图灵机的模型)这种方法需要在一开始生成所有可能状态。相比我的“动态生成状态”策略,这样势必会增加空间开销,但在搜索次数多时可以节省重复生成状态的时间,两种方法各有千秋吧。后一种方法更切合现在学习人工智能课的“状态空间搜索”思想。

时间: 2024-10-05 20:50:47

用BFS和DFS解决圆盘状态搜索问题的相关文章

搜索分析(DFS、BFS、递归、记忆化搜索)

搜索分析(DFS.BFS.递归.记忆化搜索) 1.线性查找 在数组a[]={0,1,2,3,4,5,6,7,8,9,10}中查找1这个元素. (1)普通搜索方法,一个循环从0到10搜索,这里略. (2)递归(从中间向两边) 1 //递归一定要写成记忆化递归 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool vis[11]; 5 int count1=0; 6 7 void search(int n){ 8 count1++; 9

深度优先搜索(DFS)与广度优先搜索(BFS)的Java实现

1.基础部分 在图中实现最基本的操作之一就是搜索从一个指定顶点可以到达哪些顶点,比如从武汉出发的高铁可以到达哪些城市,一些城市可以直达,一些城市不能直达.现在有一份全国高铁模拟图,要从某个城市(顶点)开始,沿着铁轨(边)移动到其他城市(顶点),有两种方法可以用来搜索图:深度优先搜索(DFS)和广度优先搜索(BFS).它们最终都会到达所有连通的顶点,深度优先搜索通过栈来实现,而广度优先搜索通过队列来实现,不同的实现机制导致不同的搜索方式. 1.1 深度优先搜索 深度优先搜索算法有如下规则: 规则1

论深度优先(DFS)和广度优先搜索(BF)的优点及不足(更新ing)

例题: POJ 1915 Knight Moves 骑士遍历问题(跳马问题) 在一个m*m的棋盘上,从任意一个给定的位置(sx , sy)出发,为象棋中的马找一条路通过最少的步数到达另一位置(ex ,ey),输出最少所需要的步数. 利用bfs求解. 当马在位置(x , y)的时候其后继节点(后继选择)是什么? 对于马,有八个方向可以选择,马可以跳到如下几个位置: (x+2 , y+1) , (x+1 , y+2 ) , (x-1 , y+2) , (x-2 , y+1), (x+2 , y -1

基于邻接矩阵和邻接表的两种方法实现无向图的BFS和DFS

广度优先搜索(Breadth-First-Search)和深度优先搜索(Deep-First-Search)是搜索策略中最经常用到的两种方法,特别常用于图的搜索. BFS的思想: 从一个图的某一个顶点V0出发,首先访问和V0相邻的且未被访问过的顶点V1.V2.--Vn,然后依次访问与V1.V2--Vn相邻且未被访问的顶点.如此继续,找到所要找的顶点或者遍历完整个图.我们采用队列来存储访问过的节点. DFS的思想: 深度优先搜索所遵循的策略就是尽可能"深"的在图中进行搜索,对于图中某一个

LeetCode刷题之BFS和DFS

广度优先搜索(BFS) 主要用于树的层序遍历或图的最短路径寻找,主要使用队列queue来完成. ①树的层序遍历:使用队列保存未被检测的结点,结点按照宽度优先的次序被访问和进出队. ②有向无环图的最短路径查找:由于有向无环图的某个节点的next节点可能会与另一个节点的next节点重复,所以我们需要记录已访问过的节点 //根节点与目标节点之间的最短路径长度 int BFS(Node root, Node target) { Queue<Node> queue; // 用来存放节点的队列 Set&l

HDU 2102 A计划 (BFS或DFS)

题意:中文题. 析:是一个简单的搜索,BFS 和 DFS都可行, 主要是这个题有一个坑点,那就是如果有一层是#,另一个层是#或者*,都是过不去的,就可以直接路过, 剩下的就是一个简单的搜索,只不过是两层而已,可能一个就是在#必须传送,这个题目已经说的很清楚了. 代码如下: BFS: #pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include <string>

BFS 和 DFS

BFS and DFS 一般来说,能用DFS解决的问题,都能用BFS.DFS容易爆栈,而BFS可以自己控制队列的长度.深度优先一般是解决连通性问题,而广度优先一般是解决最短路径问题. 广优的话,占内存多,能找到最优解,必须遍历所有分枝. 广优的一个应用就是迪科斯彻单元最短路径算法. 深优的话,占内存少,能找到最优解(一定条件下),但能很快找到接近解(优点),可能不必遍历所有分枝(也就是速度快), 深优的一个应用就是连连看游戏. 两个算法都是O(V+E),在用到的时候适当选取. A Tree is

HDU 1312 Red and Black(基础bfs或者dfs)

Red and Black Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 11843    Accepted Submission(s): 7380 Problem Description There is a rectangular room, covered with square tiles. Each tile is color

HDU 1983 Kaitou Kid - The Phantom Thief (2) bfs and dfs

Description 破解字迷之后,你得知Kid将会在展览开始后T分钟内盗取至少一颗宝石,并离开展馆.整个展馆呈矩形分布,划分为N*M个区域,有唯一的入口和出口(不能从出口进入,同样不能从入口出去).由某个区域可直接移动至相邻四个区域中的一个,且最快需要一分钟.假设Kid进入放有宝石的区域即可盗取宝石,无需耗时.问至少要封锁几个区域(可以封锁放有宝石的区域,但不能封锁入口和出口)才能保证Kid无法完成任务. Input 输入的第一行有一个整数C,代表有C组测试数据.每组测试数据的第一行有三个整