数据结构和算法(哈希表和图(A*算法精讲))

一 、哈希表

1.1 哈希表原理精讲

哈希表-散列表,它是基于快速存取的角度设计的,也是一种典型的“空间换时间”的做法

键(key): 组员的编号如,1、5、19。。。
值(value): 组员的其它信息(包含性别、年龄和战斗力等)
索引: 数组的下标(0,1,2,3,4),用以快速定位和检索数据
哈希桶: 保存索引的数组(链表或数组),数组成员为每一个索引值相同的多个元素
哈希函数: 将组员编号映射到索引上,采用求余法,如:组员编号 19
在这里插入图片描述

1.2 哈希链表算法实现

1.2.1 结构体定义

#define DEFAULT_SIZE 16

/*哈希表元素定义*/
typedef struct _ListNode
{
	struct _ListNode* next;
	int key;
	void* data;
}ListNode;

typedef ListNode*  List;
typedef ListNode*  Element;


/*哈希表结构定义*/
typedef struct _HashTable
{
	int TableSize;
	List* Thelists;
}HashTable;

1.2.2 哈希函数

/*根据key计算索引,定位Hash桶的位置*/
int Hash(int key, int TableSize)
{
	return(key % TableSize);
}

1.2.3 链表初始化


/*初始化哈希表*/
HashTable* InitHash(int TableSize)
{
	int i = 0;
	HashTable* hTable = NULL;
	if (TableSize <= 0) {
		TableSize = DEFAULT_SIZE;
	}
	hTable = (HashTable*)malloc(sizeof(HashTable));
	if (NULL == hTable)
	{
		printf("HashTable malloc error.\n");
		return NULL;
	}
	hTable->TableSize = TableSize;

	//为Hash桶分配内存空间,其为一个指针数组
	hTable->Thelists = (List*)malloc(sizeof(List) * TableSize);
	if (NULL == hTable->Thelists)
	{
		printf("HashTable malloc error\n");
		free(hTable);
		return NULL;
	}

	//为Hash桶对应的指针数组初始化链表节点
	for (i = 0; i < TableSize; i++)
	{
		hTable->Thelists[i] = (ListNode*)malloc(sizeof(ListNode));
		if (NULL == hTable->Thelists[i])
		{
			printf("HashTable malloc error\n");
			free(hTable->Thelists);
			free(hTable);
			return NULL;
		}
		else
		{
			memset(hTable->Thelists[i], 0, sizeof(ListNode));
		}
	}
	return hTable;
}

1.2.4 插入元素

/*从哈希表中根据键值查找元素*/
Element Find(HashTable* HashTable, int key)
{
	int i = 0;
	List L = NULL;
	Element e = NULL;
	i = Hash(key, HashTable->TableSize);
	L = HashTable->Thelists[i];
	e = L->next;
	while (e != NULL && e->key != key)
		e = e->next;
	return e;
}

/*哈希表插入元素,元素为键值对*/
void Insert(HashTable* HashTable, int key, void* value)
{
	Element e = NULL, tmp = NULL;
	List L = NULL;
	e = Find(HashTable, key);
	if (NULL == e)  // 目前表中还不存在
	{
		tmp = (Element)malloc(sizeof(ListNode));
		if (NULL == tmp)
		{
			printf("malloc error\n");
			return;
		}
		// 找到属于哪一个桶
		L = HashTable->Thelists[Hash(key, HashTable->TableSize)];
		tmp->data = value;
		tmp->key = key;
		tmp->next = L->next;
		L->next = tmp;   // 经典头插法
	}
	else
		printf("the key alreadye xist\n");
}

1.2.5 查找元素

/*从哈希表中根据键值查找元素*/
Element Find(HashTable* HashTable, int key)
{
	int i = 0;
	List L = NULL;
	Element e = NULL;
	i = Hash(key, HashTable->TableSize);
	L = HashTable->Thelists[i];
	e = L->next;
	while (e != NULL && e->key != key)
		e = e->next;
	return e;
}

1.2.6 删除元素

/*哈希表删除元素,元素为键值对*/
void Delete(HashTable* HashTable, int key)
{
	Element e = NULL, last = NULL;
	List L = NULL;
	int i = Hash(key, HashTable->TableSize);
	L = HashTable->Thelists[i];
	last = L;
	e = L->next;
	while (e != NULL && e->key != key) {
		last = e;
		e = e->next;
	}
	if (e) {//如果键值对存在
		last->next = e->next;
		delete(e);
	}
}

1.2.7 提取元素数据

/*哈希表元素中提取数据*/
void* Retrieve(Element e)
{
	return e ? e->data : NULL;
}

1.2.8 销毁

/*销毁哈希表*/
void Destory(HashTable* HashTable)
{
	int i = 0;
	List L = NULL;
	Element cur = NULL, next = NULL;
	for (i = 0; i < HashTable->TableSize; i++)
	{
		L = HashTable->Thelists[i];
		cur = L->next;
		while (cur != NULL)
		{
			next = cur->next;
			free(cur);
			cur = next;
		}
		free(L);
	}
	free(HashTable->Thelists);
	free(HashTable);
}

1.3 完整代码实现

bash.h 头文件

#pragma once
#define DEFAULT_SIZE 16

