状态空间搜索,如果按专业点的说法就是将问题求解过程表现为从初始状态到目标状态寻找这个路径的过程。通俗点说,就是在解一个问题时,找到一条解题的过程可以从求解的开始到问题的结果(好象并不通俗哦)。由于求解问题的过程中分枝有很多,主要是求解过程中求解条件的不确定性,不完备性造成的,使得求解的路径很多这就构成了一个图,我们说这个图就是状态空间。问题的求解实际上就是在这个图中找到一条路径可以从开始到结果。这个寻找的过程就是状态空间搜索。
一、深度优先遍历(DFS:Depth-First-Search)
深度优先是按照一定的顺序查找完一个分支,再查找另外一个分支,直到找到目标为止。图一为一个二叉树深度遍历的例子。
图一 二叉树深度遍历实例
下面以一个实际的迷宫地图,来看一下深度优先算法在程序中如何实现。迷宫的布局如图二。
图二 迷宫地图
游戏规则是:
有一个起始点和终止点,分别位于图中的左上角和右下角。迷宫中白色方格是可通过区域,黑色方格是不可通过区域,迷宫以外的区域也是不可通过区域。
算法思想:
首先定义一些变量来记录游戏中的状态信息:
二维数组:block:记录迷宫的布局。0为可通过,1代表不可通过。
dx、dy:记录移动的方向:左、右、上、下四种情况下x、y坐标需要做的更改。
table二维数组:记录某个坐标上的方格是否达到过。
函数解释:
test:判断是否已经达到目标位置
actOK:判断当前移动方向是否合理,排除不合理的情况:越界、是否有障碍等等。
back:当前路径无法继续下去,执行回退操作,更改相应变量。
DFS:核心函数,执行深度优先遍历操作。在每一个方格点A处,接下来都有4种走法,针对4种方向,依次判断,如果第一种方向合理,则移动到下一格,进行相同的操作。如果继续下去不能走到终点,则最终回退到方格点A,继续第二种方向,重复操作。如果4中方向都无法达到目标,则搜寻失败。
具体代码(C++编程实现):
- //迷宫寻路:深度优先算法
- #include <iostream>
- using namespace std;
- const int width = 10;//宽度
- const int height = 10;//高度
- //四种移动方向(左、右、上、下)对x、y坐标的影响
- //x坐标:竖直方向,y坐标:水平方向
- int dx[4] = {0,0,-1,1};
- int dy[4] = {-1,1,0,0};
- //障碍表
- int block[height][width] = {
- 0,1,0,0,0,0,0,0,0,0,
- 0,1,1,0,1,1,1,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,
- 1,1,1,0,1,0,0,0,0,0,
- 0,1,0,0,1,0,1,1,1,0,
- 0,1,0,0,1,1,1,1,1,0,
- 0,0,0,1,1,0,0,0,1,0,
- 0,1,0,0,0,0,1,0,1,0,
- 0,1,1,1,0,1,1,0,1,1,
- 0,0,0,0,0,0,1,0,0,0,
- };
- const int maxLevels = 1000;//最大移动步数
- int maxAct = 4;//移动方向总数
- int table[width][height] = {0};//标记是否已达到
- int level = -1;//第几步
- int levelComplete = 0;//这一步的搜索是否完成
- int allComplete = 0;//全部搜索是否已完成
- int Act[maxLevels] = {0};//每一步的移动方向(1、2、3、4)
- int x = 0,y = 0;//现在的坐标
- int targetX = height - 1,targetY = width - 1;//目标坐标
- void test()//测试是否已达到目标
- {
- if (x == targetX && y == targetY)
- {
- levelComplete = allComplete = 1;
- cout << "Get to destination Success" << endl;
- }
- }
- int actOK()//判断移动方向是否合理
- {
- int nextX = x + dx[Act[level] - 1];
- int nextY = y + dy[Act[level] - 1];
- if (Act[level] > maxAct)//方向是否错误
- return 0;
- if(nextX >= height || nextX < 0)//x坐标是否越界
- return 0;
- if(nextY >= width || nextY < 0)//y坐标是否越界
- return 0;
- if(table[nextX][nextY] == 1)//是否已达到过
- return 0;
- if(block[nextX][nextY] == 1)//是否有障碍
- return 0;
- x = nextX;
- y = nextY;//移动
- table[nextX][nextY] = 1;//标记已达到
- return 1;
- }
- void back()
- {
- table[x][y] = 0;
- x -= dx[Act[level - 1] - 1];
- y -= dy[Act[level - 1] - 1];//回退到原来的坐标
- Act[level] = 0;//清除方向
- --level;//回到上一层
- }
- //深度优先搜索
- void DFS()
- {
- table[x][y] = 1;
- while(!allComplete)
- {
- ++level;//搜索下一步
- levelComplete = 0;//这一步的搜索还未完成
- while(!levelComplete)
- {
- ++Act[level];//改变移动方向
- if (actOK())//方向合理
- {
- test();
- levelComplete = 1;//该步搜索完成
- }
- else
- {
- if (Act[level] > maxLevels)//已经搜索完所有方向
- {
- back();//回退,到上一个分支
- }
- if (level < 0)//全部搜索完,仍然没有搜索到目标
- {
- levelComplete = allComplete = 1;//退出
- }
- }
- }
- }
- }
- void print()
- {
- cout << "The path is " << endl;
- for (int i = 0;i < height;++i)
- {
- for (int j = 0;j < width;++j)
- {
- /*cout << table[i][j] << " ";*/
- if(table[i][j])
- cout << "1 ";
- else cout << " ";
- }
- cout << endl;
- }
- }
- int main(){
- DFS();
- print();
- }
针对上面的迷宫地图的运行结果:
二、广度优先遍历(BFS:Breadth-First-Search)
广度优先是从初始状态一层一层向下找,直到找到目标为止。非常类似于树的层次遍历。下图是一个广度优先搜索的示意图:
广度优先搜索算法思想:
在这里采用一个辅助数据结构:队列。
(1)顶点v入队列。
(2)当队列非空时则继续执行,否则算法结束。
(3)出队列取得队头顶点v;访问顶点v并标记顶点v已被访问。
(4)查找顶点v的第一个邻接顶点col。
(5)若v的邻接顶点col未被访问过的,则col入队列。
(6)继续查找顶点v的另一个新的邻接顶点col,转到步骤(5)。直到顶点v的所有未被访问过的邻接点处理完。转到步骤(2)。
(2)当队列非空时则继续执行,否则算法结束。
(3)出队列取得队头顶点v;访问顶点v并标记顶点v已被访问。
(4)查找顶点v的第一个邻接顶点col。
(5)若v的邻接顶点col未被访问过的,则col入队列。
(6)继续查找顶点v的另一个新的邻接顶点col,转到步骤(5)。直到顶点v的所有未被访问过的邻接点处理完。转到步骤(2)。
下面仍然以深度优先搜索算法中的迷宫为例,编写程序实现广度优先搜索算法。
代码(C++实现):
- //迷宫寻路:广度优先搜索
- #include <iostream>
- using namespace std;
- const int rows = 10;//行数
- const int cols = 10;//列数
- const int numDirections = 4;//每一步,下一步可以走的方向:4个
- //四种移动方向(左、右、上、下)对x、y坐标的影响
- //x坐标:竖直方向,y坐标:水平方向
- const char dx[numDirections] = {0,0,-1,1};
- const char dy[numDirections] = {-1,1,0,0};
- //障碍表
- char block[rows][cols] = {
- 0,1,0,0,0,0,0,0,0,0,
- 0,1,1,0,1,1,1,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,
- 1,1,1,0,1,0,0,0,0,0,
- 0,1,0,0,1,0,1,1,1,0,
- 0,1,0,0,1,1,1,1,1,0,
- 0,0,0,1,1,0,0,0,1,0,
- 0,1,0,0,0,0,1,0,1,0,
- 0,1,1,1,0,1,1,0,1,1,
- 0,0,0,0,0,0,1,0,0,0,
- };
- char path[rows][cols] = {0};//记录路径
- int startX = 0,startY = 0;//起始点坐标
- int endX = rows - 1,endY = cols - 1;//目标点坐标
- //保存节点位置坐标的数据结构
- typedef struct tagQNode{
- char x,y;
- int parentNode;//父节点索引
- }QNode;
- //打印路径
- void printPath()
- {
- cout << "Success : the path is " << endl;
- for (int i = 0;i < rows;++i)
- {
- for (int j = 0;j < cols;++j)
- {
- if (1 == path[i][j])
- {
- cout << "1 ";
- }
- else
- cout << " ";
- }
- cout << endl;
- }
- }
- void BFS()
- {
- int num = rows * cols;
- //利用数组来模拟队列
- QNode *queue = (QNode *)malloc(num * sizeof(QNode));
- //起始点入队列
- queue[0].x = queue[0].y = 0;
- queue[0].parentNode = -1;//起始点没有父节点
- int front = 0,rear = 1;//队列的头和尾
- while(front != rear)//队列不为空
- {
- for (int i = 0;i < numDirections;++i)
- {
- char nextX,nextY;//下一步的坐标
- nextX = queue[front].x + dx[i];
- nextY = queue[front].y + dy[i];
- //下一个节点可行
- if (nextX >= 0 && nextX < rows &&
- nextY >= 0 && nextY < cols &&
- 0 == block[nextX][nextY])
- {
- //寻找到目标点
- if (nextX == endX && nextY == endY)
- {
- //生成路径
- path[nextX][nextY] = 1;
- int tempParentIndex = front;
- while(tempParentIndex != -1)
- {
- path[queue[tempParentIndex].x][queue[tempParentIndex].y] = 1;
- tempParentIndex = queue[tempParentIndex].parentNode;
- }
- printPath();
- }
- //入栈
- queue[rear].x = nextX;
- queue[rear].y = nextY;
- queue[rear].parentNode = front;
- ++rear;
- //标记此点已被访问
- block[nextX][nextY] = 1;
- }
- }
- ++front;
- }
- }
- int main()
- {
- BFS();
- //printPath();
- }
运行结果: