用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-12-12 16:55:26

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

如何实现图的BFS和DFS

图的存储结构 本文的重点在于图的深度优先搜索(DFS)和广度优先搜索(BFS),因此不再对图的基本概念做过多的介绍,但是要先大致了解下图的几种常见的存储结构. 邻接矩阵 邻接矩阵既可以用来存储无向图,也可以用来存储有向图.该结构实际上就是用一个二维数组(邻接矩阵)来存储顶点的信息和顶点之间的关系(有向图的弧或无向图的边).其描述形式如下: //图的邻接矩阵存储表示 #define MAX_NUM 20 // 最大顶点个数 enum GraphKind{GY,GN}; // {有向图,无向图} t

怎么解决TP-LINK无线网卡搜索不到信号

  解决TP-LINK无线网卡搜索不到信号的情况一.确认驱动程序安装成功 可能原因:无线网卡驱动未安装成功. 解决方法:在电脑桌面的 计算机 或 我的电脑,右键 点击并选择 管理,点击 设备管理器 >> 网络适配器,可以查看到无线网卡的驱动程序是否安装成功.如下图红框所示,即表示网卡驱动安装成功.如果在网卡前端图标 上出现黄色感叹号或问号,即表示驱动未安装成功,需要重新安装驱动. 解决TP-LINK无线网卡搜索不到信号的情况二.确认客户端软件安装正确 可能原因:没有使用无线网卡配套的客户端软件

时间复杂度-用DFS解决迷宫寻路问题的时间与空间复杂度

问题描述 用DFS解决迷宫寻路问题的时间与空间复杂度 一个二维迷宫,判断一个人是否能从(0,0)要走到某个值为9的点,迷宫用2D数组表示,1为路,0为墙.请问以下代码的时间复杂度和空间复杂度?我有点绕晕了算不出来,谢谢! public boolean find(int[][] grid){ if(grid==null || grid.length==0 || grid[0].length==0 || grid[0][0]==0) return 0; return helper(grid,0,0)

BFS和DFS的路径输出

DFS 其实就是一直顺着一个方向不断的搜索直到找到了目标为止.路径输出的时候,利用记录前面的点即可. 举例: #include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; #define N 9 int cnt; struct Point{ int x; int y; }path[N*N]; int maze[N][N];/*保存地

poj 1724ROADS(bfs和dfs做法)

/* dfs比较好想,就是测试数据的问题,导致在遍历边的时候要倒着遍历才过! */ #include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> #define Max 0x3f3f3f3f using namespace std; struct node{ int D; int L, T; node(int D, int L,

解决win8.1搜索框经常自动关闭问题

具体操作步骤如下: 1.如果安装了插件或软件导致的删除即可等; 2.如果不是我们可以尝试进行这一步的操作,具体的操作:"Windows+R"输入"rstrui.exe"还原一下系统; 3.到相同系统的计算机中拷贝TQUERY.DLL到C:Windowssystem32,并且按"Windows+X"命令提示符管理员,输入"regsvr32 TQUERY.DLL"回车. 4.如果以前没有问题后来安装了软件导致的我们可以尝试把软件删

求高手解决Lucene索引搜索,为啥名字可以但是换成价格区间就不可以了啊

问题描述 publicPaginationsearchPage(Directorydir,StringqueryString,LongwebId,LongctgId,LongstoreId,DoublebeginPrice,DoubleendPrice,Datestart,Dateend,intpageNo,intpageSize)throwsCorruptIndexException,IOException,ParseException{Searchersearcher=newIndexSea

图论中DFS与BFS的区别、用法、详解?

  DFS与BFS的区别.用法.详解? 写在最前的三点: 1.所谓图的遍历就是按照某种次序访问图的每一顶点一次仅且一次. 2.实现bfs和dfs都需要解决的一个问题就是如何存储图.一般有两种方法:邻接矩阵和邻接表.这里为简单起 见,均采用邻接矩阵存储,说白了也就是二维数组. 3.本文章的小测试部分的测试实例是下图:   一.深度优先搜索遍历 1.从顶点v出发深度遍历图G的算法 ① 访问v ② 依次从顶点v未被访问的邻接点出发深度遍历. 2.一点心得:dfs算法最大特色就在于其递归特性,使得算法代

[LeetCode] Binary Tree Level Order Traversal 二叉树层次遍历(DFS | BFS)

目录:1.Binary Tree Level Order Traversal - 二叉树层次遍历 BFS 2.Binary Tree Level Order Traversal II - 二叉树层次遍历从低往高输出 BFS 3.Maximum Depth of Binary Tree - 求二叉树的深度 DFS4.Balanced Binary Tree - 判断平衡二叉树 DFS5.Path Sum - 二叉树路径求和判断DFS 题目概述:Given a binary tree, return