/*哈希表元素定义*/
typedef struct _ListNode
{
	struct _ListNode* next;
	int key;
	void* data;
}ListNode;

typedef ListNode*  List;
typedef ListNode*  Element;


/*哈希表结构定义*/
typedef struct _HashTable
{
	int TableSize;
	List* Thelists;
}HashTable;

/*哈希函数*/
int Hash(void* key, int TableSize);

/*初始化哈希表*/
HashTable* InitHash(int ableSize);

/*哈希表插入*/
void Insert(HashTable* HashTable, int key, void* value);

/*哈希表查找*/
Element Find(HashTable* HashTable, int key);

/*哈希表销毁*/
void Destory(HashTable*  HashTable);

/*哈希表元素中提取数据*/
void* Retrieve(Element e);

bash.cpp 源文件

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"hash.h"
#include<bits/stdc++.h>

using namespace std;

/*根据key计算索引,定位Hash桶的位置*/
int Hash(int key, int TableSize)
{
	return(key % TableSize);
}

/*初始化哈希表*/
HashTable* InitHash(int TableSize)
{
	int i = 0;
	HashTable* hTable = NULL;
	if (TableSize <= 0) {
		TableSize = DEFAULT_SIZE;
	}
	hTable = (HashTable*)malloc(sizeof(HashTable));
	if (NULL == hTable)
	{
		printf("HashTable malloc error.\n");
		return NULL;
	}
	hTable->TableSize = TableSize;

	//为Hash桶分配内存空间,其为一个指针数组
	hTable->Thelists = (List*)malloc(sizeof(List) * TableSize);
	if (NULL == hTable->Thelists)
	{
		printf("HashTable malloc error\n");
		free(hTable);
		return NULL;
	}

	//为Hash桶对应的指针数组初始化链表节点
	for (i = 0; i < TableSize; i++)
	{
		hTable->Thelists[i] = (ListNode*)malloc(sizeof(ListNode));
		if (NULL == hTable->Thelists[i])
		{
			printf("HashTable malloc error\n");
			free(hTable->Thelists);
			free(hTable);
			return NULL;
		}
		else
		{
			memset(hTable->Thelists[i], 0, sizeof(ListNode));
		}
	}
	return hTable;
}

/*从哈希表中根据键值查找元素*/
Element Find(HashTable* HashTable, int key)
{
	int i = 0;
	List L = NULL;
	Element e = NULL;
	i = Hash(key, HashTable->TableSize);
	L = HashTable->Thelists[i];
	e = L->next;
	while (e != NULL && e->key != key)
		e = e->next;
	return e;
}

/*哈希表插入元素,元素为键值对*/
void Insert(HashTable* HashTable, int key, void* value)
{
	Element e = NULL, tmp = NULL;
	List L = NULL;
	e = Find(HashTable, key);
	if (NULL == e)
	{
		tmp = (Element)malloc(sizeof(ListNode));
		if (NULL == tmp)
		{
			printf("malloc error\n");
			return;
		}
		L = HashTable->Thelists[Hash(key, HashTable->TableSize)];
		tmp->data = value;
		tmp->key = key;
		tmp->next = L->next;
		L->next = tmp;
	}
	else
		printf("the key alreadye xist\n");
}

/*哈希表删除元素,元素为键值对*/
void Delete(HashTable* HashTable, int key)
{
	Element e = NULL, last = NULL;
	List L = NULL;
	int i = Hash(key, HashTable->TableSize);
	L = HashTable->Thelists[i];
	last = L;
	e = L->next;
	while (e != NULL && e->key != key) {
		last = e;
		e = e->next;
	}
	if (e) {//如果键值对存在
		last->next = e->next;
		delete(e);
	}
}

/*哈希表元素中提取数据*/
void* Retrieve(Element e)
{
	return e ? e->data : NULL;
}

/*销毁哈希表*/
void Destory(HashTable* HashTable)
{
	int i = 0;
	List L = NULL;
	Element cur = NULL, next = NULL;
	for (i = 0; i < HashTable->TableSize; i++)
	{
		L = HashTable->Thelists[i];
		cur = L->next;
		while (cur != NULL)
		{
			next = cur->next;
			free(cur);
			cur = next;
		}
		free(L);
	}
	free(HashTable->Thelists);
	free(HashTable);
}

void main(void)
{
	const char*  elems[] = { "催化","小芳","苍老师"};
	int i = 0;
	HashTable* HashTable;
	HashTable = InitHash(31);
	Insert(HashTable, 1, (char*)(elems[0]));
	Insert(HashTable, 2, (char*)(elems[1]));
	Insert(HashTable, 3, const_cast<char*>(elems[2]));
	Delete(HashTable, 1);
	for (i = 0; i < 4; i++) {
		Element e = Find(HashTable, i);
		if (e) {
			 printf("%s\n", (char *)Retrieve(e));
			//cout << (string*)Retrieve(e) << endl;
		}
		else {
			printf("Notfound[key:%d]\n", i);
		}
	}
	system("pause");
}

运行结果:
在这里插入图片描述

二、图

2.1 图的原理精讲

2.2 图的算法实现

2.2.1 结构体定义

#include <bits/stdc++.h>

using namespace std;

#define MaxSize 1024

