图:
,我们导航从一个点到另外一个点可以有条路径,路径不同,路况就不同,拥堵程度不同,所以导 致不同路径所花的时间也不一样,这种不同我们可以使用边的权重来表示,即根据每条边的实际情况给每一条 边分配一个正数或者负数值。如飞机线路图,各个城市就是顶点,航线就是边。那么权重就是机票价格。
图算法可以通过链表和二维数组实现;
结论:大多数时候,选择邻接列表是正确的。(在图比较稀疏的情况下,每一个顶点都只会和少数几个顶点相 连,这种情况下邻接列表是最佳选择。如果这个图比较密集,每一个顶点都和大多数其他顶点相连,那么邻接 矩阵更合适。)
使用链表实现图代码:
#include <iostream>
#include <queue>
using namespace std;
#define MaxSize 64
//定义与节点连接的边
typedef struct _EdegNode {
int adjvex;//邻接的顶点
int weight;//权重
struct _EdegNode* next;//下一条边
}EdegNode;
//顶点节点定义
typedef struct _VertexNode {
char data;//节点数据
struct _EdegNode* first;//指向邻接第一条边
}VertexNode,Adjlist;
//图的定义
typedef struct _AdjlistGraph {
Adjlist* adjlist;//图的节点
int vex;//顶点数
int edge;//边数
}AdjlistGraph;
//全局数组,用来记录节点是否被访问
bool visited[MaxSize];
int Location(AdjlistGraph& G, char c);
void QueDFS(AdjlistGraph& G, int v);
//初始化图
void initGraph(AdjlistGraph& G) {
//给图的每个节点设置成一个动态数组
G.adjlist = new Adjlist[MaxSize];
G.edge = 0;
G.vex = 0;
for (int i = 0; i < MaxSize; i++) {
visited[i] = false;
}
}
//创建图
void createGraph(AdjlistGraph& G) {
cout << "输入图的顶点数和边数:" << endl;
cin >> G.vex >> G.edge;
cout << "输入相关顶点:" << endl;
for (int i = 0; i < G.vex; i++) {
cin >> G.adjlist[i].data;
G.adjlist[i].first = NULL;
}
//保存输入的顶点的字符
char v1 = 0;
char v2 = 0;
//保存顶点在数组中的下标
int i1;
int i2;
cout << "输入想关联的顶点:" << endl;
for (int i = 0; i < G.edge; i++) {
cin >> v1 >> v2;
i1 = Location(G, v1);
i2 = Location(G, v2);
//寻找位置
if (i1 != -1 && i2 != -1) {
EdegNode* tmp = new EdegNode;
tmp->adjvex = i2;
tmp->next = G.adjlist[i1].first;
G.adjlist[i1].first = tmp;
}
}
}
//通过顶点对应的字符寻找顶点在图中的邻接点
int Location(AdjlistGraph& G, char c) {
for (int i = 0; i < G.vex; i++) {
if (G.adjlist[i].data == c) {
return i;
}
}
return -1;
}
//对图上的顶点进行深度遍历(递归实现)
void DFS(AdjlistGraph& G, int v) {
if (visited[v]==true) {
return;
}
cout << G.adjlist[v].data << ",";
visited[v] = true;//设置为已访问
EdegNode* tmp = G.adjlist[v].first;
int net = -1;
while (tmp) {
net = tmp->adjvex;
tmp = tmp->next;
if (visited[net] == false) {
DFS(G, net);
}
}
}
//对图进行广度遍历(队列实现)
void QueDFS(AdjlistGraph& G, int v) {
queue<int>q;
int cur;
int index;
q.push(v);
while (!q.empty()) {//队列非空
cur = q.front();//取队首元素
if (visited[cur] == false) {//当前节点没有被访问
cout << G.adjlist[cur].data << ",";
visited[cur] = true;
}
q.pop();//出队
EdegNode* tmp = G.adjlist[cur].first;
while (tmp != NULL) {
index = tmp->adjvex;
tmp = tmp->next;
q.push(index);//将第一个邻接点入队
}
}
}
//对所有顶点进行遍历
void DFS_Main(AdjlistGraph& G) {
for (int i = 0; i < G.vex; i++) {
if (visited[i] == false) {
QueDFS(G, i);
//DFS(G, i);
}
}
}
int main(void) {
AdjlistGraph G;
initGraph(G);
createGraph(G);
DFS_Main(G);
system("pause");
return 0;
}
图的导航 -最短路径算法
从起点开始访问所有路径,则到达终点节点的路径有多条,其中路径权值最短的一条则为最短路径。最短路径算法有 深度优先遍历、广度优先遍历、Bellman-Ford 算法、弗洛伊德算法,SPFA(Shortest Path Faster Algorithm)算法和迪 杰斯特拉算法等。
代码实现:
#include <iostream>
using namespace std;
#define MaxSize 1024
//与节点连接的边的定义
typedef struct _EdgeNode {
int adjvex;//邻接的顶点
int weight;//权重
struct _EdgeNode* next;//下一条边
}EdgeNode;
//顶点节点
typedef struct _VertexNode {
char data;//节点数据
EdgeNode* first;//指向邻接第一条边
}VertexNode,AdjList;
//图的结构定义
typedef struct _AdjListGraph {
AdjList* adjlist;//节点保存的数组
int vex;//顶点数
int edge;//边数
}AdjListGraph;
//全局数组,用来记录节点是否已被访问
bool visited[MaxSize] = { 0 };
int Location(AdjListGraph& G, char c);
//初始化图
void init(AdjListGraph& G) {
G.adjlist = new AdjList[MaxSize];
G.edge = 0;
G.vex = 0;
for (int i = 0; i < MaxSize; i++) {
visited[i] = false;
}
}
//创建图
void create(AdjListGraph& G) {
cout << "输入图的顶点数和边数:" << endl;
cin >> G.vex >> G.edge;
cout << "输入相关顶点:" << endl;
for (int i = 0; i < G.vex; i++) {
cin >> G.adjlist[i].data;
G.adjlist[i].first = NULL;
}
//保存输入的顶点的字符
char v1 = 0;
char v2 = 0;
//保存顶点在数组中的下标
int i1 = 0;
int i2 = 0;
int weight = 0;//权重
cout << "输入想要关联边的顶点及权重:" << endl;
for (int i = 0; i < G.edge; i++) {
cin >> v1 >> v2 >> weight;
i1 = Location(G, v1);
i2 = Location(G, v2);
//寻找到位置
if (i1 != -1 && i2 != -1) {
EdgeNode* tmp = new EdgeNode;
tmp->adjvex = i2;
tmp->next = G.adjlist[i1].first;
tmp->weight = weight;
G.adjlist[i1].first = tmp;
}
}
}
//通过顶点对应的字符寻找顶点在图中的邻接点
int Location(AdjListGraph& G, char c) {
for (int i = 0; i < G.vex; i++) {
if (G.adjlist[i].data == c) {
return i;
}
}
return -1;
}
//最大的整数2的32次方-1
int min_weights = 0x7FFFFFFF;
//步数
int steps = 0;
//保存走过的路径
int path[MaxSize] = { 0 };
//保存最短的路径
int shortes_path[MaxSize] = { 0 };
//对图上的顶点进行深度遍历
void DFS(AdjListGraph& G, int start, int end, int weights) {
int cur = start;
//已找到终点,不用继续遍历
if (cur == end) {
//打印所经过的路径
for (int i = 0; i < steps; i++) {//输出所有可能的路径
cout << G.adjlist[path[i]].data << ",";//输出路径
}
//输出对应路径长度
cout << "\t该路径的长度为:" << weights << endl;
//更新最小路径
if (min_weights > weights) {
min_weights = weights;
memcpy(shortes_path, path, steps * sizeof(int));
}
}
//设置为已访问
visited[start] = true;
EdgeNode* tmp = G.adjlist[start].first;
while (tmp) {
int weight = tmp->weight;
cur = tmp->adjvex;
if (visited[cur] == false) {
visited[cur] = true;//标记该路径i已经在路径中
path[steps++] = cur;//保存路径到path数组中
DFS(G, cur, end, weights + weight);//递归回下一条路径
visited[cur] = false;//之前一步探索完毕后,取消对路径i标记以便另一条路径选择顶点
path[--steps] = 0;
}
tmp = tmp->next;
}
}
int main(void) {
AdjListGraph G;
init(G);
create(G);
char start;
char end;
cout << "输入要查询的最短路径的起点和终点:" << endl;
cin >> start >> end;
DFS(G, Location(G, start), Location(G, end), 0);
cout << "最短路径长度为:" << min_weights << endl;
cout << "路径:";
int i = 0;
while (i < MaxSize && shortes_path[i]>0) {
cout << G.adjlist[shortes_path[i]].data;
i++;
}
cout << endl;
system("pause");
return 0;
}
AI 游戏中的自动寻路-A算法:
随着 3D 游戏的日趋流行,在复杂的 3D 游戏环境中如何能使非玩家控制角色准确实现自动寻路功能成为了 3D 游戏开 发技术中一大研究热点。其中 A算法得到了大量的运用,A算法较之传统的路径规划算法,实时性更高、灵活性更强,寻路 结果更加接近人工选择的路径结果. A寻路算法并不是找到最优路径,只是找到相对近的路径,因为找最优要把所有可行 路径都找出来进行对比,消耗性能太大,寻路效果只要相对近路径就行了
- G 表示从起点移动到网格上指定方格的移动距离 (暂时不考虑沿斜向移动,只考虑上下左右移动)。
- H 表示从指定的方格移动到终点的预计移动距离,只计算直线距离 (H 有很多计算方法, 这里我们设定只可以上 下左右移动,即该点与终点的直线距离)
- 令 F = G + H ,F 即表示从起点经过此点预计到终点的总移动距离 8979438401111 接下来我们从起点开始,按照以下寻路步骤,直至找到目标。
寻路步骤 - 从起点开始, 把它作为待处理的方格存入一个预测可达的节点列表,简称 openList, 即把起点放入“预测可达节点列表”, 可达节点列表 openList 就是一个等待检查方格的列表。
- 寻找 openList 中 F 值最小的点 min(一开始只有起点)周围可以到达的方格(可到达的意思是其不是障碍物,也不存 在关闭列表中的方格,即不是已走过的方格)。计算 min 周围可到达的方格的 F 值。将还没在 openList 中点放入其中, 并 设置它们的"父方格"为点 min,表示他们的上一步是经过 min 到达的。如果 min 下一步某个可到达的方格已经在 openList 列表那么并且经 min 点它的 F 值更优,则修改 F 值并把其"父方格"设为点 min。
- 把 2 中的点 min 从"开启列表"中删除并存入"关闭列表"closeList 中, closeList 中存放的都是不需要再次检查的方格。如 果 2 中点 min 不是终点并且开启列表的数量大于零,那么继续从第 2 步开始。如果是终点执行第 4 步,如果 openList 列 表数量为零,那么就是找不到有效路径。
4.如果第 3 步中 min 是终点,则结束查找,直接追溯父节点到起点的路径即为所选路径
代码实现:
#pragma once
#include <list>
#include <iostream>
using namespace std;
//直移一格消耗
const int kCost1 = 1;
//斜移一格消耗
const int kCost2 = 2;
//定义一个位置的结构体
typedef struct _Posint {
//点坐标,这里为了方便按照C++的数组来计算,x为横排,y为竖排
int x, y;
//图中计算的点和目标的距离F=G+H
int F, G, H;
//父点的坐标
struct _Posint* parent;
}Posint;
//分配一个节点(格子)
Posint* AllocPosint(int x, int y);
//初始化地图
void initAstarMap(int* _maze, int _lines, int _cols);
//通过A*算法寻找路径
list<Posint*>getPath(Posint* startPosint, Posint* endPosint);
//清理资源,程序结束后必须调用
void clearAstartMaze();
#include "Astar.h"
#include <vector>
#include <math.h>
#include <Windows.h>
//地图对应的二维数组,使用一级指针表示,和定义二维数组的行数和列数
static int* maze;
static int lines;
static int cols;
//定义开放和关闭列表
list<Posint*>openList;
list<Posint*>closeList;
static Posint* getLessFposint();
vector<Posint*>getSurroundPosint(const Posint* posint);
static int calcG(Posint* tmp_start, Posint* posint);
static int calcH(Posint* posint, Posint* end);
static int calcF(Posint* posint);
static Posint* isinList(const list<Posint*>& listA, const Posint* posint);
static bool isCanreach(const Posint* point, const Posint* target);
//搜索从起点到终点的最佳路径
static Posint* findPath(Posint* startPosint, Posint* endPosint) {
//置入起点,拷贝开辟一个节点,内外隔离
openList.push_back(AllocPosint(startPosint->x, startPosint->y));
while (!openList.empty()) {
//第一步:从开放列表中取最小F的节点
Posint* curPosint = getLessFposint();//找到F值最小的点
//第二步:把当前节点放到关闭列表中
openList.remove(curPosint);
closeList.push_back(curPosint);
//第三步:找到当前节点周围可达的节点,并计算F值
vector<Posint*>surroundPosint = getSurroundPosint(curPosint);
for (vector<Posint*>::const_iterator iter = surroundPosint.begin(); iter != surroundPosint.end(); iter++) {
Posint* target = *iter;
//对某一个格子的检查,如果它不在开放列表中,加入到开始列表,设置当前格为其父节点,计算F G H
Posint* exist = isinList(openList, target);
if (!exist) {
target->parent = curPosint;
target->G = calcG(curPosint, target);
target->H = calcH(target, endPosint);
target->F = calcF(target);
openList.push_back(target);
}
else {
int tmpG = calcG(curPosint, target);
if (tmpG < target->G) {
exist->parent = curPosint;
exist->G = tmpG;
exist->F = calcF(target);
}
delete target;
}
}
surroundPosint.clear();
Posint* resPosint = isinList(openList, endPosint);
if (resPosint) {
return resPosint;
}
}
return NULL;
}
//从开始列表中返回F值最小的节点
static Posint* getLessFposint() {
if (!openList.empty()) {
Posint* resPosint = openList.front();
for (list<Posint*>::const_iterator it = openList.begin(); it != openList.end(); it++) {
if ((*it)->F < resPosint->F) {
resPosint = *it;
}
}
return resPosint;
}
return NULL;
}
//获取周围可达到的点
vector<Posint*>getSurroundPosint(const Posint* posint) {
vector<Posint*>surPosint;
for (int x = posint->x - 1; x <= posint->x + 1; x++) {
for (int y = posint->y - 1; y <= posint->y + 1; y++) {
Posint* tmp = AllocPosint(x, y);
if (isCanreach(posint, tmp)) {
surPosint.push_back(tmp);
}
else {
delete tmp;
}
}
}
return surPosint;
}
//判断某点是否可以用于下一步判断
static bool isCanreach(const Posint* point, const Posint* target) {
if (target->x<0 || target->x>(lines - 1)
|| target->y<0 || target->y>(cols - 1)
|| maze[target->x * cols + target->y] == 1
|| maze[target->x * cols + target->y] == 2
|| (target->x == point->x && target->y == point->y)
|| isinList(closeList, target)) {
return false;
}
if (abs(point->x - target->x) + abs(point->y - target->y) == 1) {
return true;
}
else {
return false;
}
}
//判断开放或者关闭列表是否包含某点
static Posint* isinList(const list<Posint*>& listA, const Posint* posint) {
//判断某个点是否在列表中,这里不能比较指针,因为每次加入列表的新开辟的节点,只能比较坐标
list<Posint*>::const_iterator it;
for (it = listA.begin(); it != listA.end(); it++) {
if ((*it)->x == posint->x && (*it)->y == posint->y) {
return *it;
}
}
return NULL;
}
//计算F,G,H的值
static int calcG(Posint* tmp_start, Posint* posint) {
//abs为求绝对值
int extraG = (abs(posint->x - tmp_start->x) + abs(posint->y - tmp_start->y)) == 1 ? kCost1 : kCost2;
//如果是初始节点,则其父节点为空
int parentG = (posint->parent == NULL ? NULL : posint->parent->G);
return parentG + extraG;
}
static int calcH(Posint* posint, Posint* end) {
//用简单的欧几里德距离计算H,可以用多种方式实现
return (int)sqrt((double)(end->x - posint->x) * (double)(end->x - posint->x) + (double)(end->y - posint->y *
(double)(end->y - posint->y)) * kCost1);
}
static int calcF(Posint* posint) {
return posint->G + posint->H;
}
//分配一个节点(格子)
Posint* AllocPosint(int x, int y) {
Posint* tmp = new Posint;
//初始值设为0
memset(tmp, 0, sizeof(Posint));
tmp->x = x;
tmp->y = y;
return tmp;
}
//初始化地图
void initAstarMap(int* _maze, int _lines, int _cols) {
maze = _maze;
lines = _lines;
cols = _cols;
}
//通过A*算法寻找路径
list<Posint*>getPath(Posint* startPosint, Posint* endPosint) {
Posint* result = findPath(startPosint, endPosint);
list<Posint*>path;
//返回路径,如果没找到路径,返回空链表
while (result) {
path.push_front(result);
result = result->parent;
}
return path;
}
//清理资源,程序结束后必须调用
void clearAstartMaze() {
maze = NULL;
lines = 0;
cols = 0;
list<Posint*>::iterator it;
//清除openList中的元素
for (it = openList.begin(); it != openList.end();) {
delete* it;
//把it等于列表中的下一个元素
it = openList.erase(it);
}
//清除closeeList中的元素
for (it = closeList.begin(); it != closeList.end(); it++) {
delete* it;
it = closeList.erase(it);
}
}
int map[6][6] = {
0,1,0,2,0,0,
1,0,0,0,1,1,
0,0,1,0,0,0,
0,0,1,1,0,1,
1,0,0,1,0,0,
0,0,0,0,1,1
};
void test() {
initAstarMap(&map[6][6], 6, 6);
//设置起点和接受点
Posint* start = AllocPosint(6, 2);
Posint* end = AllocPosint(0, 3);
//A*算法寻找路径
list<Posint*>path = getPath(start, end);
cout << "寻路结束:" << endl;
list<Posint*>::const_iterator it;
for (it = path.begin(); it != path.end(); it++) {
Posint* cur = *it;
cout << cur->x << "," << cur->y << endl;
Sleep(300);
}
clearAstartMaze();
}
int main(void) {
test();
system("pause");
return 0;
}
运行结果: