目次
実験18つのデジタル問題を解決するためのA *アルゴリズム
1.実験の目的
ヒューリスティック検索、評価関数、およびアルゴリズムプロセスの定義に精通し、習得し、A *アルゴリズムを使用して、N番号の問題を解決し、解決プロセスと検索順序を理解します。
2.実験原理
A *アルゴリズムは、評価関数の定義を特徴とするヒューリスティックグラフ検索アルゴリズムです。一般的なヒューリスティックグラフ検索では、評価関数fの値が最小のノードが常に拡張ノードとして選択されます。したがって、fは最小コストパスを見つけるという観点からノードを推定します。したがって、各ノードnの評価関数値は、開始ノードからノードnまでの実際のコストg(n)の2つの要素と見なすことができます。ノードからの推定コストH(N)、ターゲットノードとのH N ≤ H * nは、H * nは、最適経路のコストであるノードからのN宛先ノードへ。
図2.18
桁の問題の解決8桁の問題は、1〜8桁の切り札が8枚刻まれた3×3の9つの正方形のチェッカーボードに配置されます。チェス盤にはスペースがあり、スペースの横にある特定の切り札をそのスペースに移動できるので、切り札をずらすことで切り札のレイアウトを別のレイアウトに変えることができます。特定の初期レイアウトまたは構造(ターゲット状態)について、トランプカードを移動して初期状態からターゲット状態に移行する方法を尋ねます。以下の図2.1は、解決すべき特定の8桁の問題を示しています。
3.実験結果
表3.18桁の問題を解決するためのさまざまなヒューリスティック関数h(n)の結果の比較
|
ヒューリスティック関数h(n) |
||
数字ではありません |
トランプカードの「位置がずれている」距離と |
0 |
|
初期状態 |
283604175 |
283604175 |
283604175 |
目標の状態 |
123804765 |
123804765 |
123804765 |
最適なソリューション |
最適なパス長 は:8 最善の方法は次のとおりです。 レベル1のステータス: 2 8 3 0 6 4 1 7 5 レイヤー2のステータス: 2 8 3 1 6 4 0 7 5 レイヤー3のステータス: 2 8 3 1 6 4 7 0 5 レイヤー4のステータス: 2 8 3 1 0 4 7 6 5 レイヤー5のステータス: 2 0 3 1 8 4 7 6 5 レイヤー6のステータス: 0 2 3 1 8 4 7 6 5 レイヤー7のステータス: 1 2 3 0 8 4 7 6 5 8階の状況: 1 2 3 8 0 4 7 6 5 |
最適なパス長 は:8 最善の方法は次のとおりです。 レベル1のステータス: 2 8 3 0 6 4 1 7 5 レイヤー2のステータス: 2 8 3 1 6 4 0 7 5 レイヤー3のステータス: 2 8 3 1 6 4 7 0 5 レイヤー4のステータス: 2 8 3 1 0 4 7 6 5 レイヤー5のステータス: 2 0 3 1 8 4 7 6 5 レイヤー6のステータス: 0 2 3 1 8 4 7 6 5 レイヤー7のステータス: 1 2 3 0 8 4 7 6 5 8階の状況: 1 2 3 8 0 4 7 6 5 |
最適なパスの長さは次のとおりです:8 最善の方法は次のとおりです。 レベル1のステータス: 2 8 3 0 6 4 1 7 5 レイヤー2のステータス: 2 8 3 1 6 4 0 7 5 レイヤー3のステータス: 2 8 3 1 6 4 7 0 5 レイヤー4のステータス: 2 8 3 1 0 4 7 6 5 レイヤー5のステータス: 2 0 3 1 8 4 7 6 5 レイヤー6のステータス: 0 2 3 1 8 4 7 6 5 レイヤー7のステータス: 1 2 3 0 8 4 7 6 5 8階の状況: 1 2 3 8 0 4 7 6 5 |
拡張ノードの数 (リーフノードを除く) |
17 |
8 |
294 |
生成されたノードの数 (リーフノードを含む) |
33 |
17 |
470 |
営業時間 (反復回数) |
220.00ms |
120.00ms |
1469.00ms |
第四に、実験の要約
1. A *アルゴリズムのフローチャートを作成して、N数の問題を解決します。
A *アルゴリズムの実験原理によれば、f = g(n)+ h(n) ;このように、評価関数f(n)は、gの場合、距離推定値h(n)の影響を多少受けます。 (n)は一定です。)制約、ノードはターゲットポイントに近く、hの値は小さく、fの値は比較的小さいため、終了方向で最短の検索を実行できます。ポイント。したがって、fはノードを推定する最小コスト経路を見つけるの観点に基づいています。図4.1.1のようなA *アルゴリズムのフローチャートの設計、フローチャートに従って作成された擬似コード、さらに完全なプログラムを作成します。OPENテーブルが生成されたが、調査していない、とCLOSED表が訪問したノードを記録してきたすべてのノードを保存します。ノードを拡張するときは、2つのテーブル(OPENテーブルとCLOSEDテーブル)にノードの後続ノードがあるかどうかも考慮する必要があります。特定のプログラミングのアイデアについては、アルゴリズム4.1を参照してください。
図4.1.18桁の問題を解決するためのA *アルゴリズムのフローチャート
アルゴリズム4.1 開いているテーブルに開始点を追加します 開いているテーブルが空でない場合: 開いているテーブルの現在のf値が最小の点を見つけます それが終了点である場合、結果が検出され、プログラムは終了します。 それ以外の場合は、現在のテーブル内の隣接するポイントごとに、開いているテーブルが現在のテーブルから移動されます。 行くことができないか、クローズリストにある場合は、スキップしてください オープンリストにない場合は追加してください。 若它在open表中,计算g值,若g值更小,替换其父节点为current,更新 它的g值。 若open表为空,则路径不存在。
|
2、分析不同的估价函数对A*算法性能的影响。
对于同一问题启发函数h(n)可以有多种设计方法。在本次时实验中,通过选用“将牌不在位数”和“将牌‘不在位’的距离和”两种不同的启发函数,同时还编写了不考虑h值进行搜索,即不采用启发性搜索的算法(按照广度优先搜索的策略)。正如表3.1所示,我们通过将第一种和第二种启发函数对比,发现第二种启发函数优于第一种启发函数,将采用启发函数与不采用启发性函数对比发现,采用启发性函数远远优于不采用启发性函数。
下面,以图4.2.2为例,分析第二种启发函数求解的过程。第二种启发函数为h(n)=将牌‘不在位’的距离和,初始时的值为6,将牌1:2,将牌2:1,将牌6:2,将牌7:1,将牌8:2。在实验结果演示(表3.1)时,并没有选取图4.3初始状态来比较不同启发函数以及不采用启发函数对求解效率的影响,而是选取了图4.2.1初始状态进行演示,因为图4.2.2的步骤较为复杂,对于不同启发函数对于实验结果和实验效率的影响较为明显。第三种启发函数是按照广度优先搜索的策略。
图4.2.1 初始状态 图4.2.2 A*算法求解八数码示意图
3、根据宽度优先搜索算法和A*算法求解八数码问题的结果,分析启发式搜索的特点。
根据表3-1的结果,我们可以发现采用A*算法求解八数码问题时间以及搜索的节点数目远远小于采用宽度优先搜索算法,这说明对于八数码问题,选用的启发性信息有利于搜索效率的提高。但是理论上来讲,如果选用的启发性信息过强,则可能找不到最优解。
4、实验心得体会
当时面对300行的代码时,不知从何处下手,通过查阅资料和请教老师,以及与同学深入探讨,仔细研究A*算法之后,才明白程序如何编写,各部分的函数如何构成。同时,通过本次实验,发现选用不同的启发函数,对于实验的结果有较大的影响。正如表3-1所示,选用第一或第二种(也就是采用A*算法)远远优于普通的广度优先搜索,同时,明显的感觉到第二种启发函数效率更高,更快的找到最优解。但是,在实验过程中,也遇到了一些问题,比如初始值的八数码初始值的选择对于实验结果的影响很大,在选取一些样例时,比如1,3,0,2,8,4,7,6,5,实验结果达到20000次依然没有停止,无法比较两种启发函数的优越性,鉴于时间原因,选取一些迭代次数较小就可以达到目标状态的样例进行验证,发现第二种结果优于第一种启发函数的结果。总的来说,实践出真知,只有把书上的理论知识运用到实践,才是真正地掌握。本次实验顺利完成,还是挺开心的。
附录代码
#include "iostream"
#include "stdlib.h"
#include "conio.h"
#include <math.h>
#include <windows.h>
#define size 3
using namespace std;
//定义二维数组来存储数据表示某一个特定状态
typedef int status[size][size];
struct SpringLink;
//定义状态图中的结点数据结构
typedef struct Node
{
status data;//结点所存储的状态 ,一个3*3矩阵
struct Node *parent;//指向结点的父亲结点
struct SpringLink *child;//指向结点的后继结点
struct Node *next;//指向open或者closed表中的后一个结点
int fvalue;//结点的总的路径
int gvalue;//结点的实际路径
int hvalue;//结点的到达目标的困难程度
}NNode , *PNode;
//定义存储指向结点后继结点的指针的地址
typedef struct SpringLink
{
struct Node *pointData;//指向结点的指针
struct SpringLink *next;//指向兄第结点
}SPLink , *PSPLink;
PNode open;
PNode closed;
//OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点
//开始状态与目标状态
/*
status startt = {1,3,0,8,2,4,7,6,5};最佳路径为2
status startt = {1,3,0,2,8,4,7,6,5};迭代超过20000次,手动停止
status startt = {2,8,3,1,6,4,7,0,5};
status startt = {2,8,3,6,0,4,1,7,5}; //实验报告
*/
int t=0; //迭代次数,相当于运行时间
int count_extendnode=0;//扩展结点
int count_sumnode=0; //生成节点
status startt = {2,8,3,6,0,4,1,7,5}; //实验报告
status target = {1,2,3,8,0,4,7,6,5};
//初始化一个空链表
void initLink(PNode &Head)
{
Head = (PNode)malloc(sizeof(NNode));
Head->next = NULL;
}
//判断链表是否为空
bool isEmpty(PNode Head)
{
if(Head->next == NULL)
return true;
else
return false;
}
//从链表中拿出一个数据
void popNode(PNode &Head , PNode &FNode)
{
if(isEmpty(Head))
{
FNode = NULL;
return;
}
FNode = Head->next;
Head->next = Head->next->next;
FNode->next = NULL;
}
//向结点的最终后继结点链表中添加新的子结点
void addSpringNode(PNode &Head , PNode newData)
{
PSPLink newNode = (PSPLink)malloc(sizeof(SPLink));
newNode->pointData = newData;
newNode->next = Head->child;
Head->child = newNode;
}
//释放状态图中存放结点后继结点地址的空间
void freeSpringLink(PSPLink &Head)
{
PSPLink tmm;
while(Head != NULL)
{
tmm = Head;
Head = Head->next;
free(tmm);
}
}
//释放open表与closed表中的资源
void freeLink(PNode &Head)
{
PNode tmn;
tmn = Head;
Head = Head->next;
free(tmn);
while(Head != NULL)
{
//首先释放存放结点后继结点地址的空间
freeSpringLink(Head->child);
tmn = Head;
Head = Head->next;
free(tmn);
}
}
//向普通链表中添加一个结点
void addNode(PNode &Head , PNode &newNode)
{
newNode->next = Head->next;
Head->next = newNode;
}
//向非递减排列的链表中添加一个结点(已经按照权值进行排序)
void addAscNode(PNode &Head , PNode &newNode)
{
PNode P;
PNode Q;
P = Head->next;
Q = Head;
while(P != NULL && P->fvalue < newNode->fvalue)
{
Q = P;
P = P->next;
}
//上面判断好位置之后,下面就是简单的插入了
newNode->next = Q->next;
Q->next = newNode;
}
/*
//计算结点的 h 值 f=g+h. 按照不在位的个数进行计算
int computeHValue(PNode theNode)
{
int num = 0;
for(int i = 0 ; i < 3 ; i++)
{
for(int j = 0 ; j < 3 ; j++)
{
if(theNode->data[i][j] != target[i][j])
num++;
}
}
return num;
}
//计算结点的 h 值 f=g+h. 按照将牌不在位的距离和进行计算
int computeHValue(PNode theNode)
{
int num = 0;
for(int i = 0 ; i < 3 ; i++)
{
for(int j = 0 ; j < 3 ; j++)
{
if(theNode->data[i][j] != target[i][j]&&theNode->data[i][j] !=0){
for(int ii=0;ii<3;ii++){
for(int jj=0;jj<3;jj++){
if(theNode->data[i][j] == target[ii][jj]){
num+=abs(ii-i)+abs(jj-j);
break;
}
}
}
}
}
}
return num;
} */
//计算结点的f,g,h值
void computeAllValue(PNode &theNode , PNode parentNode)
{
if(parentNode == NULL)
theNode->gvalue = 0;
else
theNode->gvalue = parentNode->gvalue + 1;
// theNode->hvalue = computeHValue(theNode);
theNode->hvalue = 0;
theNode->fvalue = theNode->gvalue + theNode->hvalue;
}
//初始化函数,进行算法初始条件的设置
void initial()
{
//初始化open以及closed表
initLink(open);
initLink(closed);
//初始化起始结点,令初始结点的父节点为空结点
PNode NULLNode = NULL;
PNode Start = (PNode)malloc(sizeof(NNode));
for(int i = 0 ; i < 3 ; i++)
{
for(int j = 0 ; j < 3 ; j++)
{
Start->data[i][j] = startt[i][j];
}
}
Start->parent = NULL;
Start->child = NULL;
Start->next = NULL;
computeAllValue(Start , NULLNode);
//起始结点进入open表
addAscNode(open , Start);
}
//将B节点的状态赋值给A结点
void statusAEB(PNode &ANode , PNode BNode)
{
for(int i = 0 ; i < 3 ; i++)
{
for(int j = 0 ; j < 3 ; j++)
{
ANode->data[i][j] = BNode->data[i][j];
}
}
}
//两个结点是否有相同的状态
bool hasSameStatus(PNode ANode , PNode BNode)
{
for(int i = 0 ; i < 3 ; i++)
{
for(int j = 0 ; j < 3 ; j++)
{
if(ANode->data[i][j] != BNode->data[i][j])
return false;
}
}
return true;
}
//结点与其祖先结点是否有相同的状态
bool hasAnceSameStatus(PNode OrigiNode , PNode AnceNode)
{
while(AnceNode != NULL)
{
if(hasSameStatus(OrigiNode , AnceNode))
return true;
AnceNode = AnceNode->parent;
}
return false;
}
//取得方格中空的格子的位置
void getPosition(PNode theNode , int &row , int &col)
{
for(int i = 0 ; i < 3 ; i++)
{
for(int j = 0 ; j < 3 ; j++)
{
if(theNode->data[i][j] == 0)
{
row = i;
col = j;
return;
}
}
}
}
//交换两个数字的值
void changeAB(int &A , int &B)
{
int C;
C = B;
B = A;
A = C;
}
//检查相应的状态是否在某一个链表中
bool inLink(PNode spciNode , PNode theLink , PNode &theNodeLink , PNode &preNode)
{
preNode = theLink;
theLink = theLink->next;
while(theLink != NULL)
{
if(hasSameStatus(spciNode , theLink))
{
theNodeLink = theLink;
return true;
}
preNode = theLink;
theLink = theLink->next;
}
return false;
}
//产生结点的后继结点(与祖先状态不同)链表
void SpringLink(PNode theNode , PNode &spring)
{
int row;
int col;
getPosition(theNode , row , col);
//空的格子右边的格子向左移动
if(col != 2)
{
PNode rlNewNode = (PNode)malloc(sizeof(NNode));
statusAEB(rlNewNode , theNode);
changeAB(rlNewNode->data[row][col] , rlNewNode->data[row][col + 1]);
if(hasAnceSameStatus(rlNewNode , theNode->parent))
{
free(rlNewNode);//与父辈相同,丢弃本结点
}
else
{
rlNewNode->parent = theNode;
rlNewNode->child = NULL;
rlNewNode->next = NULL;
computeAllValue(rlNewNode , theNode);
//将本结点加入后继结点链表
addNode(spring , rlNewNode);
}
}
//空的格子左边的格子向右移动
if(col != 0)
{
PNode lrNewNode = (PNode)malloc(sizeof(NNode));
statusAEB(lrNewNode , theNode);
changeAB(lrNewNode->data[row][col] , lrNewNode->data[row][col - 1]);
if(hasAnceSameStatus(lrNewNode , theNode->parent))
{
free(lrNewNode);//与父辈相同,丢弃本结点
}
else
{
lrNewNode->parent = theNode;
lrNewNode->child = NULL;
lrNewNode->next = NULL;
computeAllValue(lrNewNode , theNode);
//将本结点加入后继结点链表
addNode(spring , lrNewNode);
}
}
//空的格子上边的格子向下移动
if(row != 0)
{
PNode udNewNode = (PNode)malloc(sizeof(NNode));
statusAEB(udNewNode , theNode);
changeAB(udNewNode->data[row][col] , udNewNode->data[row - 1][col]);
if(hasAnceSameStatus(udNewNode , theNode->parent))
{
free(udNewNode);//与父辈相同,丢弃本结点
}
else
{
udNewNode->parent = theNode;
udNewNode->child = NULL;
udNewNode->next = NULL;
computeAllValue(udNewNode , theNode);
//将本结点加入后继结点链表
addNode(spring , udNewNode);
}
}
//空的格子下边的格子向上移动
if(row != 2)
{
PNode duNewNode = (PNode)malloc(sizeof(NNode));
statusAEB(duNewNode , theNode);
changeAB(duNewNode->data[row][col] , duNewNode->data[row + 1][col]);
if(hasAnceSameStatus(duNewNode , theNode->parent))
{
free(duNewNode);//与父辈相同,丢弃本结点
}
else
{
duNewNode->parent = theNode;
duNewNode->child = NULL;
duNewNode->next = NULL;
computeAllValue(duNewNode , theNode);
//将本结点加入后继结点链表
addNode(spring , duNewNode);
}
}
}
//输出给定结点的状态
void outputStatus(PNode stat)
{
for(int i = 0 ; i < 3 ; i++)
{
for(int j = 0 ; j < 3 ; j++)
{
cout << stat->data[i][j] << " ";
}
cout << endl;
}
}
//输出最佳的路径
void outputBestRoad(PNode goal)
{
int deepnum = goal->gvalue;
if(goal->parent != NULL)
{
outputBestRoad(goal->parent);
}
cout << "第" << deepnum-- << "层的状态:" << endl;
outputStatus(goal);
}
void AStar()
{
PNode tmpNode;//指向从open表中拿出并放到closed表中的结点的指针
PNode spring;//tmpNode的后继结点链
PNode tmpLNode;//tmpNode的某一个后继结点
PNode tmpChartNode;
PNode thePreNode;//指向将要从closed表中移到open表中的结点的前一个结点的指针
bool getGoal = false;//标识是否达到目标状态
long numcount = 1;//记录从open表中拿出结点的序号
initial();//对函数进行初始化
initLink(spring);//对后继链表的初始化
tmpChartNode = NULL;
//Target.data=target;
cout<<"1"<<endl;
PNode Target = (PNode)malloc(sizeof(NNode));
for(int i = 0 ; i < 3 ; i++)
{
for(int j = 0 ; j < 3 ; j++)
{
Target->data[i][j] =target[i][j];
}
}
cout<<"1"<<endl;
cout << "从open表中拿出的结点的状态及相应的值" << endl;
while(!isEmpty(open))
{
t++;
//从open表中拿出f值最小的元素,并将拿出的元素放入closed表中
popNode(open , tmpNode);
addNode(closed , tmpNode);
count_extendnode=count_extendnode+1;
cout << "第" << numcount++ << "个状态是:" << endl;
outputStatus(tmpNode);
cout << "其f值为:" << tmpNode->fvalue << endl;
cout << "其g值为:" << tmpNode->gvalue << endl;
cout << "其h值为:" << tmpNode->hvalue << endl;
/* //如果拿出的元素是目标状态则跳出循环
if(computeHValue(tmpNode) == 0)
{ count_extendnode=count_extendnode-1;
getGoal = true;
break;
}*/
//如果拿出的元素是目标状态则跳出循环
if(hasSameStatus(tmpNode,Target)== true)
{ count_extendnode=count_extendnode-1;
getGoal = true;
break;
}
//产生当前检测结点的后继(与祖先不同)结点列表,产生的后继结点的parent属性指向当前检测的结点
SpringLink(tmpNode , spring);
//遍历检测结点的后继结点链表
while(!isEmpty(spring))
{
popNode(spring , tmpLNode);
//状态在open表中已经存在,thePreNode参数在这里并不起作用
if(inLink(tmpLNode , open , tmpChartNode , thePreNode))
{
addSpringNode(tmpNode , tmpChartNode);
if(tmpLNode->gvalue < tmpChartNode->gvalue)
{
tmpChartNode->parent = tmpLNode->parent;
tmpChartNode->gvalue = tmpLNode->gvalue;
tmpChartNode->fvalue = tmpLNode->fvalue;
}
free(tmpLNode);
}
//状态在closed表中已经存在
else if(inLink(tmpLNode , closed , tmpChartNode , thePreNode))
{
addSpringNode(tmpNode , tmpChartNode);
if(tmpLNode->gvalue < tmpChartNode->gvalue)
{
PNode commu;
tmpChartNode->parent = tmpLNode->parent;
tmpChartNode->gvalue = tmpLNode->gvalue;
tmpChartNode->fvalue = tmpLNode->fvalue;
freeSpringLink(tmpChartNode->child);
tmpChartNode->child = NULL;
popNode(thePreNode , commu);
addAscNode(open , commu);
}
free(tmpLNode);
}
//新的状态即此状态既不在open表中也不在closed表中
else
{
addSpringNode(tmpNode , tmpLNode);
addAscNode(open , tmpLNode);
count_sumnode+=1;//生成节点
}
}
}
//目标可达的话,输出最佳的路径
if(getGoal)
{
cout << endl;
cout << "最佳路径长度为:" << tmpNode->gvalue << endl;
cout << "最佳路径为:" <<endl;
outputBestRoad(tmpNode);
}
//释放结点所占的内存
freeLink(open);
freeLink(closed);
// getch();
}
int main()
{ double start = GetTickCount();
AStar();
printf("生成节点数目:%d\n",count_sumnode);
printf("扩展节点数目:%d\n",count_extendnode);
printf("运行时间:%f\n",GetTickCount()-start);
return 0;
}
推荐文章
- 700套个人简历模板(考研保研工作):https://mp.csdn.net/editor/html/114280230
- 人工智能2019年秋季学期期末复习知识点整理:https://mp.csdn.net/editor/html/104431474
-
Fisher线性分类器的设计与实现,感知器算法的设计实现:https://download.csdn.net/download/weixin_43442778/16017212
欢迎大家关注【小果果学长】微信公众号,期待你的点赞和支持!