// 与节点连接的边的定义
typedef struct _EdgeNode {
	int adjvex;  // 领接的顶点
	int weight;  // 边的权重
	struct _EdgeNode* next;  // 下一条边
}EdgeNode;


// 顶点节点
typedef struct _VertexNode {
	char data; // 节点数据
	struct _EdgeNode* first;  // 指向邻接的第一条边
}VertexNode,AdjList;

// 定义邻接表
typedef struct _AdjListGraph {
	AdjList*  adjlist;   
	int vex; // 顶点数
	int edge;// 边数
}AdjListGraph;

2.2.2 初始化创建图

// 图的初始化
void Init(AdjListGraph& G) {
	G.adjlist = new AdjList[MaxSize]; // 属于直接创建最大值链表数量
	G.vex = 0;
	G.edge = 0;
}

// 图的创建
void Great(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, v2 = 0; //保存输入顶点的字符
	int i1, i2;	// 保存顶点在数组中的下标

	cout << "请输入相关联边的顶点:" << endl;
	for (int i = 0; i < G.edge; i++) {
		cin >> v1 >> v2;   // 代表 v1 v2 之间是相互连接的
		i1 = Location(G, v1);	 // 查找 v1 这个点下标在哪里
		i2 = Location(G, v2);

		if (i1 != -1 && i2 != -1) { // 代表两个点都找到了
			EdgeNode* temp = new EdgeNode;
			temp->adjvex = i2;      // 代表,1可以找到2,所以1可以访问到2的链表
			temp->next = G.adjlist[i1].first;  // 头插法
			G.adjlist[i1].first = temp;
		}

	}

}

// 通过顶点对应的字符寻找顶点在图中的领接点
// 用来查找这个点在图的下标是多少
int Location(AdjListGraph& G, char v) {
	for (int i = 0; i < G.vex; i++) {
		if (G.adjlist[i].data == v)
		{
			return i;
		}
	}
	return -1;
}

2.3 深度优先遍历

思想:
1 首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点;
2 当没有未访问过的顶点时,则回到上一个顶点,继续试探别的顶点,直到所有的顶点都被访问过。

具体过程:
1. 首先从一个未走到过的顶点作为起始顶点,比如A顶点作为起点。
2. 沿A顶点的边去尝试访问其它未走到过的顶点,首先发现E号顶点还没有走到过,于是访问E顶点。
3. 再以E顶点作为出发点继续尝试访问其它未走到过的顶点,接下来访问D顶点。
4. 再尝试以D顶点作为出发点继续尝试访问其它未走到过的顶点。
5. 但是,此时沿D顶点的边,已经不能访问到其它未走到过的顶点,接下来返回到E顶点。
6. 返回到E顶点后,发现沿E顶点的边也不能再访问到其它未走到过的顶点。此时又回到顶点A(D->E->A),再以A顶点作为出发点继续访问其它未走到过的顶点,于是接下来访问C顶点。
7. 。。。。。。。。。。
8. 最终访问的结果是 A->E->D->C->B(这个不唯一的,看自己怎么选择路径)

// 对图上的一个顶点进行深度遍历 DFS
void DFS(AdjListGraph& G, int v) {
	int next = -1;

	if (visited[v]) { // 代表这个节点被访问过
		return;
	}
	cout << "节点未被访问过:" << G.adjlist[v].data;
	visited[v] = true;  // 设置为已访问;

	EdgeNode* temp = G.adjlist[v].first;

	//if (temp != NULL) { // 访问下一个节点
	//	next = temp->adjvex;

	//	if (visited[next] == false) {
	//		DFS(G, next);
	//	}
	//	while (temp->next) {
	//		temp = temp->next;
	//		next = temp->adjvex;
	//		if (visited[next] == false) {
	//			DFS(G, next);
	//		}
	//	}
	//}

	// 直接整合为一步解决
	while (temp) {
		next = temp->adjvex;
		temp = temp->next;

		if (visited[next] == false) {
			DFS(G, next);
		}
	}
}

// 所有顶点遍历
void DFS_Main(AdjListGraph& G) {
	for (int i =0; i < G.vex; i++) {
		if (visited[i] == false) {
			DFS(G, i);
		}
	}
}

2.3 广度优先遍历

思想:
1 首先以一个未被访问过的顶点作为起始顶点,访问其所有相邻的顶点;
2 然后对每个相邻的顶点,再访问它们相邻的未被访问过的顶点,直到所有顶点都被访问过,遍历结束。
在这里插入图片描述

// 对一个顶点进行BFS
void BFS(AdjListGraph& G,int v) {
	queue<int> q;
	int cur = -1;  // 当前处理节点
	int index = -1; // 位置

	q.push(v); // 入队

	while (!q.empty()) { // 队列非空
		cur = q.front(); // 取队列头元素

		if (visited[cur] == false) { // 当前节点未访问
			cout << G.adjlist[cur].data << "   ";
			visited[cur] = true;
		}

		q.pop(); // 队头出队,因为已经访问过了

		EdgeNode* temp = G.adjlist[cur].first;
		while (temp != NULL)
		{
			index = temp->adjvex;
			temp = temp->next;
			q.push(index);
		}
	}
}

// 所有顶点遍历广度优先遍历
void BFS_Main(AdjListGraph& G) {
	for (int i = 0; i < G.vex; i++) {
		if (visited[i] == false) {
			BFS(G, i);
		}
	}
}

