深度优先算法解决八数码问题

深度优先算法解决八数码问题,第1张

首先我们来想象一只老鼠,在一座不见天日的迷宫内,老鼠在入口处进去,要从出 口出来。那老鼠会怎么走?当然是这样的:老鼠如果遇到直路,就一直往前走,如果遇到分叉路口,就任意选 择其中的一个继续往下走,如果遇到死胡同,就退回到最近的一个分叉路口,选择另一条道路再走下去,如果 遇到了出口,老鼠的旅途就算结束了。深度优先搜索法的基本原则就是这样:按照某种条件往前试探搜索,如 果前进中遭到失败(正如老鼠遇到死胡同)则退回头另选通路继续搜索,直到找到条件的目标为止。

实现这一算法,我们要用到编程的另一大利器--递归。递归是一个很抽象的概念, 但是在日常生活中,我们还是能够看到的。拿两面镜子来,把他们面对着面,你会看到什么?你会看到镜子中 有无数个镜子?怎么回事?A镜子中有B镜子的象,B镜子中有A镜子的象,A镜子的象就是A镜子本身的真实写 照,也就是说A镜子的象包括了A镜子,还有B镜子在A镜子中的象………………好累啊,又烦又绕口,还不好理 解。换成计算机语言就是A调用B,而B又调用A,这样间接的,A就调用了A本身,这实现了一个重复的功能。再 举一个例子;从前有座山,山里有座庙,庙里有个老和尚,老和尚在讲故事,讲什么呢?讲:从前有座山,山 里有座庙,庙里有个老和尚,老和尚在讲故事,讲什么呢?讲:从前有座山,山里有座庙,庙里有个老和尚, 老和尚在讲故事,讲什么呢?讲:…………。好家伙,这样讲到世界末日还讲不玩,老和尚讲的故事实际上就 是前面的故事情节,这样不断地调用程序本身,就形成了递归。 万一这个故事中的某一个老和尚看这个故事不 顺眼,就把他要讲的故事换成:“你有完没完啊!”,这样,整个故事也就嘎然而止了。我们编程就要注意这 一点,在适当的时候,就必须要有一个这样的和尚挺身而出,把整个故事给停下来,或者使他不再往深一层次 搜索,要不,我们的递归就会因计算机存储容量的限制而被迫溢出,切记,切记。

我们把递归思想运用到上面的迷宫中,记老鼠现在所在的位置是(x,y),那它现在有 前后左右4个方向可以走,分别是(x+1,y),(x-1,y),(x,y+1),(x,y-1),其中一个方向是它来时的路,我们先不 考虑,我们就分别尝试其他三个方向,如果某个方向是路而不是墙的话,老鼠就向那个方向迈出一步。在新的 位置上,我们又可以重复前面的步骤。老鼠走到了死胡同又是怎么回事?就是除了来时的路,其他3个方向都是 墙,这时这条路就走到了尽头,无法再向深一层发展,我们就应该沿来时的路回去,尝试另外的方向。

例:八皇后问题:在标准国际象棋的棋盘上(88格)准备放置8只皇后,我们知 道,国际象棋中皇后的威力是最大的,她既可以横走竖走,还可以斜着走,遇到挡在她前进路线上的敌人,她 就可以吃掉对手。要求在棋盘上安放8只皇后,使她们彼此互相都不能吃到对方,求皇后的放法。

这是一道很经典的题目了,我们先要明确一下思路,如何运用深度优先搜索法,完 成这道题目。我们先建立一个88格的棋盘,在棋盘的第一行的任意位置安放一只皇后。紧接着,我们就来放 第二行,第二行的安放就要受一些限制了,因为与第一行的皇后在同一竖行或同一对角线的位置上是不能安放 皇后的,接下来是第三行,……,或许我们会遇到这种情况,在摆到某一行的时候,无论皇后摆放在什么位 置,她都会被其他行的皇后吃掉,这说明什么呢?这说明,我们前面的摆放是失败的,也就是说,按照前面 的皇后的摆放方法,我们不可能得到正确的解。那这时怎么办?改啊,我们回到上一行,把原先我们摆好的 皇后换另外一个位置,接着再回过头摆这一行,如果这样还不行或者上一行的皇后只有一个位置可放,那怎 么办?我们回到上一行的上一行,这和老鼠碰了壁就回头是一个意思。就这样的不断的尝试

