赞
踩
八数码问题:在3×3的方格棋盘上,摆放着1到8这八个数码,有1个方格是空的,其初始状态如图1所示,要求对空格执行空格左移、空格右移、空格上移和空格下移这四个操作使得棋盘从初始状态到目标状态。
1 | 2 | 3 |
8 | 0 | 4 |
7 | 6 | 5 |
2 | 8 | 3 |
1 | 0 | 4 |
7 | 6 | 5 |
(b)目标状态
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表:
用于保存已经搜索过的结点。可以直接使用线性表来模拟。最优结果搜索路径应该隐含在这里。
- Void fun() {
- Openlist.push(初始节点);
-
- While(当openlist不为空) {
- //取出最优的
- Node tmp = openlist.front();
- If(tmp == target) {
- 依据父子关系回溯输出结果路径。
- }
- while(tmp可以生成不同的child) {
- 生成孩子child_i,以及child_i的估价值;
- 判断孩子是否与自己祖先相同,若相同舍弃,
- /*进行生成下一个孩子并检测;
- 若不同
- 1.检测child_i是否在open表中,若在,则比较child_i 与child_in_open的估值函数,
- 若child_i的估值函数优于child_in_open,则将child_i的评价值以及指向的父节点指针
- 赋予child_in_open,并将child_in_open加入到tmp的孩子结点指针列表里。(标记为父子关系)
- 此时可以释放child_i,因为孩子已经用child_in_open代替。
- 2.检测child_i是否在closed表中,若在,则比较child_i 与child_in_closed的估值函数,
- 若child_i的估值函数优于child_in_closed,则将child_i的评价值以及指向的父节点指针赋予
- child_in_closed, 并将child_in_closed加入到tmp的孩子结点指针列表里。将child_in_closed的
- 孩子列表清空,并压入open表中待扩展。
- 此时可以释放child_i,因为孩子已经用child_in_closed代替。
- 3.若child_i不在两个表中,将child_i压入open表中。并为child_i与tmp标记父子关系。
- */
- }// end_of_while(tmp可以生成不同的child)
- //tmp扩展完成,压入closed结点列表
- Closed.push(tmp);
- } //end_of_While(当openlist不为空)
-
-
- }// end_of_fun()
https://github.com/YIWANFENG/Algorithm-github
此例中设置了2种启发函数。
- #include <iostream>
- #include <vector>
- #include <cmath>
- using namespace std;
-
- const short G_N = 3; //方形数码问题每行元素个数
-
-
- class Node{
- public:
-
- int x,y; //0(特殊值所在位置)
- int chest[G_N][G_N]; //此时分布状态
- int h_value,g_value,f_value; //股价函数值
- Node *prior; //父节点指针
- vector<Node*> childs; //孩子指针
-
- Node *next_open; //用于访问open队列
- Node *prior_open; //用于访问open队列
-
- Node() {
- x=y=0;
- h_value = g_value = f_value = 0;
- prior = NULL;
- next_open = prior_open =NULL;
- }
- void inherit(Node* ff) {
- //仅复制x,y,chest
- if(!ff) return ;
- x = ff->x; y = ff->y;
- for (int i = 0; i < G_N; i++) {
- for (int j = 0; j < G_N; j++) {
- chest[i][j] = ff->chest[i][j];
- }
- }
- prior = ff;
- }
-
- void hn2(Node *target) {
- //启发函数 1
- //不在目标位置点数作为h_value
- int num = 0;
- for(int i = 0 ; i < G_N ; i++) {
- for(int j = 0 ; j < G_N ; j++) {
- if(this->chest[i][j] != target->chest[i][j])
- num++;
- }
- }
- h_value = num;
- }
-
- void hn(Node *target) {
- //启发函数 2
- class Distance {
- public:
- int operator() (int x,int y,int value,Node *target) {
- //返回(x,y)对应的值value在target中的位置与(x,y)的距离
- for (int i = 0; i < G_N; i++)
- for (int j = 0; j < G_N; j++)
- if (value == target->chest[i][j])
- return abs(x - i) + abs(y - j);
- return 1000;
- }
- } distance;
- //所有点距离目标位置和作为h_value
- int num = 0;
- for (int i = 0; i < G_N; i++) {
- for (int j = 0; j < G_N; j++) {
- num += distance(i, j, chest[i][j], target);
- }
- }
- h_value = num;
- }
-
- //n到目标的最短路径的启发值
- void gn() {
- if(this->prior == NULL)
- this->g_value = 0;
- else
- this->g_value = this->prior->g_value + 1;
- }
- //fn估价函数
- void fn() {
- f_value = g_value + h_value;
- }
-
- void show() {
- for (int i = 0; i < G_N; i++) {
- for (int j = 0; j < G_N; j++) {
- cout << this->chest[i][j] << ' ';
- }
- cout << endl;
- }
- }
- };
-
- bool is_same_node(Node *a, Node *b) {
- if (a->x != b->x || a->y != b->y) return false;
- for (int i = 0; i < G_N; i++) {
- for (int j = 0; j < G_N; j++) {
- if (a->chest[i][j] != b->chest[i][j]) {
- return false;
- }
- }
- }
- return true;
- }
-
-
- class OpenList {
- //此class用于访问在open队列
- public:
- Node* head_;
- void add_one(Node*one) {
- //list中加入one
- if(one == NULL) return ;
- one->next_open = NULL;
- Node* tmp = head_;
- if(head_ == NULL) {
- head_ = one;
- one->prior_open = NULL;
- } else {
- while(tmp->next_open!=NULL) tmp=tmp->next_open;
- tmp->next_open = one;
- one->prior_open = tmp;
- one->next_open = NULL;
- }
- }
- void delete_one(Node *one) {
- //list中删除one
- if(one->prior_open==NULL) {
- head_ = one->next_open;
- if(one->next_open) //找了几天的bug,哎,此处之前漏写,导致链表操作失误
- one->next_open->prior_open = NULL;
- } else {
- one->prior_open->next_open = one->next_open;
- if(one->next_open != NULL) one->next_open->prior_open = one->prior_open;
- }
- one->next_open = one->prior_open = NULL;
-
- }
- Node* front() {
- //取出 Openlist 中 f_value最大的元素地址
- Node *tmp = head_;
- Node *min=head_;
- while(tmp!=NULL) {
- if(tmp->f_value < min->f_value) {
- min = tmp;
- }
- tmp = tmp->next_open;
- }
- return min;
- }
- bool has_child(Node *child,Node *&who) {
- //判断OPEN表中是否含有结点child
- //并由who指出其在open中的位置
- Node *tmp = head_;
- while(tmp) {
- if(is_same_node(tmp, child)) {
- who = tmp;
- return true;
- }
- tmp = tmp->next_open;
- }
- who = NULL;
- return false;
- }
- bool empty() {
- return (head_ == NULL);
- }
- };
-
-
- Node target;
- //priority_queue<Node*,vector<Node*>,cmp> OPEN; //open表,保存等待扩展的结点
- vector<Node*> CLOSED; //closed表,保存访问过的节点
- OpenList open_list; //用于访问open结点
-
- bool is_on_closed(Node *child,Node * &who,int &who_pos) {
-
- //判断child是否在CLOSED中出现过,并由who指出其在closed中的位置
- for(who_pos=0; who_pos < CLOSED.size(); who_pos++) {
-
- if(is_same_node(child,CLOSED[who_pos])) {
- who = (CLOSED[who_pos]);
- return true;
- }
- }
- who_pos = -1;
- who = NULL;
- return false;
- }
-
- bool is_target(Node*target,Node* one) {
- //判断是否到达目标状态
- return is_same_node(target, one);
- }
-
- bool is_same_as_parent(Node *one) {
- //判断是否和其父辈相同
- Node * tmp= one->prior;
- while (tmp != NULL) {
- if (is_same_node(one, tmp)) {
- return true;
- }
- tmp = tmp->prior;
- }
- return false;
- }
- void calculate_g_to_update_open(Node *source,Node* child,Node *who) {
- //source 为child的父亲结点
- ///***计算估值函数并修改
- //须知其父是谁,且因为待扩展,不许考虑其子
- child->gn();
- child->fn();
- if(child->g_value < who->g_value) {
-
- who->g_value = child->g_value;
- who->f_value = child->f_value;
- who->prior = child->prior;
- }
- source->childs.push_back(who);
- delete child;
- }
-
- void calculate_g_to_update_closed(Node *source,Node* child,Node *who,int who_pos_on_closed) {
- //source 为child的父亲结点
- ///***计算估值函数并修改,已扩展,需考虑其子
- child->gn();
- child->fn();
- source->childs.push_back(who);
-
- if(child->g_value < who->g_value) {
- who->g_value = child->g_value;
- who->f_value = child->f_value;
- who->prior = child->prior;
- who->childs.clear();
- CLOSED.erase(CLOSED.begin() + who_pos_on_closed);
- open_list.add_one(who);
- delete child;
- }
- }
-
- void check_child(Node *source,Node *child) {
- //source --child的父节点
- //检查孩子在整个搜索图中的位置以及状态并做相应动作
- if (is_same_as_parent(child)) return;
- child->hn(&target);
- Node *who;
- int who_pos_on_closed;
- if(open_list.has_child(child,who)) {
- //cout<<"on open"<<endl;
- calculate_g_to_update_open(source,child,who);
- } else if(is_on_closed(child,who, who_pos_on_closed)) {
- //cout<<"on closed"<<endl;
- calculate_g_to_update_closed(source,child,who, who_pos_on_closed);
- } else {
- //cout<<"add open"<<endl;
- source->childs.push_back(child);
- open_list.add_one(child);
- }
- }
-
-
- void creat_child(Node *source) {
- //产生source结点的后继可能结点(即走一步后的状态)
-
- class Swap {
- public:
- void operator()(int &a, int &b) {
- int t = a;
- a = b;
- b = t;
- }
- }swap;
-
- //向左交换
- if(source->y>0) {
- //cout<<"左"<<endl;
- Node *child = new Node();
- child->inherit(source);
- swap(child->chest[child->x][child->y],
- child->chest[child->x][child->y - 1]);
- child->x = source->x;
- child->y = source->y-1;
- check_child(source,child);
- }
- //向右交换
- if(source->y<G_N-1) {
- //cout<<"右"<<endl;
- Node *child = new Node();
- child->inherit(source);
- swap(child->chest[child->x][child->y],
- child->chest[child->x][child->y + 1]);
- child->x = source->x;
- child->y = source->y + 1;
- check_child(source, child);
- }
- //向上交换
- if(source->x>0) {
- //cout<<"上"<<endl;
- Node *child = new Node();
- child->inherit(source);
- swap(child->chest[child->x][child->y],
- child->chest[child->x - 1][child->y]);
- child->x = source->x - 1;
- child->y = source->y;
- check_child(source, child);
- }
- //向下交换
- if(source->x<G_N-1) {
- //cout<<"下"<<endl;
- Node *child = new Node();
- child->inherit(source);
- swap(child->chest[child->x][child->y],
- child->chest[child->x + 1][child->y]);
- child->x = source->x + 1;
- child->y = source->y;
- check_child(source, child);
-
- }
- }
-
-
-
- void show_path(Node *one) {
- Node *tmp = one;
- while (tmp) {
- cout << "↑" << endl;
- tmp->show();
- tmp = tmp->prior;
- }
- }
-
- void search_Astar(Node *target, Node *init_node)
- {
-
-
- //压入初始状态
- open_list.add_one(init_node);
- // 进入搜索树搜索图
- Node *tmp = NULL;
- while (!open_list.empty()) {
- //取出估价最优的元素,并从open表中除名
- tmp = open_list.front();
- open_list.delete_one(tmp);
-
- //检查该结点是否为目标节点
- if(is_target(target,tmp)) {
- cout<<"Yes"<<endl;
- break;
- }
-
- //有后即状态
- if(true) {
- //产生后继状态(后继结点)
- creat_child(tmp);
- }
- //该点检测完毕,压入closed表
- CLOSED.push_back(tmp);
-
- }
- show_path(tmp);
- //搜索结束,最终结点是tmp
- }
-
- void init_node_status(Node * a, int b[])
- {
- for (int i = 0; i < G_N; i++) {
- for (int j = 0; j < G_N; j++) {
- a->chest[i][j] = b[i*G_N + j];
- if (b[i*G_N + j] == 0) {
- a->x = i;
- a->y = j;
- }
- }
- }
- a->hn(&target);
- a->gn();
- a->fn();
- }
-
- int main()
- {
- Node init_node;
- init_node.next_open = NULL;
- int a[] = { 2,0,3,1,8,4,7,6,5 };
- int b[] = { 1,2,3,8,0,4,7,6,5 };
-
- init_node_status(&target, b);
- init_node_status(&init_node, a);
-
- target.show();
- search_Astar(&target, &init_node);
- cout << "Waitint for input" << endl;
- cin.get();
- return 0;
- }
明显由上可知,A*算法类似于优先队列搜索,但与之不同的是估值函数的设置,这也就是A*与优先队列搜索最大不同的地方,所以A*算法可以认为是优先队列搜索。
算法难点在于设置合适的启发式函数与评价估计函数。
以及由上可知我们并未利用节点的孩子节点。因为我再次偷懒。实际的高效做法是在检测孩子节点时,应该在第2步检测中递归修改其孩子节点的估价值。(此时明显会从closed追踪修改到open表中)由此open表需要再排序(注意使用STL的局限性)。
其次,这样第1步检测中若child_in_open的父指针被更改,则应先将其从其原父节点的孩子列表中删除以不影响第二步。
PS:
但是这里注意不是八数码问题所有状态可达某一状态,需要通过分析其初始状态的逆序数问题来判断是否可达目标态。
PS的PS:
在调试阶段,间断想了几天的bug,就是想不到问题在哪。直到前不久突然发现自己的链表在出节点后(此处应逐步跟踪,不过我在深层搜索时一般懒得逐步,选择人肉debug),弹出的节点的前后指针未赋空,在这个算法过程中会导致死循环的出现。所以不仅需要注意主要逻辑,还要把简单的部分写完整,不然debug起来真的很累人。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。