在这里插入图片描述

2.4 最短路径算法

从起点开始访问所有路径,则到达终点节点的路径有多条,其中路径权值最短的一条则为最短路径。
最短路径算法: 深度优先遍历、广度优先遍历、Bellman-Ford算法、弗洛伊德算法SPFA(ShortestPathFasterAlgorithm)算法和迪杰斯特拉算法等。
在这里插入图片描述
此处时间就相当于权重,我们采用 DFS 搜索算法进行最短路搜索:
此处,和之前写的DFS遍历,就在其基础上引入了权重.
最重要一点:就是回溯 ,因为我们遍历也需要保存路径,需要将每条路就过一遍,就存在,或许有的节点再另一条路劲可能访问过了,就需要回退,设置为未访问。

以下为搜索的主要代码:

int min_weights = 0x7FFFFFFF;  // 默认最大
int steps = 0; // 已走过的步数
int path[MaxSize] = { 0 };  // 保存走过的路径
int short_path[MaxSize] = {0};  // 保存最短路劲

// 通过深度遍历,求解起点到终点最短路
void DFS(AdjListGraph& G,int start,int end, int weights) { 
	int cur = -1;  // 用来查看当前下标


	if (start == end) { // 代表已经找到终点,不用继续遍历了
		for (int i = 0; i < steps; i++) {
			cout << G.adjlist[path[i]].data << "  "; // 可能的路径
		}
		cout << endl;
		cout << "权重:  " <<weights << endl;

		if (min_weights > weights) {
			min_weights = weights;
			memcpy(short_path, path, steps*sizeof(int));
		}

	}

	visited[start] = true; // 设置为已访问
	EdgeNode* temp = G.adjlist[start].first;

	while (temp) {
		int weight = temp->weight;  // 权重
		cur = temp->adjvex;  // 当前下标

		// 防止转圈,防止进入环
		if (visited[cur] == false) { 
			visited[cur] = true;  // 当前点已访问
			path[steps++] = temp->adjvex;  // 保存路径到path数组
			DFS(G, cur, end, weights + weight);
			// 前一步探索完成后,让cur顶点为未选择状态,以便后续节点还可以访问 
			visited[cur] = false;  // 回溯
			path[--steps] = 0;
		}
		temp = temp->next;
	}
}

完整代码:

#include <bits/stdc++.h>

using namespace std;

#define MaxSize 1024

// 与节点连接的边的定义
typedef struct _EdgeNode {
	int adjvex;  // 领接的顶点
	int weight;  // 边的权重
	struct _EdgeNode* next;  // 下一条边
}EdgeNode;


// 顶点节点
typedef struct _VertexNode {
	char data; // 节点数据
	struct _EdgeNode* first;  // 指向邻接的第一条边
}VertexNode, AdjList;

// 定义邻接表
typedef struct _AdjListGraph {
	AdjList* adjlist;   //
	int vex; // 顶点数
	int edge;// 边数
}AdjListGraph;

bool visited[MaxSize]; //全局数组,用来记录节点是否已被访问

int Location(AdjListGraph& G, char v);

// 图的初始化
void Init(AdjListGraph& G) {
	G.adjlist = new AdjList[MaxSize]; // 属于直接创建最大值链表数量
	G.vex = 0;
	G.edge = 0;
}

// 图的创建
void Great(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, v2 = 0; //保存输入顶点的字符
	int i1, i2;	// 保存顶点在数组中的下标
	int weight = 0 ;  // 保存边的权重

	cout << "请输入相关联边的顶点:" << endl;
	for (int i = 0; i < G.edge; i++) {
		cin >> v1 >> v2 >> weight;   // 代表 v1 v2 之间是相互连接的,和这个边权重
		i1 = Location(G, v1);	 // 查找 v1 这个点下标在哪里
		i2 = Location(G, v2);

		if (i1 != -1 && i2 != -1) { // 代表两个点都找到了
			EdgeNode* temp = new EdgeNode;
			temp->weight = weight;
			temp->adjvex = i2;      // 代表,1可以找到2,所以1可以访问到2的链表
			temp->next = G.adjlist[i1].first;  // 头插法
			G.adjlist[i1].first = temp;
		}

	}

}

// 通过顶点对应的字符寻找顶点在图中的领接点
// 用来查找这个点在图的下标是多少
int Location(AdjListGraph& G, char v) {
	for (int i = 0; i < G.vex; i++) {
		if (G.adjlist[i].data == v)
		{
			return i;
		}
	}
	return -1;
}

int min_weights = 0x7FFFFFFF;  // 默认最大
int steps = 0; // 已走过的步数
int path[MaxSize] = { 0 };  // 保存走过的路径
int short_path[MaxSize] = {0};  // 保存最短路劲