八数码是一种数字游戏,游戏的最终目标是将随机排列的八个数字重新排列为1~8的顺序并留空一格。扭动数字的步数越少,意味着你的解题速度越快。相信很多人们玩八数码时都有过这样的一个问题,八数码为什么是9的阶乘?其实,这个问题的答案很简单。八数码的每个位置都有8种不同的数字选择,但是,棋盘上有一格是空的,因此总共的排列数量为8的阶乘,即8!。另外一种方法推导出来八数码总共的排列数量为9!-1,其中-1表示八数码被重排后不包括终止状态。这就是为什么可以玩好几十年不重复一种状态的原因。

#pragma warning(disable:4786)

#include <algorithm>

#include <cstdio>

#include <set>

#include <utility>

#include <ctime>

#include <cassert>

#include <cstring>

#include <iostream>

using namespace std;

/item记录搜索空间中一个结点

state 记录用整数形式表示的8数码格局

blank 记录当前空格位置,主要用于程序优化,

扩展时可不必在寻找空格位置

g, h 对应g(n), h(n)

pre 记录当前结点由哪个结点扩展而来 /

struct item

{

int state;

int blank;

int g;

int h;

int pre;

};

const int MAXSTEPS = 100000;

const int MAXCHAR = 100;

char buf[MAXCHAR][MAXCHAR]; //open表

item open[MAXSTEPS];

//vector<item> open;

int steps = 0;

//closed表,已查询状态只要知道该状态以及它由哪个结点扩展而来即可,用于输出路径

//每次只需得到对应f值最小的待扩展结点,用堆实现提高效率

pair<int, int> closed[MAXSTEPS];

//读入,将8数码矩阵格局转换为整数表示

bool read(pair<int,int> &state)

{

if (!gets(buf[0]))

return false;

if (!gets(buf[1]))

return false;

if (!gets(buf[2]))

return false;

//cout << strlen(buf[0]) << ' ' << strlen(buf[1]) << ' ' << strlen(buf[2]) << endl;

assert(strlen(buf[0]) == 5 && strlen(buf[1]) == 5 && strlen(buf[2]) == 5);

// astarin中的每行数据长度必须为5

statefirst = 0;

for (int i = 0, p = 1; i < 3; ++i)

{

for (int j = 0; j < 6; j += 2)

{

if (buf[i][j] == '0')

statesecond = i 3 + j / 2; // statesecond为0(空格)在节点中的位置

else

statefirst += p (buf[i][j] - '0');

p = 10;

}

}

/ 若初试节点为:

1 2 3

8 0 4

7 6 5

则statefirst为567408321,statesecond为4

/

return true;

}

//计算当前结点距目标的距离

int calculate(int current, int target) // return h=the sum of distances each block have to move to the right position,这里的each block不包括空格

{

int c[9], t[9];

int i, cnt = 0;

for (i = 0; i < 9; ++i)

{

c[current % 10] = t[target % 10] = i;

current /= 10;

target /= 10;

}

for (i = 1; i < 9; ++i)

cnt += abs(c[i] / 3 - t[i] / 3) + abs(c[i] % 3 - t[i] % 3);

return cnt;

}

//open表中结点间选择时的规则 f(n) = g(n) + h(n)

class cmp

{

public: inline bool operator()(item a, item b)

{

return ag + ah > bg + bh;

}

};

//将整数形式表示转换为矩阵表示输出

void pr(int state)

