启发式搜索 八数码问题

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

启发式搜索
1. 介绍
八数码问题也称为九宫问题。

在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。

棋盘上还有一个空格(以数字0来表示),与空格相邻的棋子可以移到空格中。

要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。

所谓问题的一个状态就是棋子在棋盘上的一种摆法。

解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。

2. 使用启发式搜索算法求解8数码问题。

1) A ,A 星算法采用估价函数
()()()()w n f n d n p n ⎧⎪=+⎨⎪⎩
, 其中:()d n 是搜索树中结点n 的深度;()w n 为结点n 的数据库中错放的棋子个数;()p n 为结点n 的数据库中每个棋子与其目标位置之间的距离总和。

2)
宽度搜索采用f(i)为i 的深度,深度搜索采用f(i)为i 的深度的倒数。

3. 算法流程
① 把起始节点S 放到OPEN 表中,并计算节点S 的)(S f ;
② 如果OPEN 是空表,则失败退出,无解;
③ 从OPEN 表中选择一个f 值最小的节点i 。

如果有几个节点值相同,当其中有一个 为目标节点时,则选择此目标节点;否则就选择其中任一个节点作为节点i ;
④ 把节点i 从 OPEN 表中移出,并把它放入 CLOSED 的已扩展节点表中;
⑤ 如果i 是个目标节点,则成功退出,求得一个解;
⑥ 扩展节点i ,生成其全部后继节点。

对于i 的每一个后继节点j :
计算)(j f ;如果j 既不在OPEN 表中,又不在CLOCED 表中,则用估价函数f 把 它添入OPEN 表中。

从j 加一指向其父节点i 的指针,以便一旦找到目标节点时记住一个解答路径;如果j 已在OPEN 表或CLOSED 表中,则比较刚刚对j 计算过的f 和前面计算过的该节点在表中的f 值。

如果新的f 较小,则
(I)以此新值取代旧值。

(II)从j 指向i ,而不是指向他的父节点。

(III)如果节点j 在CLOSED 表中,则把它移回OPEN 表中。

⑦ 转向②,即GOTO ②。

4. 估价函数
计算一个节点的估价函数,可以分成两个部分:
1、 已经付出的代价(起始节点到当前节点);
2、 将要付出的代价(当前节点到目标节点)。

节点n 的估价函数)(n f 定义为从初始节点、经过n 、到达目标节点的路径的最小代价
的估计值,即)(*n f = )(*n g + )(*
n h 。

)(*n g 是从初始节点到达当前节点n 的实际代价;
)(*n h 是从节点n 到目标节点的最佳路径的估计代价,体现出搜索过程中采用的启发式信息(背景知识),称之为启发函数。

)(*n g 所占的比重越大,越趋向于宽度优先或等代价搜索;反之,)(*n h 的比重越大,表示启发性能就越强。

5. 实验代码 为方便起见,目标棋局为不变
(1)以下代码估价函数为深度+错放棋子个数 (2) 若估价函数为深度+每个棋子与其目标位置之间的距离总和,则加入估价函数 int calvalue1(int a[]) //不在位棋子数
{
int c = 0;
int b=0;
for (int i = 0;i <= 8;i++)
for (int j = 0;j <= 8;j++)
if (a[i] = goal[j])
if (goal[j] != 0)
c=c+abs(i%3-j%3)+abs((i- i%3)/3+(j- j%3)/3);
return c;
}
(3)宽度搜索采用OPEN->jiedian.f = depth;
(4) 深度搜索采用OPEN->jiedian.f = -depth;
源代码:
1. #include "stdio.h"
2.
3. int goal[9] = { 1,2,3,8,0,4,7,6,5 }, sgoal[9];//goal 为棋盘的目标布局,并用中间状态sgoal 与之比较
4.
5.struct Board
6.{
7.int shuzu[9];
8.int d, f, e;//d:深度;f:启发函数;e:记录前一次的扩展节点
9.};
10.
11.struct NodeLink
12.{
13.Board jiedian;
14.NodeLink *parent;
15.NodeLink *previous;
16.NodeLink *next;
17.NodeLink *path;
18.};
19.//更新纪录八数码的状态
20.void setboard(int a[], int b[], int flag) //flag=0,写棋子;flag=1,写棋盘
21.{
22.for (int i = 0;i <= 8;i++)
23.if (flag)
24.a[b[i]] = i;
25.else
26.b[a[i]] = i;
27.}
28.//计算启发值的函数
29.int calvalue(int a[]) //不在位棋子数
30.{
31.int c = 0;
32.for (int i = 0;i <= 8;i++)
33.if (a[i] != goal[i])
34.if (goal[i] != 0)
35.c++;
36.return c;
37.}
38.//生成一个新节点的函数
39.NodeLink *newnode(NodeLink *TEM, int depth, int flag)
40.{
41.NodeLink *temp = new NodeLink;
42.for (int i = 0;i <= 8;i++)
43.temp->jiedian.shuzu[i] = TEM->jiedian.shuzu[i];
44.switch (flag)
45.{
46.case 1:
47.{
48.temp->jiedian.shuzu[0]--;
49.temp->jiedian.shuzu[sgoal[temp->jiedian.shuzu[0]]]++; //向左移
51.}
52.case 2:
53.{
54.temp->jiedian.shuzu[0]++;
55.temp->jiedian.shuzu[sgoal[temp->jiedian.shuzu[0]]]--; //向右移
56.break;
57.}
58.case 3:
59.{
60.temp->jiedian.shuzu[0] -= 3;
61.temp->jiedian.shuzu[sgoal[temp->jiedian.shuzu[0]]] += 3; //向上移
62.break;
63.}
64.case 4:
65.{
66.temp->jiedian.shuzu[0] += 3;
67.temp->jiedian.shuzu[sgoal[temp->jiedian.shuzu[0]]] -= 3; //向下移
68.break;
69.}
70.}
71.temp->jiedian.d = depth + 1;
72.setboard(sgoal, temp->jiedian.shuzu, 1);
73.temp->jiedian.f = temp->jiedian.d + calvalue(sgoal);
74.temp->jiedian.e = flag;
75.temp->parent = TEM;
76.return temp;
77.}
78.//把新节点加入OPEN队列
79.NodeLink *addnode(NodeLink *head, NodeLink *node) //把node插入到head链中
80.{
81.NodeLink *TEM;
82.TEM = head;
83.head = node;
84.head->next = TEM;
85.head->previous = NULL;
86.if (TEM)
87.TEM->previous = head; //TEM已为空,无需操作
88.return head;
89.}
90.
91.//求启发值最小的结点
92.NodeLink *minf(NodeLink *head)
93.{
94.NodeLink *min, *forward;
96.forward = head;
97.while (forward)
98.{
99.if (min->jiedian.f>forward->jiedian.f)
100.min = forward;
101.forward = forward->next;
102.}
103.return min;
104.}
105.
106.int main()
107.{
108.int depth = 0;
109.int source[9];
110.int i, j;
111.
112.NodeLink *OPEN = new NodeLink;
113.NodeLink *TEMP, *TEM;
114.
115.printf("请输入初始状态:\n");
116.for (i = 0;i<9;i++)
117.scanf_s("%d", &source[i]);
118.
119.setboard(source, OPEN->jiedian.shuzu, 0);
120.OPEN->jiedian.d = depth;
121.OPEN->jiedian.e = 0;
122.OPEN->jiedian.f = depth + calvalue(source);
123.OPEN->next = NULL;
124.OPEN->previous = NULL;
125.OPEN->parent = NULL;
126.
127.while (OPEN)
128.{
129.TEMP = minf(OPEN); //求具有最小启发值的节点
130.setboard(sgoal, TEMP->jiedian.shuzu, 1); //写棋盘
131.if (!calvalue(sgoal))
132.break;
133.if (TEMP != OPEN) //如果不是第一个节点
134.{
135.TEMP->previous->next = TEMP->next;
136.TEMP->next->previous = TEMP->previous;
137.}
138.else //是第一个节点
140.if (OPEN->next) //如果还有节点
141.{
142.OPEN = OPEN->next;
143.OPEN->previous = NULL;
144.}
145.else OPEN = NULL; //否则置为空
146.}
147.
148.if (TEMP->jiedian.shuzu[0] - 1 >= 0 && TEMP->jiedian.e != 2) //防止棋子回到原状态
149.OPEN = addnode(OPEN, newnode(TEMP, depth, 1));
150.if (TEMP->jiedian.shuzu[0] + 1 <= 8 && TEMP->jiedian.e != 1)
151.OPEN = addnode(OPEN, newnode(TEMP, depth, 2));
152.if (TEMP->jiedian.shuzu[0] - 3 >= 0 && TEMP->jiedian.e != 4)
153.OPEN = addnode(OPEN, newnode(TEMP, depth, 3));
154.if (TEMP->jiedian.shuzu[0] + 3 <= 8 && TEMP->jiedian.e != 3)
155.OPEN = addnode(OPEN, newnode(TEMP, depth, 4));
156.depth++;
157.}
158.
159.if (OPEN) //如有解,则打印出解的步骤
160.{
161.TEMP->path = NULL;
162.while (TEMP->parent) //每次回溯父节点,生成路径
163.{
164.TEMP->parent->path = TEMP;
165.TEMP = TEMP->parent;
166.}
167.j = 0;
168.while (TEMP->path)
169.{
170.setboard(sgoal, TEMP->jiedian.shuzu, 1);
171.printf("第%d步:\n", j);
172.for (i = 0;i <= 2;i++)
173.printf(" %d", sgoal[i]);
174.printf(" \n");
175.for (i = 3;i <= 5;i++)
176.printf(" %d", sgoal[i]);
177.printf("\n");
178.for (i = 6;i <= 8;i++)
179.printf(" %d", sgoal[i]);
180.printf("\n");
181.TEMP = TEMP->path;
182.j++;
184.setboard(sgoal, TEMP->jiedian.shuzu, 1);
185.printf("第%d步:\n", j);
186.for (i = 0;i <= 2;i++)
187.printf(" %d", sgoal[i]);
188.printf("\n");
189.for (i = 3;i <= 5;i++)
190.printf(" %d", sgoal[i]);
191.printf("\n");
192.for (i = 6;i <= 8;i++)
193.printf(" %d", sgoal[i]);
194.printf("\n");
195.}
196.else
197.printf("无法求解!");
198.}
(1)以上代码估价函数为深度+错放棋子个数
(2) 若估价函数为深度+每个棋子与其目标位置之间的距离总和,则函数改为int calvalue(int a[]) //不在位棋子数
{
int c = 0;
int b=0;
for (int i = 0;i <= 8;i++)
for (int j = 0;j <= 8;j++)
if (a[i] = goal[j])
if (goal[j] != 0)
c=c+abs(i%3-j%3)+abs((i- i%3)/3+(j- j%3)/3);
return c;
}
(3)宽度搜索采用OPEN->jiedian.f = depth;
(4) 深度搜索采用OPEN->jiedian.f = -depth;
6.输出结果:
(输入为:

目标状态为:
(1)估价函数为深度+错放棋子个数
(2) 估价函数为深度+每个3棋子与其目标位置之间的距离总和
(3)宽度搜索采用OPEN->jiedian.f = depth;
(4) 深度搜索采用OPEN->jiedian.f = -depth;。

相关文档
最新文档