// 通过深度遍历,求解起点到终点最短路
void DFS(AdjListGraph& G,int start,int end, int weights) { 
	int cur = -1;  // 用来查看当前下标


	if (start == end) { // 代表已经找到终点,不用继续遍历了
		for (int i = 0; i < steps; i++) {
			cout << G.adjlist[path[i]].data << "  "; // 可能的路径
		}
		cout << endl;
		cout << "权重:  " <<weights << endl;

		if (min_weights > weights) {
			min_weights = weights;
			memcpy(short_path, path, steps*sizeof(int));
		}

	}

	visited[start] = true; // 设置为已访问
	EdgeNode* temp = G.adjlist[start].first;

	while (temp) {
		int weight = temp->weight;  // 权重
		cur = temp->adjvex;  // 当前下标

		// 防止转圈,防止进入环
		if (visited[cur] == false) { 
			visited[cur] = true;  // 当前点已访问
			path[steps++] = temp->adjvex;  // 保存路径到path数组
			DFS(G, cur, end, weights + weight);
			// 前一步探索完成后,让cur顶点为未选择状态,以便后续节点还可以访问 
			visited[cur] = false;  // 回溯
			path[--steps] = 0;
		}
		temp = temp->next;

	}

}



int main() {

	AdjListGraph G;
	// 初始化
	Init(G);

	//创建图
	Great(G);

	// 深度遍历
	
	char start, end;
	cout << "输入查询起点终点" << endl;
	cin >> start >> end;

	// 求两点间的最短路径
	path[steps++] = Location(G, start);
	DFS(G, Location(G, start), Location(G, end), 0);

	cout << "最小路劲长度:    " << min_weights << endl;
	cout << "路径:";
	int i = 0;
	while (i < MaxSize && short_path[i]) {
		cout << G.adjlist[short_path[i]].data << "   ";
		i++;
	}
	cout << endl;

	system("pause");
	return 0;

}

测试结果:
在这里插入图片描述

2.5 图的项目案例 (A* 算法)(非常重要)

2.5.1 A*算法原理

什么是A* 算法?

A算法较之传统的路径规划算法,实时性更高、灵活性更强,寻路结果更加接近人工选择的路径结果. A寻路算法并不是找到最优路径,只是找到相对近的路径,因为找最优要把所有可行路径都找出来进行对比,消耗性能太大,寻路效果只要相对近路径就行了*

寻路步骤

  1. 从起点开始, 把它作为待处理的方格存入一个预测可达的节点列表,简称openList, 即把起点放入“预测可达节点列表”,
    可达节点列表openList就是一个等待检查方格的列表。

  2. 寻找openList中F值最小的点min(一开始只有起点)周围可以到达的方格(可到达的意思是其不是障碍物,也不存
    在关闭列表中的方格,即不是已走过的方格)。计算min周围可到达的方格的F值。将还没在openList中点放入其中, 并
    设置它们的"父方格"为点min,表示他们的上一步是经过min到达的。如果min下一步某个可到达的方格已经在openList
    列表那么并且经min点它的F值更优,则修改F值并把其"父方格"设为点min。

  3. 把2中的点min从"开启列表"中删除并存入"关闭列表"closeList中,closeList中存放的都是不需要再次检查的方格。如
    果2中点min不是终点并且开启列表的数量大于零,那么继续从第2步开始。如果是终点执行第4步,如果openList列
    表数量为零,那么就是找不到有效路径。

  4. 如果第3步中min 是终点,则结束查找,直接追溯父节点到起点的路径即为所选路径。

2.5.2 A*算法图示,一步一步讲解

具体寻路步步骤图示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.5.3 A*算法完整代码

AStar.h

// AStar.h

#pragma once

#include <list>
#include <vector>

using namespace std;

const int kCost1 = 10; // 直移一个消耗
const int kCost2 = 20; // 斜移一个消耗

typedef struct _point {
	int x, y;		// 点坐标,x为横,y为竖
	int F, G, H;	// F= G+H , 代表到终点总距离
	struct _point* parent;	// parent 坐标
}point;

/* 分配一个节点(格子) */
point* AllocPoint(int x, int y);

/* 初始化地图 */
void InitAstarMaze(int* _maze, int _lines, int _colums);

/* 通过A*算法寻找路径 */
list<point*>GetPath(point* startPoint, point* endPoint);

/* 清理资源、结束后调用 */
void ClearAstarMaze();

AStar.cpp

// AStar.cpp

#include<bits/stdc++.h>
#include"AStar.h"


static int* maze;	//迷宫对应二维数组,一维指针表示
static int cols;  // 二维数组对应列数
static int lines; // 二维数组对应行数

static list<point*> openList;	//	开放列表
static list<point*> closeList;	//	关闭列表

/* 搜索从起点到终点的最佳路径 */
static point* findPath(point* startPoint, point* endPoint);

/* 从openList里面获取最小的节点 */
static point* getLeastPoint();	

/* 获取当前节点的周边节点 */
static vector<point*> getSurroundPoints(const point* curPoint);

/* 判断某点是否可以用于下一步判断 */
static bool isCanreach(const point* curPoint, point* temp);

/* 判断开启/关闭列表中是否包含某点 */
static point* isInList(const std::list<point*>& list, const point* cpoint);

//计算FGH值
static int calG(point* temp_start, point* point);
static int calH(point* cpoint, point* end);
static int calF(point* cpoint);

/* 分配一个节点(格子) */
point* AllocPoint(int x, int y) {
	point* temp = new point;

	memset(temp, 0, sizeof(point));  // 初始值清0

	temp->x = x;
	temp->y = y;
	return temp;
}


/* 初始化地图 */
void InitAstarMaze(int* _maze, int _lines, int _colums) {
	maze = _maze;
	lines = _lines;
	cols = _colums;
}
 

