当前位置:   article > 正文

算法篇-14-A*算法解决八数码问题_基于a*算法求解八数码问题的ppt

基于a*算法求解八数码问题的ppt

问题描述

八数码问题:在3×3的方格棋盘上,摆放着1到8这八个数码,有1个方格是空的,其初始状态如图1所示,要求对空格执行空格左移、空格右移、空格上移和空格下移这四个操作使得棋盘从初始状态到目标状态。

1

2

3

8

 0

4

7

6

5

 ( a )初始状态     

2

8

3

1

 0

4

7

6

5

 (b)目标状态

 A*算法介绍

A* 算法实际是一种启发式搜索,所谓启发式搜索,就是利用一个估价函数评估每次决策的价值,决定先尝试哪一种方案,这样可以极大的优化普通的广度优先搜索。

A*算法是一个可采纳的最好优先算法。A*算法的估价函数可表示为:

f'(n) = g'(n) + h'(n)

这里,f'(n)是估价函数,g'(n)是起点到终点的最短路径值,h'(n)是n到目标的最断路经的启发值。由于这个f'(n)其实是无法预先知道的,所以我们用前面的估价函数f(n)做近似。g(n)代替g'(n),但g(n)>=g'(n)才可(大多数情况下都是满足的,可以不用考虑),h(n)代替h'(n),但h(n)<=h'(n)才可。可以证明应用这样的估价函数是可以找到最短路径的,也就是可采纳的。

貌似其经常用在游戏中?

算法关键数据结构

Node状态结点:

1.保存每个搜索得到的状态,所以需要有保存状态的方式。

int chest[G_N][G_N];

2.明显需要指向前一个状态节点的指针,以及指向自己后续状态的指针。

Node *prior; //父节点指针

vector<Node*> childs;

3.由于采用启发式搜索,所以还需要保存自身的估价值。

int h_value,g_value,f_value;

Open表:

用于存放待扩展的结点。其中节点的排列顺序是依照估价函数排列,每次取出估价最好的结点扩展(类似优先队列)。如果可以由初始状态到达目标状态,那么一定可以在open表内结点取完前搜索到,否则当前初始态无法达到目标状态。

Closed表:

用于保存已经搜索过的结点。可以直接使用线性表来模拟。最优结果搜索路径应该隐含在这里。

算法伪代码:

  1. Void fun() {
  2. Openlist.push(初始节点);
  3. While(当openlist不为空) {
  4. //取出最优的
  5. Node tmp = openlist.front();
  6. If(tmp == target) {
  7. 依据父子关系回溯输出结果路径。
  8. }
  9. while(tmp可以生成不同的child) {
  10. 生成孩子child_i,以及child_i的估价值;
  11. 判断孩子是否与自己祖先相同,若相同舍弃,
  12. /*进行生成下一个孩子并检测;
  13. 若不同
  14. 1.检测child_i是否在open表中,若在,则比较child_i 与child_in_open的估值函数,
  15. 若child_i的估值函数优于child_in_open,则将child_i的评价值以及指向的父节点指针
  16. 赋予child_in_open,并将child_in_open加入到tmp的孩子结点指针列表里。(标记为父子关系)
  17. 此时可以释放child_i,因为孩子已经用child_in_open代替。
  18. 2.检测child_i是否在closed表中,若在,则比较child_i 与child_in_closed的估值函数,
  19. 若child_i的估值函数优于child_in_closed,则将child_i的评价值以及指向的父节点指针赋予
  20. child_in_closed, 并将child_in_closed加入到tmp的孩子结点指针列表里。将child_in_closed的
  21. 孩子列表清空,并压入open表中待扩展。
  22. 此时可以释放child_i,因为孩子已经用child_in_closed代替。
  23. 3.若child_i不在两个表中,将child_i压入open表中。并为child_i与tmp标记父子关系。
  24. */
  25. }// end_of_while(tmp可以生成不同的child)
  26. //tmp扩展完成,压入closed结点列表
  27. Closed.push(tmp);
  28. } //end_of_While(当openlist不为空)
  29. }// end_of_fun()

源代码github地址:

https://github.com/YIWANFENG/Algorithm-github
此例中设置了2种启发函数。


