A*寻路算法的探寻与改良(二)

A*寻路算法的探寻与改良(二)

by:田宇轩                                                    

第二部分:这部分内容主要是使用C语言编程实现A*,想了解A*算法的优化内容的朋友们可以跳过这部分并阅读稍后更新的其他内容

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

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

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

part3为控制流程的头文件,被主函数调用

1 #include "process_control.h"
2
3 int main()
4 {
5     testStart();
6
7     return 0;
8 }

part4

part4为主函数

其实,了解数据结构的人会看出来,A*里的开启列表每次都要找出里面的最小值,本文中逐个搜索取最小值的方法并不是最好的方法,这涉及到查找,二叉排序树等等知识,在下一篇文章中,我们开始正式分析如何优化这个算法。

作为参考,这篇文章里的程序在VS2015中运行的结果差不多是这样的:

         

时间: 2024-10-06 20:24:45

A*寻路算法的探寻与改良(二)的相关文章

A*寻路算法的探寻与改良(三)

A*寻路算法的探寻与改良(三) by:田宇轩                                        第三分:这部分内容基于树.查找算法等对A*算法的执行效率进行了改良,想了解细化后的A*算法和变种A*算法内容的朋友们可以跳过这部分并阅读稍后更新的其他内容 3.1 回顾 在我的上一篇文章中,我们探讨了如何用编程实现A*算法,并给出了C语言的算法实现,这一章内容中我们主要研究如何提高A*算法的执行效率.抛开时间复杂度的复杂计算,我们大概可以知道,函数对数据的操作次数越少,达成

[转] A*寻路算法C++简单实现

参考文章: http://www.policyalmanac.org/games/aStarTutorial.htm   这是英文原文<A*入门>,最经典的讲解,有demo演示 http://www.cnblogs.com/technology/archive/2011/05/26/2058842.html  这是国人翻译后整理的简版,有简单代码demo,不过有些错误,讲得很清晰,本文图片来自这篇 http://blog.csdn.net/b2b160/article/details/4057

js实现A*寻路算法

这两天在做百度前端技术学院的题目,其中有涉及到寻路相关的,于是就找来相关博客进行阅读. 看了Create Chen写的理解A*寻路算法具体过程之后,我理解A*算法的原理,不得不说作者写的很好,通熟易懂,图片也做的很好,可见作者在这上面是花了心思的.如果让我写,我是写不来这么好的. 唯一的不足就是,因为我学的是js,因此最后给我的源码我是用不了的......因此才有自己写一篇的打算,方面学习js人的学习.然而前面的描述我就借用他的了,因为如果然我的表达能力实在是太渣了. 简易地图 如图所示简易地图

A*寻路算法的lua实现

一.问题概述 游戏中有敌我双方,有四十个方格,当轮到我方武将行动的时候,要先显示出我方武将可以行动的方位,这个就涉及到我方武将的行动力的大小来决定,预先做出路径的预算.这里还要考虑敌方以及地标(例如:炸弹.势头)的阻挡,以及特殊方格对武将行动力的消耗以及敌方的间隔阻挡规则. 当碰到这个问题的时候,问老大选择用什么寻路算法,他推荐的是Dijstra算法,但我看了之后感觉还不是很适合我的需求,第一:我觉得Dijstra算法是有向图的最佳路径选择,节点之间路径长度必须先知晓,但我这里四十个方格如果要两

A星寻路算法以及C++实现

A星寻路算法真是我一生接触的第一个人工智能算法了... A星寻路算法显然是用来寻路的,应用也很普遍,比如梦幻西游...算法的思路很简单,就是在bfs的基础上加了估值函数. 它的核心是 F(x) = G(x) + H(x) 和open.close列表: G(x)表示从起点到X点的消耗(或者叫移动量什么的),H(X)表示X点到终点的消耗的估值,F(x)就是两者的和值.open列表记录了可能要走的区域,close列表记录了不会再考虑的区域.我们每次都选F值最小的区域搜索,就能搜到一条到终点的最短路径,

PHP树生成迷宫及A*自己主动寻路算法

PHP树生成迷宫及A*自己主动寻路算法 迷宫算法是採用树的深度遍历原理.这样生成的迷宫相当的细,并且死胡同数量相对较少! 随意两点之间都存在唯一的一条通路. 至于A*寻路算法是最大众化的一全自己主动寻路算法 完整代码已上传,http://download.csdn.net/detail/hello_katty/8885779 ,此处做些简单解释,还须要大家自己思考动手.废话不多说,贴上带代码 迷宫生成类: /** 生成迷宫类 * @date 2015-07-10 * @edit http://w

A星寻路算法-(入门级)

你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢? 如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它! 在网上已经有很多篇关于A星寻路算法的文章,但是大部分都是提供给已经了解基本原理的高级开发者的. 本篇教程将从最基本的原理讲起.我们会一步步讲解A星寻路算法,幷配有很多图解和例子. 不管你使用的是什么编程语言或者操作平台,你会发现本篇教程很有帮助,因为它在非编程语言的层面上解释了算法的原理. 现在找下到达一杯咖啡因饮料和美味的零食的最

寻路算法A*, JPS(跳点搜索)的一些杂谈

A*是一个比较经典的启发式寻路算法.是基于dijkstra算法,但是加入了启发函数,使路径搜索效率更高.实现起来很简单.不过要做到通用性高,比如支持各种不同类型的地图,甚至不仅仅是地图,而是个图结构如解决拼图游戏N-puzzle会用到的,就需要多花点心思.用C++实现的话,可以使用模板来适应不同的需要.也可以使用类继承. template <typename NodeType, typename CostType, typename Heuristic> static vector<No

C#实现简单的AStar寻路算法

1.算法实施模型 在我看来,最简单最基础的寻路环境是:在一片二维网格区域中存在一些围墙(Block),在起始点和终点之间保持连通的前提下寻找一条最佳路径. 2.算法原理 详细介绍有很多,可参考网址:http://www.policyalmanac.org/games/aStarTutorial.htm,浅显易懂,重点是理解"启发式搜索"的概念.下面谈谈我自己的理解,如果是第一次接触A*寻路算法,还是先老老实实看完给出的参考网址吧(当然也可以参考相关的中文介绍资料). 在一片二维网格中,