/* 通过A*算法寻找路径 */
list<point*> GetPath(point* startPoint, point* endPoint) {
	point* result = findPath(startPoint, endPoint);

	list<point*> path; // 返回的路径

	// 如果没找到路径,则返回空的列表
	while (result) {
		path.push_front(result);
		result = result->parent;   // 相当于最后一个节点逆转,比如原本path 是 CBA,逆转为ABC
	}
	return path;  
}

/* 搜索从起点到终点的最佳路径 */
static point* findPath(point* startPoint, point* endPoint) {

	// 1. 初始节点放入 OpenList
	openList.push_back(AllocPoint(startPoint->x,startPoint->y));

	while (!openList.empty()) {
		// 2. 寻找openList中最小值
		point* curPoint = getLeastPoint(); // 取到最小初始值

		// 3. 把当前节点放在关闭列表中
		openList.remove(curPoint);
		closeList.push_back(curPoint);

		// 4. 找到当前节点可到达的周围节点,并计算F值
		vector<point*> surroundPoints = getSurroundPoints(curPoint);

		vector<point*>::const_iterator it;
		for (it = surroundPoints.begin(); it != surroundPoints.end(); it++) {
			point* target = *it;

			// 对某个格子,如果它不在开放列表中,加入到开启列表,设置当前格为其父节点,计算FGH
			point* exist = isInList(openList, target);

			if (!exist) {
				target->parent = curPoint;
				target->G = calG(curPoint, target);
				target->H = calH(target, endPoint);
				target->F = calF(target);

				openList.push_back(target);
			}
			// 如果已经存在,就从新计算G值,然后看大小更新
			else {
				int tempG = calG(curPoint, target);
				if (tempG < target->G) {
					exist->parent = curPoint;
					exist->G = tempG;
					exist->F = calF(target);
				}
				delete target;
			}
		}
		// 结束条件
		surroundPoints.clear();

		point* resPoint = isInList(openList, endPoint); // end 已经在openList,代表已经跑到终点
		if (resPoint) {
			return resPoint;
		}
	}
	return NULL;
}

/* 从openList里面获取最小的节点 */
static point* getLeastPoint() {
	if (!openList.empty()) {
		point* resPoint = openList.front();
		list<point* >::const_iterator it;  // 迭代器
		for (it = openList.begin(); it != openList.end(); it++) {
			point* p = *it;  // 如何获取迭代器数据? 加 *
			if (p->F < resPoint->F) {
				resPoint = *it;
			}
		}
		return resPoint;
	}
	return NULL;
}

/* 获取当前节点的周边节点 */
static vector<point*> getSurroundPoints(const point* curPoint) {
	vector<point* > surroundPoints;

	for (int x = curPoint->x - 1; x <= curPoint->x + 1; x++) {
		for (int y = curPoint->y - 1; y <= curPoint->y + 1; y++) { // 周围8个点
			point* temp = AllocPoint(x, y);
			if (isCanreach(curPoint, temp)) { // 只需要 8个点中的上下左右,斜角此处不考虑
				surroundPoints.push_back(temp);
			}
			else {
				delete temp;
			}
		}
	}
	return surroundPoints;
}

/* 判断某点是否可以用于下一步判断 */
static bool isCanreach(const point* curPoint, point* temp) { // curPoint 当前点,temp 下一个点
	if (temp->x<0 || temp->x>(lines - 1)         // x超过范围
		|| temp->y<0 || temp->y>(cols - 1)		// y超过范围
		|| maze[temp->x * cols + temp->y] == 1  // 障碍物
		|| maze[temp->x * cols + temp->y] == 2  // 障碍物
		|| (temp->x == curPoint->x && temp->y == curPoint->y) // 点从重叠
		|| isInList(closeList, temp)) {
		return false;
	}
	// abs 求绝对值
	if (abs(curPoint->x - temp->x) + abs(curPoint->y - temp->y) == 1) {
		return true;
	}
	else {
		return false;
	}
}

/* 判断开启/关闭列表中是否包含某点 */
static point* isInList(const std::list<point*>& list, const point* cpoint){
	//判断某个节点是否在列表中,这里不能比较指针,因为每次加入列表是新开辟的节点,只能比较坐标
	std::list<point*>::const_iterator itor;

	for (itor = list.begin(); itor != list.end(); itor++) {
		if ((*itor)->x == cpoint->x && (*itor)->y == cpoint->y) {
			return *itor;
		}
	}
	return NULL;
}


//计算FGH值
static int calG(point* temp_start, point* cpoint)
{
	// 判断走直线还是斜线,此题只需要直线,斜线以后可以这么写
	int extraG = (abs(cpoint->x - temp_start->x) + abs(cpoint->y - temp_start->y)) == 1 ? kCost1 : kCost2;
	int parentG = (cpoint->parent == NULL ? NULL : cpoint->parent->G); //如果是初始节点,则其父节点是空
		return parentG + extraG;
}
static int calH(point* cpoint, point* end) {
	// 就是该点到终点,看成一个矩形,我们就看直线距离,这个看自己怎么算,都可以
	return (int)sqrt((double)(end->x - cpoint->x) * (double)(end->x - cpoint->x) +
		(double)(end->y - cpoint->y) * (double)(end->y - cpoint->y));
}
static int calF(point* cpoint) {
	return cpoint->G + cpoint->H;
}