算法注释:

  1. #include <iostream>
  2. #include <vector>
  3. #include <cmath>
  4. using namespace std;
  5. const short G_N = 3; //方形数码问题每行元素个数
  6. class Node{
  7. public:
  8. int x,y; //0(特殊值所在位置)
  9. int chest[G_N][G_N]; //此时分布状态
  10. int h_value,g_value,f_value; //股价函数值
  11. Node *prior; //父节点指针
  12. vector<Node*> childs; //孩子指针
  13. Node *next_open; //用于访问open队列
  14. Node *prior_open; //用于访问open队列
  15. Node() {
  16. x=y=0;
  17. h_value = g_value = f_value = 0;
  18. prior = NULL;
  19. next_open = prior_open =NULL;
  20. }
  21. void inherit(Node* ff) {
  22. //仅复制x,y,chest
  23. if(!ff) return ;
  24. x = ff->x; y = ff->y;
  25. for (int i = 0; i < G_N; i++) {
  26. for (int j = 0; j < G_N; j++) {
  27. chest[i][j] = ff->chest[i][j];
  28. }
  29. }
  30. prior = ff;
  31. }
  32. void hn2(Node *target) {
  33. //启发函数 1
  34. //不在目标位置点数作为h_value
  35. int num = 0;
  36. for(int i = 0 ; i < G_N ; i++) {
  37. for(int j = 0 ; j < G_N ; j++) {
  38. if(this->chest[i][j] != target->chest[i][j])
  39. num++;
  40. }
  41. }
  42. h_value = num;
  43. }
  44. void hn(Node *target) {
  45. //启发函数 2
  46. class Distance {
  47. public:
  48. int operator() (int x,int y,int value,Node *target) {
  49. //返回(x,y)对应的值value在target中的位置与(x,y)的距离
  50. for (int i = 0; i < G_N; i++)
  51. for (int j = 0; j < G_N; j++)
  52. if (value == target->chest[i][j])
  53. return abs(x - i) + abs(y - j);
  54. return 1000;
  55. }
  56. } distance;
  57. //所有点距离目标位置和作为h_value
  58. int num = 0;
  59. for (int i = 0; i < G_N; i++) {
  60. for (int j = 0; j < G_N; j++) {
  61. num += distance(i, j, chest[i][j], target);
  62. }
  63. }
  64. h_value = num;
  65. }
  66. //n到目标的最短路径的启发值
  67. void gn() {
  68. if(this->prior == NULL)
  69. this->g_value = 0;
  70. else
  71. this->g_value = this->prior->g_value + 1;
  72. }
  73. //fn估价函数
  74. void fn() {
  75. f_value = g_value + h_value;
  76. }
  77. void show() {
  78. for (int i = 0; i < G_N; i++) {
  79. for (int j = 0; j < G_N; j++) {
  80. cout << this->chest[i][j] << ' ';
  81. }
  82. cout << endl;
  83. }
  84. }
  85. };
  86. bool is_same_node(Node *a, Node *b) {
  87. if (a->x != b->x || a->y != b->y) return false;
  88. for (int i = 0; i < G_N; i++) {
  89. for (int j = 0; j < G_N; j++) {
  90. if (a->chest[i][j] != b->chest[i][j]) {
  91. return false;
  92. }
  93. }
  94. }
  95. return true;
  96. }
  97. class OpenList {
  98. //此class用于访问在open队列
  99. public:
  100. Node* head_;
  101. void add_one(Node*one) {
  102. //list中加入one
  103. if(one == NULL) return ;
  104. one->next_open = NULL;
  105. Node* tmp = head_;
  106. if(head_ == NULL) {
  107. head_ = one;
  108. one->prior_open = NULL;
  109. } else {
  110. while(tmp->next_open!=NULL) tmp=tmp->next_open;
  111. tmp->next_open = one;
  112. one->prior_open = tmp;
  113. one->next_open = NULL;
  114. }
  115. }
  116. void delete_one(Node *one) {
  117. //list中删除one
  118. if(one->prior_open==NULL) {
  119. head_ = one->next_open;
  120. if(one->next_open) //找了几天的bug,哎,此处之前漏写,导致链表操作失误
  121. one->next_open->prior_open = NULL;
  122. } else {
  123. one->prior_open->next_open = one->next_open;
  124. if(one->next_open != NULL) one->next_open->prior_open = one->prior_open;
  125. }
  126. one->next_open = one->prior_open = NULL;
  127. }
  128. Node* front() {
  129. //取出 Openlist 中 f_value最大的元素地址
  130. Node *tmp = head_;
  131. Node *min=head_;
  132. while(tmp!=NULL) {
  133. if(tmp->f_value < min->f_value) {
  134. min = tmp;
  135. }
  136. tmp = tmp->next_open;
  137. }
  138. return min;
  139. }
  140. bool has_child(Node *child,Node *&who) {
  141. //判断OPEN表中是否含有结点child
  142. //并由who指出其在open中的位置
  143. Node *tmp = head_;
  144. while(tmp) {
  145. if(is_same_node(tmp, child)) {
  146. who = tmp;
  147. return true;
  148. }
  149. tmp = tmp->next_open;
  150. }
  151. who = NULL;
  152. return false;
  153. }
  154. bool empty() {
  155. return (head_ == NULL);
  156. }
  157. };
  158. Node target;
  159. //priority_queue<Node*,vector<Node*>,cmp> OPEN; //open表,保存等待扩展的结点
  160. vector<Node*> CLOSED; //closed表,保存访问过的节点
  161. OpenList open_list; //用于访问open结点
  162. bool is_on_closed(Node *child,Node * &who,int &who_pos) {
  163. //判断child是否在CLOSED中出现过,并由who指出其在closed中的位置
  164. for(who_pos=0; who_pos < CLOSED.size(); who_pos++) {
  165. if(is_same_node(child,CLOSED[who_pos])) {
  166. who = (CLOSED[who_pos]);
  167. return true;
  168. }
  169. }
  170. who_pos = -1;
  171. who = NULL;
  172. return false;
  173. }
  174. bool is_target(Node*target,Node* one) {
  175. //判断是否到达目标状态
  176. return is_same_node(target, one);
  177. }
  178. bool is_same_as_parent(Node *one) {
  179. //判断是否和其父辈相同
  180. Node * tmp= one->prior;
  181. while (tmp != NULL) {
  182. if (is_same_node(one, tmp)) {
  183. return true;
  184. }
  185. tmp = tmp->prior;
  186. }
  187. return false;
  188. }
  189. void calculate_g_to_update_open(Node *source,Node* child,Node *who) {
  190. //source 为child的父亲结点
  191. ///***计算估值函数并修改
  192. //须知其父是谁,且因为待扩展,不许考虑其子
  193. child->gn();
  194. child->fn();
  195. if(child->g_value < who->g_value) {
  196. who->g_value = child->g_value;
  197. who->f_value = child->f_value;
  198. who->prior = child->prior;
  199. }
  200. source->childs.push_back(who);
  201. delete child;
  202. }
  203. void calculate_g_to_update_closed(Node *source,Node* child,Node *who,int who_pos_on_closed) {
  204. //source 为child的父亲结点
  205. ///***计算估值函数并修改,已扩展,需考虑其子
  206. child->gn();
  207. child->fn();
  208. source->childs.push_back(who);
  209. if(child->g_value < who->g_value) {
  210. who->g_value = child->g_value;
  211. who->f_value = child->f_value;
  212. who->prior = child->prior;
  213. who->childs.clear();
  214. CLOSED.erase(CLOSED.begin() + who_pos_on_closed);
  215. open_list.add_one(who);
  216. delete child;
  217. }
  218. }
  219. void check_child(Node *source,Node *child) {
  220. //source --child的父节点
  221. //检查孩子在整个搜索图中的位置以及状态并做相应动作
  222. if (is_same_as_parent(child)) return;
  223. child->hn(&target);
  224. Node *who;
  225. int who_pos_on_closed;
  226. if(open_list.has_child(child,who)) {
  227. //cout<<"on open"<<endl;
  228. calculate_g_to_update_open(source,child,who);
  229. } else if(is_on_closed(child,who, who_pos_on_closed)) {
  230. //cout<<"on closed"<<endl;
  231. calculate_g_to_update_closed(source,child,who, who_pos_on_closed);
  232. } else {
  233. //cout<<"add open"<<endl;
  234. source->childs.push_back(child);
  235. open_list.add_one(child);
  236. }
  237. }
  238. void creat_child(Node *source) {
  239. //产生source结点的后继可能结点(即走一步后的状态)
  240. class Swap {
  241. public:
  242. void operator()(int &a, int &b) {
  243. int t = a;
  244. a = b;
  245. b = t;
  246. }
  247. }swap;
  248. //向左交换
  249. if(source->y>0) {
  250. //cout<<"左"<<endl;
  251. Node *child = new Node();
  252. child->inherit(source);
  253. swap(child->chest[child->x][child->y],
  254. child->chest[child->x][child->y - 1]);
  255. child->x = source->x;
  256. child->y = source->y-1;
  257. check_child(source,child);
  258. }
  259. //向右交换
  260. if(source->y<G_N-1) {
  261. //cout<<"右"<<endl;
  262. Node *child = new Node();
  263. child->inherit(source);
  264. swap(child->chest[child->x][child->y],
  265. child->chest[child->x][child->y + 1]);
  266. child->x = source->x;
  267. child->y = source->y + 1;
  268. check_child(source, child);
  269. }
  270. //向上交换
  271. if(source->x>0) {
  272. //cout<<"上"<<endl;
  273. Node *child = new Node();
  274. child->inherit(source);
  275. swap(child->chest[child->x][child->y],
  276. child->chest[child->x - 1][child->y]);
  277. child->x = source->x - 1;
  278. child->y = source->y;
  279. check_child(source, child);
  280. }
  281. //向下交换
  282. if(source->x<G_N-1) {
  283. //cout<<"下"<<endl;
  284. Node *child = new Node();
  285. child->inherit(source);
  286. swap(child->chest[child->x][child->y],
  287. child->chest[child->x + 1][child->y]);
  288. child->x = source->x + 1;
  289. child->y = source->y;
  290. check_child(source, child);
  291. }
  292. }
  293. void show_path(Node *one) {
  294. Node *tmp = one;
  295. while (tmp) {
  296. cout << "↑" << endl;
  297. tmp->show();
  298. tmp = tmp->prior;
  299. }
  300. }
  301. void search_Astar(Node *target, Node *init_node)
  302. {
  303. //压入初始状态
  304. open_list.add_one(init_node);
  305. // 进入搜索树搜索图
  306. Node *tmp = NULL;
  307. while (!open_list.empty()) {
  308. //取出估价最优的元素,并从open表中除名
  309. tmp = open_list.front();
  310. open_list.delete_one(tmp);
  311. //检查该结点是否为目标节点
  312. if(is_target(target,tmp)) {
  313. cout<<"Yes"<<endl;
  314. break;
  315. }
  316. //有后即状态
  317. if(true) {
  318. //产生后继状态(后继结点)
  319. creat_child(tmp);
  320. }
  321. //该点检测完毕,压入closed表
  322. CLOSED.push_back(tmp);
  323. }
  324. show_path(tmp);
  325. //搜索结束,最终结点是tmp
  326. }
  327. void init_node_status(Node * a, int b[])
  328. {
  329. for (int i = 0; i < G_N; i++) {
  330. for (int j = 0; j < G_N; j++) {
  331. a->chest[i][j] = b[i*G_N + j];
  332. if (b[i*G_N + j] == 0) {
  333. a->x = i;
  334. a->y = j;
  335. }
  336. }
  337. }
  338. a->hn(&target);
  339. a->gn();
  340. a->fn();
  341. }
  342. int main()
  343. {
  344. Node init_node;
  345. init_node.next_open = NULL;
  346. int a[] = { 2,0,3,1,8,4,7,6,5 };
  347. int b[] = { 1,2,3,8,0,4,7,6,5 };
  348. init_node_status(&target, b);
  349. init_node_status(&init_node, a);
  350. target.show();
  351. search_Astar(&target, &init_node);
  352. cout << "Waitint for input" << endl;
  353. cin.get();
  354. return 0;
  355. }