{

memset(buf, ' ', sizeof(buf));

for (int i = 0; i < 3; ++i)

{

for (int j = 0; j < 6; j += 2)

{

if (state % 10)

buf[i][j] = state % 10 + '0';

state /= 10;

}

buf[i][5] = '\0';

puts(buf[i]);

}

}

//用于判断当前空格是否可以向对应方向移动

inline bool suit(int a, int b) //空格移动后的坐标为(a,b)

{

return (a >= 0 && a < 3 && b >= 0 && b < 3);

}

//递归输出搜索路径

void path(int index)

{

if (index == 0)

{

pr(closed[index]first);

puts("");

return;

}

path(closed[index]second);

pr(closed[index]first); //将整数形式表示转换为矩阵表示输出

puts("");

++steps;

}

int getNixuNum( int state ) //求节点的逆序对数

{

int sum = 0;

int result[9];

memset( result, 0, sizeof(result) );

//cout << result[8] << result[7] << endl;

char buf[10];

itoa( state, buf, 10 );

//cout << buf << endl;

int k = 0;

while( buf[k] != '\0' )

{

result[9-k-1] = buf[k] - '0';

k++;

}

for( int i = 0; i < 9; i++ )

{

for( int j = i + 1; j < 9; j++ )

{

if( result[i] && result[j] && result[i] > result[j] )

{

sum++;

}

}

}

return sum; //返回33方格数组的逆序对数

}

int main()

{

//cout << getNixuNum(87654321);

//openresize(MAXSTEPS);

unsigned int t1 = clock();

//cout << opensize() << endl;

if( freopen("astarin", "r", stdin) == NULL )

{

cout << "file not find\n";

exit(0);

};

freopen("astar2out", "w", stdout);

set<int>states;

char tmp[100];

int i, x, y, a, b, nx, ny, end, next, index, kase = 0;

pair<int,int> start, target;

item head; //4个方向移动时的偏移量

const int xtran[4] = {-1, 0, 1, 0};

const int ytran[4] = {0, 1, 0, -1};

const int p[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};

while (read(start)) // 读取初试状态节点

{

unsigned int t2 = clock();

printf("Case %d:\n\n", ++kase);

gets(tmp);

read(target); // 读取目标状态节点

gets(tmp);

int targetNixuNum = getNixuNum(targetfirst);

//若两者的逆序对数不是同为奇数或同为偶数,则无解

if( !(getNixuNum(startfirst)&1 && targetNixuNum&1 || !(getNixuNum(startfirst)&1) && !(targetNixuNum&1)) )

{

cout << "无法从初始节点到终态节点\n";

exit(0);

}

//初始化open表,将初始状态加入

open[0]state = startfirst;

open[0]h = calculate(startfirst, targetfirst); // 计算当前节点到目标节点的估计距离

open[0]blank = startsecond;

open[0]pre = -1; // 初始节点无父节点

open[0]g = 0; // 初始节点的g为0

index = 0;

statesinsert(startfirst); // 扩展过节点保存在states中,即出现过的状态保存在states中,states为set<int>类型,其中的states中的元素唯一

//提取open表中f值最小元素放入closed表,并对该结点进行扩展

for (end = 1; end > 0; ++index) // end为open表中的元素个数,一直循环到open表为空

{

assert(index < MAXSTEPS);

//临时存储

head = open[0]; // 由于使用pop_heap函数和push_heap函数,所以open[0]为g+h最小的元素

//放入closed表记录当前格局和由哪个结点扩展而来(该结点肯定已在closed表中)

closed[index]first = open[0]state; //放入close表中,表示已经扩展完的节点,下面的for循环会扩展其节点

closed[index]second = open[0]pre; // index表示当前close表中当前扩展节点的下标

//从open表中删除该结点

pop_heap(open, open + end, cmp());//为algorithm文件中的函数,第一个参数指定开始位置,第二个指定结束,第三个指定比较函数

--end;

//得到结果,递归输出路径

if (headstate == targetfirst)

{

path(index);

break;

}

x = headblank / 3;

y = headblank % 3; //空格在33方格中的x,y坐标

/

|2 0 3|

A = |1 8 4|

|7 6 5| // 看成33的数组

则headblank=1

x=0,y=1,即空格的在33的数组中下标为(0,1)

/

for (i = 0; i < 4; ++i)

{

nx = x + xtran[i];

ny = y + ytran[i];

/

i=0时:(nx,ny)为当前空格向上移动一格后的坐标

i=1时:(nx,ny)为当前空格向右移动一格后的坐标

i=2时:(nx,ny)为当前空格向下移动一格后的坐标

i=3时:(nx,ny)为当前空格向左移动一格后的坐标

/

if (suit(nx, ny)) // 判断是否能够移动

{

a = headblank; // 空格当前位置,以上面矩阵A为例,a=1

b = nx 3 + ny; // 空格移动后的新位置,开始是能够向右边移动,故b=03+2=2

//调换十进制表示整数对应两个数字位

next = headstate + ((headstate % p[a + 1]) / p[a] - (headstate % p[b + 1]) / p[b]) p[b] + ((headstate % p[b + 1]) / p[b] - (headstate % p[a + 1]) / p[a]) p[a];

// 如headstate=567481302,空格向右移动一次后,next=567481032,即为移动后的节点

// 判断能否由当前节点到达目标节点

if( ( getNixuNum(next)&1 && targetNixuNum&1 ) || ( !(getNixuNum(next)&1) && !(targetNixuNum&1) ) )

{

//判断是否已经扩展过,即已经出现过

if (statesfind(next) == statesend()) //没出现就保存一下,也存入open表

{

statesinsert(next);

open[end]pre = index; //扩展后的子节点,其父节点为当前扩展节点

open[end]blank = b;

open[end]state = next;

open[end]h = calculate(next,targetfirst);

open[end]g = headg + 1;

++end; //open表中元素加1

push_heap(open, open + end, cmp()); //压入堆中

}

}

}

}

}

if (end <= 0)

puts("No solution");

else

{

printf("Num of steps: %d\n", steps);

printf("Num of expanded: %d\n", index);

printf("Num of generated: %d\n", index + end);

printf("Time consumed: %d\n\n", clock() - t2);

}

statesclear();

steps = 0;

}

printf("Total time consumed: %d\n", clock() - t1);

return 0;

}