/* 清理资源、结束后调用 */
void ClearAstarMaze() {
	maze = NULL;
	lines = 0;
	cols = 0;

	std::list<point *>::iterator itor;

	//清除openList中的元素
	cout << openList.size() << endl;
	for (itor = openList.begin(); itor != openList.end();) {
		delete* itor;
		itor = openList.erase(itor);// 获取到下一个节点
	}

	//清理closeList中的元素
	cout << closeList.size() << endl;
	for (itor = closeList.begin(); itor != closeList.end();) {
		delete* itor;
		itor = closeList.erase(itor);
	}
}

main.cpp

// main.cpp

#include "AStar.h"
#include<bits/stdc++.h>
#include<windows.h>

using namespace std;

const int ROWS = 13;
const int COLS = 13;

//定义地图数组
int maps[13][13] = {//二维数组在内存顺序存储的
 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,},
 {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
 {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
 {0, 1, 0, 1, 0, 1, 2, 1, 0, 1, 0, 1, 0,},
 {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
 {0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,},
 {0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,},
 {2, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 2,},
 {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,},
 {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
 {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
 {0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0,},
 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}
};

void PrintMap(int maps[ROWS][COLS]) {
	for (int i = 0; i < ROWS; ++i) {
		for (int j = 0; j < COLS; ++j) {
			if (maps[i][j] == 0) {
				std::cout << '0';  // 通路用空格表示
			}
			else {
				std::cout << '*';  // 墙用星号表示
			}
		}
		std::cout << '\n';
	}
}

void AStarText() {
	InitAstarMaze(&maps[0][0], 13, 13);

	PrintMap(maps);

	// 设置起始地址 和 终点
	point* start = AllocPoint(12, 4);
	point* end = AllocPoint(0, 0);


	// A* 算法寻找路劲
	list<point*> path = GetPath(start, end);

	cout << "寻找结果" << endl;

	list<point*>::const_iterator it;
	for (it = path.begin(); it != path.end(); it++) {
		point* cur = *it;
		cout << '(' << cur->x << ',' << cur->y << ')' << endl;

		//delete cur;
		//it = path.erase(it);

		Sleep(800);
	}
	ClearAstarMaze();
}


int main() {
	AStarText();

	system("pause");
	return 0;
}

测试结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/547577.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

并查集练习

前言&#xff1a; 关于并查集的一些训练题。 正文&#xff1a; 1.亲戚&#xff1a; #include<bits/stdc.h> using namespace std; const int N5005; int fa[N]; int find(int x){if(xfa[x])return x;return fa[x]find(fa[x]); } void merge(int x,int y){fa[find(x)]fi…

MajorDoMo thumb.php 未授权RCE漏洞复现(CNVD-2024-02175)

0x01 产品简介 MajorDoMo是MajorDoMo社区的一个开源DIY智能家居自动化平台。 0x02 漏洞概述 MajorDoMo /modules/thumb/thumb.php接口处存在远程命令执行漏洞&#xff0c;未经身份验证的攻击者可利用此漏洞执行任意指令&#xff0c;获取服务器权限。 0x03 影响范围 MajorD…

消息队列和分布式消息队列

文章目录 分析系统现状不足中间件消息队列什么是消息队列&#xff1f;应用场景消息队列的模型为什么不直接传输&#xff0c;而要用消息队列&#xff1f;为什么要用消息队列&#xff1f;消息队列的缺点&#xff1f; 分布式消息队列分布式消息队列的优势&#xff1f;消息队列应用…

数字晶体管数字三极管

数字晶体管 指内部集成了电阻的三极管&#xff0c;有PNP和NPN型&#xff0c;也有双管&#xff0c;双管有3种形式&#xff0c;其中一种是PNPNPN。下面以双NPN示例&#xff0c;好处是外面没有电阻&#xff0c;批量应用时&#xff0c;焊点费用就可省下不少。双NPN的用在串口自动下…

SSH客户端工具输入目标地址端口远程失败故障原因和解决方案

问题表现&#xff1a;SSH客户端工具输入目标地址端口远程失败时&#xff0c;出现ssh client 报 algorithm negotiation failed的异常信息。 使用SSH Secure Shell Client连接Linux服务器的SSH的时候有时会出现错误提示信息&#xff1a;ssh algorithm negotiation failed。这是…

HarmonyOS NEXT星河版之实战知乎App评论功能

文章目录 一、目标完成页面二、实战2.1 定义数据2.2 mock数据2.3 封装顶部标题栏2.4 封装评论Item2.5 定义回复组件2.6 主页面 三、小结 一、目标完成页面 二、实战 2.1 定义数据 export interface ReplyItem {avatar: ResourceStr // 头像author: string // 作者id: number …

【数据结构|C语言版】顺序表应用

前言1. 基于动态顺序表实现通讯录1.1 通讯录功能1.2 代码实现1.2.1 SeqList.h1.2.2 SeqList.c1.2.3 Contact.h1.2.4 Contact.c1.2.5 test.c 1.3 控制台测试1.3.1 添加联系人1.3.2 删除联系人1.3.3 修改联系人1.3.4 查找联系人1.3.5 清空通讯录1.3.6 通讯录读档和存档 2. 好题测…

Day 41:动态规划 LeedCode 343. 整数拆分 96.不同的二叉搜索树

343. 整数拆分 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 思路: 1.确定dp数组&#xff0…

支付系统核心逻辑 — — 状态机(JavaGolang版本)

支付系统核心逻辑 — — 状态机 代码地址&#xff1a;https://github.com/ziyifast/ziyifast-code_instruction/tree/main/state_machine_demo 1 概念&#xff1a;FSM&#xff08;有限状态机&#xff09;&#xff0c;模式之间转换 状态机&#xff0c;也叫有限状态机&#xff08…

OpenWrt 多拨负载均衡不起作用

检查 负载均衡->规则->Https->粘滞模式 是否启动&#xff0c;设置为 否 如果设置为是&#xff0c;那么根据官方描述&#xff1a; 来自相同源 IP 的流量&#xff0c;如果已经匹配过此规则并且在粘滞超时时间内&#xff0c;将会使用相同的 WAN 接口 意思就是如果你同一个…

R语言 并行计算makeCluster报错

问题&#xff1a;使用parallel包进行并行计算&#xff0c; cl <- makeCluster(detectCores()) 出现以下问题&#xff1a; 解决方式&#xff1a;用makeClusterPSOCK命令代替即可 library("future") cl <- makeClusterPSOCK(124, revtunnel TRUE, outfile &…

【QT入门】Qt自定义控件与样式设计之自定义QTabWidget实现tab在左,文本水平的效果

往期回顾 【QT入门】Qt自定义控件与样式设计之控件提升与自定义控件-CSDN博客 【QT入门】Qt自定义控件与样式设计之鼠标相对、绝对位置、窗口位置、控件位置-CSDN博客【QT入门】Qt自定义控件与样式设计之自定义QLineEdit实现搜索编辑框-CSDN博客 【QT入门】Qt自定义控件与样式…

(一)基于IDEA的JAVA基础16(end)

二维数组 二维数组就是数组里面再放一个数组 语法: <数据类型> [] [] 数组名&#xff1b; 或: <数据类型> 数组名 [] []&#xff1b; 比如这里有5个单位&#xff0c;每个单位员工有20个&#xff0c;他们都在忙几个相同的项目&#xff0c;现在要对某项项目进行操…

html、css、QQ音乐移动端静态页面,资源免费分享,可作为参考,提供InsCode在线运行演示

CSDN将我上传的免费资源私自变成VIP专享资源&#xff0c;且作为作者的我不可修改为免费资源&#xff0c;不可删除&#xff0c;寻找客服无果&#xff0c;很愤怒&#xff0c;&#xff08;我发布免费资源就是希望大家能免费一起用、一起学习&#xff09;&#xff0c;接下来继续寻找…

Linux进阶篇:centos7搭建jdk环境

Linux服务搭建篇&#xff1a;centos7搭建jdk环境 本文主要介绍的是如何是Linux环境下安装JDK的&#xff0c;关于jdk的概念就不做赘述了&#xff0c;相信大家都有所耳闻了&#xff0c;Linux环境下&#xff0c;很多时候也离不开Java的&#xff0c;下面笔者就和大家一起分享如何jd…

【数据结构|C语言版】单链表应用

前言1. 基于单链表实现通讯录1.1 知识要求1.2 功能要求 2. 代码总结2.1 SeqList.h2.2 SeqList.c2.3 Contact.h2.4 Contact.c2.5 test.c 后言 上期回顾&#xff1a;【数据结构|C语言版】单链表 前言 各位小伙伴大家好&#xff01;上期小编讲解了单链表相关知识&#xff0c;在此…

SpringCloudAlibaba真的不行了吗?

Spring Cloud Alibaba 作为SpringCloudAlibaba微服务架构实战派上下册和RocketMQ消息中间件实战派上下册的作者胡弦&#xff0c;我想和技术人聊一下&#xff0c;阿里巴巴开源的SpringCloudAlibaba真的不行了吗&#xff1f; 目前SpringCloudAlibaba最新的版本为2023.0.0.0-RC1 …

蓝桥杯2024年第十五届省赛

E:宝石组合 根据给的公式化简后变为gcd(a,b,c)根据算数基本定理&#xff0c;推一下就可以了 然后我们对1到mx的树求约数&#xff0c;并记录约数的次数&#xff0c;我们选择一个最大的且次数大于等3的就是gcd int mx; vector<int> g[N]; vector<int> cnt[N]; int…

flutter material中的Icon组件的IconData 查阅

查阅 https://fonts.google.com/icons?selectedMaterialSymbolsOutlined:expand_less:FILL0;wght300;GRAD0;opsz24&icon.platformandroidhttps://fonts.google.com/icons?selectedMaterialSymbolsOutlined:expand_less:FILL0;wght300;GRAD0;opsz24&icon.platformand…

Java的maven项目导入本地jar包的三种方式

一、使用本地jar包 在项目中创建一个lib文件夹&#xff0c;将想要使用的本地jar包放进去 然后直接在pom.xml中添加下列依赖&#xff08;项目协作推荐&#xff09; <dependency><groupId>com.fpl</groupId><artifactId>spring</artifactId><…