明显由上可知,A*算法类似于优先队列搜索,但与之不同的是估值函数的设置,这也就是A*与优先队列搜索最大不同的地方,所以A*算法可以认为是优先队列搜索。

算法难点在于设置合适的启发式函数与评价估计函数。

以及由上可知我们并未利用节点的孩子节点。因为我再次偷懒。实际的高效做法是在检测孩子节点时,应该在第2步检测中递归修改其孩子节点的估价值。(此时明显会从closed追踪修改到open表中)由此open表需要再排序(注意使用STL的局限性)。

其次,这样第1步检测中若child_in_open的父指针被更改,则应先将其从其原父节点的孩子列表中删除以不影响第二步。

 

PS:

但是这里注意不是八数码问题所有状态可达某一状态,需要通过分析其初始状态的逆序数问题来判断是否可达目标态。

PS的PS:

在调试阶段,间断想了几天的bug,就是想不到问题在哪。直到前不久突然发现自己的链表在出节点后(此处应逐步跟踪,不过我在深层搜索时一般懒得逐步,选择人肉debug),弹出的节点的前后指针未赋空,在这个算法过程中会导致死循环的出现。所以不仅需要注意主要逻辑,还要把简单的部分写完整,不然debug起来真的很累人。








声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/786719
推荐阅读
相关标签
  

闽ICP备14008679号