广度优先搜索法

在搜索法中,广度优先搜索法是寻找最短路经的首选。

1广度优先搜索算法的基本步骤

1)建立一个队列,将初始结点入队,并设置队列头和尾指针

2)取出队列头(头指针所指)的结点进行扩展,从它扩展出子结点,并将这些结点按扩展的顺序加入队列。

3)如果扩展出的新结点与队列中的结点重复,则抛弃新结点,跳至第六步。

4)如果扩展出的新结点与队列中的结点不重复,则记录其父结点,并将它加入队列,更新队列尾指针。

5)如果扩展出的结点是目标结点,则输出路径,程序结束。否则继续下一步。

6)如果队列头的结点还可以扩展,直接返回第二步。否则将队列头指针指向下一结点,再返回第二步。

2搜索路径的输出

搜索到目标结点后,需要输出搜索的路径。每个结点有一个数据域last,它记录了结点的父结点,因此输出搜索路径时,就是从目标结点Q出发,根据last找到它的父结点,再根据这个结点的last找到它的父结点,,最后找到初始结点。搜索的路径就是从初始结点循相反方向到达目标结点的路径。

以上就是关于深度优先算法解决八数码问题全部的内容,包括:深度优先算法解决八数码问题、八数码为什么是9的阶乘、问: 40 人工智能及其应用期末作业 用A*算法解决下面的八数码难题。试定义估价函数,启发函数,等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/9684689.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-30
下一篇 2023-04-30

发表评论

登录后才能评论

评论列表(0条)

保存