标签:
by:田宇轩
2.1 回顾
在我的上一篇文章中,我们通过抽象的思维方式得出了A*算法的概念和原理,这一章内容中主要探讨如何用编程实现A*算法。
在数据结构与算法的学习中,每个算法都应该结合一定的数据结构在计算机中存储,然后用对应的函数操控这些数据结构,A*算法也不例外,从上一篇文章中,我们知道,A*算法需要:
(1)地图,这是一个存储静态路网的结构,由格子组成
(2)格子,格子是组成地图的基本单位,每个格子都有坐标,F,G,H,父节点这五种属性;
(3)开启列表,用于记录等待处理的格子;
(4)关闭列表,用于记录已经处理的格子;
(5)起点和终点,用于接受用户输入指定哪个点为起点,哪个点为终点;
这些存储结构都是A*算法需要的,其实为了实现A*算法,我们还需要更多的存储结构,这些结构我们将会在用到的时候抽象出来的。弄清思路之后,我们先用C语言定义一下这些结构,如果您是其他语言的使用者,也可以按照这些结构的描述用其他语言的定义和实现。下面就是C语言对A*所需结构的实现,下面这段代码可以单独定义在一个头文件中,拥有全局作用域,其实让这些代码拥有全局作用域的方式我们并不提倡,这里只是方便教学和理解用。
1 #define MAX_number 5 //这是地图的最大值,可以自己修改以符合实际需求 2 3 //一个比较基础的结构体,用于和地图联动 4 struct baseNode 5 { 6 int i; 7 int j; 8 int weigt; 9 10 int f; 11 int g; 12 int h; 13 14 struct baseNode *father; 15 }; 16 17 //定义了一个全局变量用来存储二维矩阵地图 18 struct baseNode map[MAX_number][MAX_number]; 19 20 //记录起点和终点的数组,起点为Ascenery[0],终点为Ascenery[1] 21 struct baseNode AsceneryList[2]; 22 23 //开启列表,用于A*算法 24 struct baseNode OpenList[MAX_number*MAX_number]; 25 26 //关闭列表,用于A*算法 27 struct baseNode CloseList[MAX_number*MAX_number]; 28 29 //用于记录开启列表中元素的个数 30 int openListCount = 0; 31 32 //用于记录关闭列表中元素的个数 33 int closeListCount = 0;
代码2.1.1—A*的基本存储结构
2.2 A*算法的流程
2.2.1 设计代码体系结构
为了方便用户输入和输出,也方便我们直观地看到A*的结果,在代码结构上,我们准备设计三个头文件:data.h,用于存储A*依赖的结构,func.h,用于写一些功能,process_control.h,用于显示一个简单的用户界面,控制用户流程,识别用户的输入是否在合法范围内,最后,我们用main.c调用所有这些内容。主函数很简单,这里先写出来:
1 #include "process_control.h" 2 3 int main() 4 { 5 testStart(); 6 7 return 0; 8 }
代码2.2.1.1—主函数
2.2.2 流程控制函数
我们把前面定义的存储结构写在了data.h里面。然后我们稍微设计一下控制流程的process.h。这是main.c唯一引用的头文件,里面包含一个testStart函数。
1 #pragma once 2 3 #include "funcs.h" 4 5 //用于控制整个校园导航系统流程 6 void testStart() 7 { 8 //flag用于辅助程序判断有没有足够条件执行各功能 9 int flag1 = 0, flag2 = 0; 10 11 //不断的让用户选择菜单 12 for (;;) 13 { 14 printf("基于A*算法的校园导航系统程序\n\n"); 15 16 printf("你可以进行以下操作:\n"); 17 printf("1.设定校园地图地形\n"); 18 printf("2.设定寻径的起点和终点\n"); 19 printf("3.找出最佳路径\n"); 20 printf("0.退出系统\n\n"); 21 22 //让用户输入自己的选择 23 int userInput = 0; 24 scanf("%d", &userInput); 25 26 //根据自己的选择分别执行inputmap,setroad,Astar三个函数 27 switch (userInput) 28 { 29 case 1: 30 inputmap(); 31 flag1 = 1; 32 printf("设定校园地图地形成功\n\n"); 33 break; 34 35 case 2: 36 if (flag1 == 1) 37 { 38 setRoad(); 39 flag2 = 1; 40 printf("起点终点设定完毕\n\n"); 41 } 42 else 43 { 44 printf("请先完成地图设定\n"); 45 } 46 break; 47 48 case 3: 49 if (flag1 == 1&&flag2==1) 50 { 51 Astar(); 52 printf("寻径完毕\n\n"); 53 } 54 else 55 { 56 printf("请先完成地图、起点终点设定\n"); 57 } 58 break; 59 60 case 0: 61 exit(0); 62 break; 63 64 default: 65 printf("输入不在指定范围内,请重新输入\n\n"); 66 break; 67 } 68 } 69 }
代码2.2.2.1—流程控制函数
2.2.3 设定地图样式的函数inputmap和设定起点终点的函数setroad
让我们先设定好地图再进行A*算法本体的编写,这部分没有什么难度,因此也不先写伪代码和分析逻辑了,对此不感兴趣的朋友可以直接往后看Astar函数的实现。
1 #pragma once 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <math.h> 7 #include "data_define.h" 8 9 //设定地图的函数 10 void inputmap() 11 { 12 printf("目前地图大小为%d * %d。\n",MAX_number,MAX_number); 13 printf("请通过输入整数的形式填充地图,0代表地形不可通过,\n其他正整数代表权值,权值越大,地形越不方便通过\n\n"); 14 15 for (int i = 0; i <MAX_number; ++i) 16 { 17 for (int j = 0; j < MAX_number; ++j) 18 { 19 scanf("%d", &map[i][j].weigt); 20 21 map[i][j].i = i; 22 map[i][j].j = j; 23 } 24 printf("第%d行输入完毕,共%d行\n\n",i+1,MAX_number); 25 } 26 } 27 28 //设定起点和终点的函数 29 void setRoad() 30 { 31 int i = 0, j = 0, p = 0, q = 0; 32 33 printf("地图坐标从【1,1】开始\n"); 34 printf("请输入起点的横坐标:\n"); 35 scanf("%d", &i); 36 printf("请输入起点的纵坐标:\n"); 37 scanf("%d", &j); 38 39 AsceneryList[0].i = i - 1; 40 AsceneryList[0].j = j - 1; 41 42 printf("请输入终点的横坐标:\n"); 43 scanf("%d", &p); 44 printf("请输入终点的纵坐标:\n"); 45 scanf("%d", &q); 46 47 AsceneryList[1].i = p - 1; 48 AsceneryList[1].j = q - 1; 49 50 }
代码2.2.3.1—设定地图样式和起点终点的函数
2.2.4 Astar函数的设计
由于Astar算法需要对每个点的邻居都分析其F值,而且这里我们用二维矩阵来设定地图,因此一个格子最多有8个邻居格子,为了一一处理这些邻居格子,我们设计一种大小为8的邻居数组,用循环从邻居数组的第一个元素处理到邻居数组的最大第八个元素。邻居数组定义如下:
1 //邻居列表,用于记录每个当前点的所有邻居 2 struct baseNode Neibo[8]; 3 4 //用于记录邻居的个数 5 int neibNum = 0;
代码2.2.4.1—邻居列表的定义
接下来我们按照上前文章总结的流程编写一个代码框架,先不实现各个函数的具体功能。先回顾一下上一篇文章的A*寻路流程:
2.2.4.1—A*寻路流程
复习了流程之后,我们就可以按照流程上面的大致打一个框架:
1 //假设我们预先把起点放入迭代器iter,终点设为ender 2 //把起点放入开启列表 3 putInOpenList(iter); 4 5 //当开启列表为空或者终点在关闭列表中,结束寻径 6 for (; openListCount != 0 && isInCloseList(ender)==0;) 7 { 8 //取出开启列表中f值最小的节点(之一),并设为iter(当前点) 9 iter = readTopOpenList(); 10 11 //把当前点从开启列表中删除 12 outOpenList(iter); 13 14 //把当前点记录在关闭列表中 15 putInCloseList(iter); 16 17 //把当前点的邻居加入邻居列表 18 addNeibo(iter); 19 20 //对于每个邻居,分三种情况进行操作 21 for (int i = 0; i < neibNum; ++i) 22 { 23 //如果这个邻居节点不可通过,或者这个邻居节点在关闭列表中,略过它 24 if (Neibo[i].weigt==0 || isInCloseList(Neibo[i])) 25 { 26 } 27 //如果这个邻居节点已经在开启列表中 28 else if(isInOpenList(Neibo[i])) 29 { //看看以当前格子为父节点,算出来的新G值是不是比原来的G值小,如果更小,就改变这一格的父节点,G值,重新计算F值 30 if (NewG(Neibo[i],iter)<Neibo[i].g) 31 { 32 map[Neibo[i].i][Neibo[i].j].father = &map[iter.i][iter.j]; 33 map[Neibo[i].i][Neibo[i].j].g = map[iter.i][iter.j].g + increment(Neibo[i]); 34 map[Neibo[i].i][Neibo[i].j].f = map[Neibo[i].i][Neibo[i].j].g + map[Neibo[i].i][Neibo[i].j].h; 35 //把这一格的旧记录从开启列表删除,把更新后的这一格的值加入开启列表等待处理 36 outOpenList(Neibo[i]); 37 putInOpenList(Neibo[i]); 38 } 39 } 40 //如果这个邻居节点不在开启列表中 41 else 42 { 43 map[Neibo[i].i][Neibo[i].j].father= Neibo[i].father = &map[iter.i][iter.j]; 44 map[Neibo[i].i][Neibo[i].j].g = map[iter.i][iter.j].g + increment(Neibo[i]); 45 map[Neibo[i].i][Neibo[i].j].f = map[Neibo[i].i][Neibo[i].j].g + map[Neibo[i].i][Neibo[i].j].h; 46 47 Neibo[i] = map[Neibo[i].i][Neibo[i].j]; 48 putInOpenList(Neibo[i]); 49 } 50 } 51 }
代码2.2.4.2—A*寻路流程的代码
然后,只要分别实现上面代码逻辑中的函数就好了:
1 //以下函数都是A*算法的一部分/////////////////////////// 2 3 //把一个元素插入开启列表中///////// 4 void putInOpenList(struct baseNode inList) 5 { 6 OpenList[openListCount] = inList; 7 ++openListCount; 8 } 9 10 //取出开启列表中最小的数 11 struct baseNode readTopOpenList() 12 { 13 struct baseNode temp; 14 15 for (int i = 0; i < openListCount-1; ++i) 16 { 17 if (OpenList[i].f<OpenList[i+1].f) 18 { 19 temp=OpenList[i]; 20 OpenList[i]=OpenList[i + 1]; 21 OpenList[i + 1] = temp; 22 } 23 } 24 25 return OpenList[openListCount-1]; 26 } 27 28 //把一个元素加入关闭列表中 29 void putInCloseList(struct baseNode temp) 30 { 31 CloseList[closeListCount] = temp; 32 33 ++closeListCount; 34 } 35 36 //把开启列表中的当前节点删除 37 void outOpenList(struct baseNode iter) 38 { 39 int i = openListCount - 1; 40 for (; i >= 0;--i) 41 { 42 if (OpenList[i].i==iter.i&&OpenList[i].j==iter.j) 43 { 44 break; 45 } 46 } 47 48 for (int j = i; j < openListCount-1; ++j) 49 { 50 OpenList[j] = OpenList[j+1]; 51 } 52 --openListCount; 53 } 54 55 //对于一路上的每个点,分析它的最多八个邻居,并加入邻居列表 56 void addNeibo(struct baseNode iter) 57 { 58 neibNum = 0; 59 60 for (int i = iter.i - 1; i <= iter.i + 1; ++i) 61 { 62 for (int j = iter.j - 1; j <= iter.j + 1; ++j) 63 { 64 if (i >= 0 && i <= MAX_number - 1 && j >= 0 && j <= MAX_number - 1) 65 { 66 if (i == iter.i&&j == iter.j) 67 { 68 } 69 else 70 { 71 map[i][j].h = manhatten(i, j); 72 73 Neibo[neibNum] = map[i][j]; 74 ++neibNum; 75 } 76 } 77 } 78 } 79 } 80 81 //查看临近格在不在开启列表中的函数 82 int isInOpenList(struct baseNode neibo) 83 { 84 for (int i = 0; i < openListCount - 1; ++i) 85 { 86 if (OpenList[i].i == neibo.i&&OpenList[i].j == neibo.j) 87 { 88 return 1; 89 } 90 } 91 return 0; 92 } 93 94 //查看指定的temp在不在关闭列表中的函数 95 int isInCloseList(struct baseNode temp) 96 { 97 for (int i = 0; i < closeListCount-1; ++i) 98 { 99 if (CloseList[i].i == temp.i&&CloseList[i].j == temp.j) 100 { 101 return 1; 102 } 103 } 104 return 0; 105 } 106 107 //A*中的启发式函数,用于求指定位置和终点之间的曼哈顿距离 108 int manhatten(int i, int j) 109 { 110 return (abs(AsceneryList[1].node.i - i) + abs(AsceneryList[1].node.j - j))*10; 111 } 112 113 //求当前点与父亲节点的距离 114 int increment(struct baseNode this) 115 { 116 if ((abs(this.father->i-this.i)==1) && (abs(this.father->j - this.j) == 1)) 117 { 118 return 14*this.weigt; 119 } 120 else if ((this.father->i - this.i) == 0 && (this.father->j - this.j) == 0) 121 { 122 return 0; 123 } 124 else 125 { 126 return 10*this.weigt; 127 } 128 } 129 130 //求出用当前点作为父节点时这个点的G值 131 int NewG(struct baseNode this,struct baseNode father) 132 { 133 if (abs(father.i - this.i) == 1 && abs(father.j - this.j) == 1) 134 { 135 return father.g+14; 136 } 137 else if (abs(father.i - this.i) == 0 && abs(father.j - this.j) == 0) 138 { 139 return father.g; 140 } 141 else 142 { 143 return father.g+10; 144 } 145 }
代码2.2.4.3—刚才代码中具体函数的分别实现
经过函数实现之后,我们就得出来了一条最短的从起点A到终点B的路径,这条路径上(包含A和B),每一个格子都指向它的父节点,因此我们可以从终点开始,一直遍历父节点,并设置一个迭代器,把每个格子的父节点赋值给迭代器,再储存入一个存储路径的数组里,我们就得到了这条路径:
1 //用来记录路径经过的点的个数 2 int AstackCount = 0; 3 4 //用来储存整理后的路径 5 struct baseNode Astack[MAX_number*MAX_number];
代码2.2.4.4—存储最佳路线的数组
1 //把A*算法的节点按倒序整理到Astack里面 2 void arrange(struct baseNode iter) 3 { 4 AstackCount = 0; 5 for (; ; iter=map[iter.father->i][iter.father->j]) 6 { 7 Astack[AstackCount] = iter; 8 ++AstackCount; 9 if (iter.i == AsceneryList[0].node.i&&iter.j == AsceneryList[0].node.j) 10 { 11 break; 12 } 13 } 14 } 15 16 //打印出A*算法的路径矩阵 17 printAstar() 18 { 19 printf("A为最佳路径,Q为不经过区域\n\n"); 20 int boole = 0; 21 22 for (int i = 0; i < MAX_number; ++i) 23 { 24 for (int j = 0; j < MAX_number; ++j) 25 { 26 for (int w=0; w<AstackCount; ++w) 27 { 28 if (Astack[w].i==i&&Astack[w].j==j) 29 { 30 boole = 1; 31 break; 32 } 33 } 34 35 if (boole==1) 36 { 37 printf("A "); 38 boole = 0; 39 } 40 else 41 { 42 printf("Q "); 43 } 44 } 45 printf("\n"); 46 } 47 }
代码2.2.4.5—迭代整理和输出
大功告成......等等,还差一步,我们在做A*操作时曾经假设iter初始化为起点,ender为终点,记得吗?因此,我们在做A*算法之前还要做这种类似初始化的操作:
//每次执行A*算法,都初始化开启/关闭列表 openListCount = 0; closeListCount = 0; //创建一个迭代器,每次都等于f值最小的节点 struct baseNode iter; //让这个迭代器的初值为起点 iter.i = AsceneryList[0].i; iter.j = AsceneryList[0].j; iter.weigt = map[AsceneryList[0].i][AsceneryList[0].j].weigt; //起点的没有父节点,且为唯一G值为0的点 iter.g = 0; iter.h = manhatten(iter.i,iter.j); iter.f = iter.g + iter.h; //创建终点 struct baseNode ender; ender.i = AsceneryList[1].i; ender.j = AsceneryList[1].j; //把起点放入开启列表 putInOpenList(iter);
代码2.2.4.6—初始化A*
2.3 A*算法总结
这里我们按顺序写一遍A*算法的完整代码,默认是折叠的。看了上文自己做代码的朋友如果实际操作遇到问题,就可以参考以下代码:(下面的代码因为课设加了一些无关紧要的功能)
1 #pragma once 2 3 #define MAX_number 5 4 5 //一个比较基础的结构体,用于和地图联动 6 struct baseNode 7 { 8 int i; 9 int j; 10 int weigt; 11 12 int f; 13 int g; 14 int h; 15 16 struct baseNode *father; 17 }; 18 19 //定义了一个全局变量用来存储二维矩阵地图 20 struct baseNode map[MAX_number][MAX_number]; 21 22 //用于记录景点的数组元素 23 struct scenerySpotsList 24 { 25 struct baseNode node; 26 char placeName[20]; 27 }; 28 29 //邻居列表,用于记录每个当前点的所有邻居 30 struct baseNode Neibo[8]; 31 32 //记录景点,起点和终点的数组 33 struct scenerySpotsList AsceneryList[MAX_number*MAX_number]; 34 35 //开启列表,用于A*算法 36 struct baseNode OpenList[MAX_number*MAX_number]; 37 38 //关闭列表,用于A*算法 39 struct baseNode CloseList[MAX_number*MAX_number]; 40 41 //用于记录现在的景点个数,第1个景点记录在AsceneryList【2】里,AsceneryList【0】和AsceneryList【1】分别用来记录起点和终点 42 int sceneryCount = 2; 43 44 //用于记录开启列表中元素的个数 45 int openListCount = 0; 46 47 //用于记录关闭列表中元素的个数 48 int closeListCount = 0; 49 50 //用于记录邻居的个数 51 int neibNum = 0; 52 53 //用来储存整理后的路径 54 struct baseNode Astack[MAX_number*MAX_number]; 55 56 //用来记录路径经过的点的个数 57 int AstackCount = 0;
part1为数据定义的头文件
1 #pragma once 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <math.h> 7 #include "data_define.h" 8 9 //设定地图的函数 10 void inputmap() 11 { 12 printf("目前地图大小为%d * %d。\n",MAX_number,MAX_number); 13 printf("请通过输入整数的形式填充地图,0代表地形不可通过,\n其他正整数代表权值,权值越大,地形越不方便通过\n\n"); 14 15 for (int i = 0; i <MAX_number; ++i) 16 { 17 for (int j = 0; j < MAX_number; ++j) 18 { 19 scanf("%d", &map[i][j].weigt); 20 21 map[i][j].i = i; 22 map[i][j].j = j; 23 } 24 printf("第%d行输入完毕,共%d行\n\n",i+1,MAX_number); 25 } 26 } 27 28 //设定校园景点的函数 29 void setView() 30 { 31 for (; ; ) 32 { 33 int inputI = 0; 34 int inputJ = 0; 35 char inputS[20]; 36 37 printf("请输入景点的名称,输入done结束景点录入\n"); 38 scanf("%s", inputS); 39 40 if (strcmp(inputS,"done")==0) 41 { 42 printf("结束输入:\n"); 43 break; 44 } 45 else 46 { 47 strcpy(AsceneryList[sceneryCount].placeName,inputS); 48 49 printf("地图坐标从【1,1】开始\n"); 50 printf("请输入景点的横坐标:\n"); 51 scanf("%d", &inputI); 52 AsceneryList[sceneryCount].node.i = inputI-1; 53 54 printf("请输入景点的纵坐标:\n"); 55 scanf("%d", &inputJ); 56 AsceneryList[sceneryCount].node.j = inputJ-1; 57 58 printf("成功输入一个景点:\n"); 59 ++sceneryCount; 60 } 61 } 62 } 63 64 //设定起点和终点的函数 65 void setRoad() 66 { 67 printf("你可以进行以下操作\n"); 68 printf("1.寻找一个景点\n"); 69 printf("2.手动设定起点终点坐标\n"); 70 71 int user_input = 0; 72 scanf("%d",&user_input); 73 74 if (user_input==1) 75 { 76 int i = 2; 77 78 char inputS[20]; 79 printf("请输入想查找的景点的名称:\n这个景点将作为起点\n"); 80 scanf("%s", inputS); 81 82 for (; i < sceneryCount; ++i) 83 { 84 if (strcmp(AsceneryList[i].placeName, inputS) == 0) 85 { 86 AsceneryList[0].node = AsceneryList[i].node; 87 88 i = 0; 89 printf("成功把%s设置为起点\n",inputS); 90 break; 91 } 92 } 93 if (i != 0) 94 { 95 printf("找不到这个景点\n"); 96 } 97 98 int j = 2; 99 100 char inputM[20]; 101 printf("请输入想查找的景点的名称:\n这个景点将作为终点\n"); 102 scanf("%s", inputM); 103 104 for (; j < sceneryCount; ++j) 105 { 106 if (strcmp(AsceneryList[j].placeName, inputM) == 0) 107 { 108 AsceneryList[1].node = AsceneryList[j].node; 109 110 j = 0; 111 printf("成功把%s设置为终点\n",inputM); 112 break; 113 } 114 } 115 if (j!=0) 116 { 117 printf("找不到这个景点\n"); 118 } 119 } 120 else if(user_input==2) 121 { 122 int i = 0, j = 0, p = 0, q = 0; 123 124 printf("地图坐标从【1,1】开始\n"); 125 printf("请输入起点的横坐标:\n"); 126 scanf("%d", &i); 127 printf("请输入起点的纵坐标:\n"); 128 scanf("%d", &j); 129 130 AsceneryList[0].node.i = i - 1; 131 AsceneryList[0].node.j = j - 1; 132 133 printf("请输入终点的横坐标:\n"); 134 scanf("%d", &p); 135 printf("请输入终点的纵坐标:\n"); 136 scanf("%d", &q); 137 138 AsceneryList[1].node.i = p - 1; 139 AsceneryList[1].node.j = q - 1; 140 } 141 else 142 { 143 printf("输入错误\n"); 144 } 145 } 146 147 //以下函数都是A*算法的一部分/////////////////////////// 148 149 //把一个元素插入开启列表中///////// 150 void putInOpenList(struct baseNode inList) 151 { 152 OpenList[openListCount] = inList; 153 ++openListCount; 154 } 155 156 //取出开启列表中最小的数 157 struct baseNode readTopOpenList() 158 { 159 struct baseNode temp; 160 161 for (int i = 0; i < openListCount-1; ++i) 162 { 163 if (OpenList[i].f<OpenList[i+1].f) 164 { 165 temp=OpenList[i]; 166 OpenList[i]=OpenList[i + 1]; 167 OpenList[i + 1] = temp; 168 } 169 } 170 171 return OpenList[openListCount-1]; 172 } 173 174 //把一个元素加入关闭列表中 175 void putInCloseList(struct baseNode temp) 176 { 177 CloseList[closeListCount] = temp; 178 179 ++closeListCount; 180 } 181 182 //把开启列表中的当前节点删除 183 void outOpenList(struct baseNode iter) 184 { 185 int i = openListCount - 1; 186 for (; i >= 0;--i) 187 { 188 if (OpenList[i].i==iter.i&&OpenList[i].j==iter.j) 189 { 190 break; 191 } 192 } 193 194 for (int j = i; j < openListCount-1; ++j) 195 { 196 OpenList[j] = OpenList[j+1]; 197 } 198 --openListCount; 199 } 200 201 //对于一路上的每个点,分析它的最多八个邻居,并加入邻居列表 202 void addNeibo(struct baseNode iter) 203 { 204 neibNum = 0; 205 206 for (int i = iter.i - 1; i <= iter.i + 1; ++i) 207 { 208 for (int j = iter.j - 1; j <= iter.j + 1; ++j) 209 { 210 if (i >= 0 && i <= MAX_number - 1 && j >= 0 && j <= MAX_number - 1) 211 { 212 if (i == iter.i&&j == iter.j) 213 { 214 } 215 else 216 { 217 map[i][j].h = manhatten(i, j); 218 219 Neibo[neibNum] = map[i][j]; 220 ++neibNum; 221 } 222 } 223 } 224 } 225 } 226 227 //查看临近格在不在开启列表中的函数 228 int isInOpenList(struct baseNode neibo) 229 { 230 for (int i = 0; i < openListCount - 1; ++i) 231 { 232 if (OpenList[i].i == neibo.i&&OpenList[i].j == neibo.j) 233 { 234 return 1; 235 } 236 } 237 return 0; 238 } 239 240 //查看指定的temp在不在关闭列表中的函数 241 int isInCloseList(struct baseNode temp) 242 { 243 for (int i = 0; i < closeListCount-1; ++i) 244 { 245 if (CloseList[i].i == temp.i&&CloseList[i].j == temp.j) 246 { 247 return 1; 248 } 249 } 250 return 0; 251 } 252 253 //A*中的启发式函数,用于求指定位置和终点之间的曼哈顿距离 254 int manhatten(int i, int j) 255 { 256 return (abs(AsceneryList[1].node.i - i) + abs(AsceneryList[1].node.j - j))*10; 257 } 258 259 //求当前点与父亲节点的距离 260 int increment(struct baseNode this) 261 { 262 if ((abs(this.father->i-this.i)==1) && (abs(this.father->j - this.j) == 1)) 263 { 264 return 14*this.weigt; 265 } 266 else if ((this.father->i - this.i) == 0 && (this.father->j - this.j) == 0) 267 { 268 return 0; 269 } 270 else 271 { 272 return 10*this.weigt; 273 } 274 } 275 276 //求出用当前点作为父节点时这个点的G值 277 int NewG(struct baseNode this,struct baseNode father) 278 { 279 if (abs(father.i - this.i) == 1 && abs(father.j - this.j) == 1) 280 { 281 return father.g+14; 282 } 283 else if (abs(father.i - this.i) == 0 && abs(father.j - this.j) == 0) 284 { 285 return father.g; 286 } 287 else 288 { 289 return father.g+10; 290 } 291 } 292 293 //把A*算法的节点按倒序整理到Astack里面 294 void arrange(struct baseNode iter) 295 { 296 AstackCount = 0; 297 for (; ; iter=map[iter.father->i][iter.father->j]) 298 { 299 Astack[AstackCount] = iter; 300 ++AstackCount; 301 if (iter.i == AsceneryList[0].node.i&&iter.j == AsceneryList[0].node.j) 302 { 303 break; 304 } 305 } 306 } 307 308 //打印出A*算法的路径矩阵 309 printAstar() 310 { 311 printf("A为最佳路径,Q为不经过区域\n\n"); 312 int boole = 0; 313 314 for (int i = 0; i < MAX_number; ++i) 315 { 316 for (int j = 0; j < MAX_number; ++j) 317 { 318 for (int w=0; w<AstackCount; ++w) 319 { 320 if (Astack[w].i==i&&Astack[w].j==j) 321 { 322 boole = 1; 323 break; 324 } 325 } 326 327 if (boole==1) 328 { 329 printf("A "); 330 boole = 0; 331 } 332 else 333 { 334 printf("Q "); 335 } 336 } 337 printf("\n"); 338 } 339 } 340 341 //Astar的本体 342 void Astar() 343 { 344 //每次执行A*算法,都初始化开启/关闭列表 345 openListCount = 0; 346 closeListCount = 0; 347 348 //创建一个迭代器,每次都等于f值最小的节点 349 struct baseNode iter; 350 351 //让这个迭代器的初值为起点 352 iter.i = AsceneryList[0].node.i; 353 iter.j = AsceneryList[0].node.j; 354 iter.weigt = map[AsceneryList[0].node.i][AsceneryList[0].node.j].weigt; 355 356 //起点的没有父节点,且为唯一G值为0的点 357 iter.g = 0; 358 iter.h = manhatten(iter.i,iter.j); 359 iter.f = iter.g + iter.h; 360 361 //创建终点 362 struct baseNode ender; 363 364 ender.i = AsceneryList[1].node.i; 365 ender.j = AsceneryList[1].node.j; 366 367 //把起点放入开启列表 368 putInOpenList(iter); 369 370 //当开启列表为空或者终点在关闭列表中,结束寻径 371 for (; openListCount != 0 && isInCloseList(ender)==0;) 372 { 373 //取出开启列表中f值最小的节点(之一),并设为iter(当前点) 374 iter = readTopOpenList(); 375 376 //把当前点从开启列表中删除 377 outOpenList(iter); 378 379 //把当前点记录在关闭列表中 380 putInCloseList(iter); 381 382 //把当前点的邻居加入邻居列表 383 addNeibo(iter); 384 385 //对于每个邻居,分三种情况进行操作 386 for (int i = 0; i < neibNum; ++i) 387 { 388 //如果这个邻居节点不可通过,或者这个邻居节点在关闭列表中,略过它 389 if (Neibo[i].weigt==0 || isInCloseList(Neibo[i])) 390 { 391 } 392 //如果这个邻居节点已经在开启列表中 393 else if(isInOpenList(Neibo[i])) 394 { 395 if (NewG(Neibo[i],iter)<Neibo[i].g) 396 { 397 map[Neibo[i].i][Neibo[i].j].father = &map[iter.i][iter.j]; 398 map[Neibo[i].i][Neibo[i].j].g = map[iter.i][iter.j].g + increment(Neibo[i]); 399 map[Neibo[i].i][Neibo[i].j].f = map[Neibo[i].i][Neibo[i].j].g + map[Neibo[i].i][Neibo[i].j].h; 400 401 outOpenList(Neibo[i]); 402 putInOpenList(Neibo[i]); 403 } 404 } 405 //如果这个邻居节点不在开启列表中 406 else 407 { 408 map[Neibo[i].i][Neibo[i].j].father= Neibo[i].father = &map[iter.i][iter.j]; 409 map[Neibo[i].i][Neibo[i].j].g = map[iter.i][iter.j].g + increment(Neibo[i]); 410 map[Neibo[i].i][Neibo[i].j].f = map[Neibo[i].i][Neibo[i].j].g + map[Neibo[i].i][Neibo[i].j].h; 411 412 Neibo[i] = map[Neibo[i].i][Neibo[i].j]; 413 putInOpenList(Neibo[i]); 414 } 415 } 416 } 417 418 arrange(map[ender.i][ender.j]); 419 printAstar(); 420 }
part2为函数的头文件
1 #pragma once 2 3 #include "funcs.h" 4 5 //用于控制整个校园导航系统流程 6 void testStart() 7 { 8 int flag1 = 0, flag3 = 0; 9 10 for (;;) 11 { 12 printf("基于A*算法的校园导航系统程序\n\n"); 13 14 printf("你可以进行以下操作:\n"); 15 printf("1.设定校园地图地形\n"); 16 printf("2.设定校园景点\n"); 17 printf("3.设定寻径的起点和终点\n"); 18 printf("4.找出最佳路径\n"); 19 printf("0.退出系统\n\n"); 20 21 int userInput = 0; 22 scanf("%d", &userInput); 23 24 switch (userInput) 25 { 26 case 1: 27 inputmap(); 28 flag1 = 1; 29 printf("设定校园地图地形成功\n\n"); 30 break; 31 32 case 2: 33 if (flag1==1) 34 { 35 setView(); 36 printf("校园景点设定完毕\n\n"); 37 } 38 else 39 { 40 printf("请先完成地图设定\n"); 41 } 42 break; 43 44 case 3: 45 if (flag1 == 1) 46 { 47 setRoad(); 48 flag3 = 1; 49 printf("起点终点设定完毕\n\n"); 50 } 51 else 52 { 53 printf("请先完成地图设定\n"); 54 } 55 break; 56 57 case 4: 58 if (flag1 == 1&&flag3==1) 59 { 60 Astar(); 61 printf("寻径完毕\n\n"); 62 } 63 else 64 { 65 printf("请先完成地图、起点终点设定\n"); 66 } 67 break; 68 69 case 0: 70 exit(0); 71 break; 72 73 default: 74 printf("输入不在指定范围内,请重新输入\n\n"); 75 break; 76 } 77 } 78 }
part3为控制流程的头文件,被主函数调用
1 #include "process_control.h" 2 3 int main() 4 { 5 testStart(); 6 7 return 0; 8 }
part4为主函数
其实,了解数据结构的人会看出来,A*里的开启列表每次都要找出里面的最小值,本文中逐个搜索取最小值的方法并不是最好的方法,这涉及到查找,二叉排序树等等知识,在下一篇文章中,我们开始正式分析如何优化这个算法。
作为参考,这篇文章里的程序在VS2015中运行的结果差不多是这样的:
标签:
原文地址:http://www.cnblogs.com/bugrabbit/p/5050936.html