A*算法(寻路)

A*算法(寻路)

一.什么是A*算法

A*搜寻算法俗称A星算法。A*算法是比较流行的启发式搜索算法之一,被广泛应用于路径优化领域。它的独特之处是检查最短路径中每个可能的节点时引入了全局信息,对当前节点距终点的距离做出估计,并作为评价该节点处于最短路线上的可能性的量度。

A*改变它自己行为的能力基于启发式代价函数,启发式函数在游戏中非常有用。在速度和精确度之间取得折衷将会让你的游戏运行得更快。在很多游戏中,你并不真正需要得到最好的路径,仅需要近似的就足够了。而你需要什么则取决于游戏中发生着什么,或者运行游戏的机器有多快。 假设你的游戏有两种地形,平原和山地,在平原中的移动代价是1而在山地的是3,那么A星算法就会认为在平地上可以进行三倍于山地的距离进行等价搜寻。 这是因为有可能有一条沿着平原到山地的路径。把两个邻接点之间的评估距离设为1.5可以加速A*的搜索过程。然后A*会将3和1.5比较,这并不比把3和1比较差。然而,在山地上行动有时可能会优于绕过山脚下进行行动。所以花费更多时间寻找一个绕过山的算法并不经常是可靠的。 同样的,想要达成这样的目标,你可以通过减少在山脚下的搜索行为来打到提高A星算法的运行速率。若想如此可以将A星算法的山地行动耗费从3调整为2即可。这两种方法都会给出可靠地行动策略。

可采纳性

在一些问题的求解过程中,如果存在最短路径,无论在什么情况之下,如果一个搜索算法都能够保证找到这条最短路径,则称这样的搜索算法就具有可采纳性。 [2]

单调性

A*算法扩展的所有节点的序列的f值是递增的,那么它最先生成的路径一定就是最短的。

信息性

如何判断两种策略哪一个更好呢?具有的启发信息越多,搜索的状态就越少,越容易找到最短路径,这样的策略就更好。在两个启发策略中,如果有h(n1)

 h(n2),则表示策略h:比h,具有更多的启发信息,同时h(n)越大表示它所搜索的空间数就越少。但是同时需要注意的是:信息越多就需要更多的计算时间,从而有可能抵消因为信息多而减少的搜索空间所带来的好处。

缺陷

A*算法进行下一步将要走的节点的搜索的时候,每次都是选择F值最小的节点,因此找到的是最优路径。但是正因为如此A*算法每次都要扩展当前节点的全部后继节点,运用启发函数计算它们的F值,然后选择F值最小的节点作为下一步走的节点。在这个过程中,OPEN表需要保存大量的节点信息,不仅存储量大是一个问题,而且在查找F值最小的节点时,需要查询的节点也非常多,当然就非常耗时,这个问题就非常严重了。再加上如果游戏地图庞大,路径比较复杂,路径搜索过程则可能要计算成千上万的节点,计算量非常巨大。因此,搜索一条路径需要一定的时间,这就意味着游戏运行速度降低。

二.实践

index.html

<!DOCTYPE html>
<html>
<head>
    <title>A* Pathfinding Demo</title>
    <style>
        #grid {
            display: grid;
            grid-template-columns: repeat(10, 50px);
            justify-content: center;
            margin-top: 50px;
        }

        .cell {
            border: 1px solid gray;
            height: 50px;
            width: 50px;
            background-color: white;
        }

        .cell.start {
            background-color: green;
        }

        .cell.end {
            background-color: red;
        }

        .cell.wall {
            background-color: black;
        }

        .cell.path {
            background-color: yellow;
        }
    </style>
</head>
<body>
<div id="grid"></div>
<button type="button" id="clearCell">清空点位</button>
<button type="button" id="seekingWay">执行</button>
<script>
	const CELL_COLORS = {
	    DEFAULT: "#fff",
	    WALL: "#000",
	    START: "#00f",
	    END: "#f00",
	    OPEN: "#0f0",
	    CLOSED: "#f0f",
	    PATH: "#ff0",
	};
</script>
<script src="./Cell.js"></script>
<script src="./Grid.js"></script>
<script src="./AStar.js"></script>
<script src="./index.js"></script>
</body>
</html>

Cell.js

/**
 * @description: 创建Cell
 * @method: Cell
 * @author: sgjy
 * @date: 2024/3/25
 * @lastEditors: sgjy
 */
class Cell {
	constructor(row, col) {
		this.row = row;
		this.col = col;
		this.isWall = false;
		this.g = 0;
		this.h = 0;
		this.f = 0;
		this.previous = null;
		this.hasStartCell = false;
		this.hasEndCell = false;
		this.element = this.createElement();
		// this.element.addEventListener("click", this.toggleWall.bind(this));
	}

	createElement() {
		const element = document.createElement("div");
		element.className = "cell";
		return element;
	}

	// toggleWall() {
	// 	this.isWall = !this.isWall;
	// 	this.element.style.backgroundColor = this.isWall ? CELL_COLORS.WALL : CELL_COLORS.DEFAULT;
	// }

	getNeighbors() {
		const neighbors = [];
		const offsets = [{
				row: -1,
				col: 0
			}, // 上方格子
			{
				row: 1,
				col: 0
			}, // 下方格子
			{
				row: 0,
				col: -1
			}, // 左侧格子
			{
				row: 0,
				col: 1
			}, // 右侧格子
		];
		for (const offset of offsets) {
			const neighborRow = this.row + offset.row;
			const neighborCol = this.col + offset.col;
			if (neighborRow >= 0 && neighborRow < grid.rows && neighborCol >= 0 && neighborCol < grid.cols) {
				neighbors.push(grid.cells[neighborRow][neighborCol]);
			}
		}
		return neighbors;
	}
}

Grid.js

/**
 * @description: 创建Grid
 * @method: Grid
 * @author: sgjy
 * @date: 2024/3/25
 * @lastEditors: sgjy
 */
class Grid {
    constructor(rows, cols, containerId) {
        this.rows = rows;
        this.cols = cols;
        this.container = document.getElementById(containerId);
        this.cells = [];
        this.startCell = null;
        this.endCell = null;
    }

    createCells() {
        for (let row = 0; row < this.rows; row++) {
            const rowCells = [];
            for (let col = 0; col < this.cols; col++) {
                const cell = new Cell(row, col);
				cell.element.addEventListener('click', () => {
					this.addStartAndEndCell(cell);
				});
                rowCells.push(cell);
                this.container.appendChild(cell.element);
            }
            this.cells.push(rowCells);
        }
    }

    clear() {
        for (const rowCells of this.cells) {
            for (const cell of rowCells) {
                if (cell) {
                    cell.isWall = false;
                    cell.g = 0;
                    cell.h = 0;
                    cell.f = 0;
                    cell.previous = null;
                    cell.element.style.backgroundColor = CELL_COLORS.DEFAULT;
                }
            }
        }
		this.startCell = null;
		this.endCell = null;
    }

    setStartCell(cell) {
        this.startCell = cell;
        cell.element.style.backgroundColor = CELL_COLORS.START;
    }

    setEndCell(cell) {
        this.endCell = cell;
        cell.element.style.backgroundColor = CELL_COLORS.END;
    }
	
	getStartCell() {
		return this.startCell;
	}
	
	getEndCell() {
		return this.endCell;
	}
	
	addStartAndEndCell(cell) {
		console.log(cell);
		if(!this.startCell) {
			this.setStartCell(cell);
		} else if (!this.endCell && cell !== this.startCell) {
			this.setEndCell(cell);
		}
	}
}

index.js

const grid = createGrid();
let startCell = null; // 起点
let endCell = null; // 终点
// 点击格子事件处理函数
/* function handleCellClick(row, col, grid) {
  const cell = grid[row][col];
  if (!startCell) {
    // 如果起点为空,则设置为点击的格子,并更新样式为红色
    startCell = cell;
    startCell.element.style.backgroundColor = CELL_COLORS.START;
	grid.setStartCell(startCell);
  } else if (!endCell && cell !== startCell) {
    // 如果终点为空且点击的格子不是起点,则设置为终点,并更新样式为红色
    endCell = cell;
    endCell.element.style.backgroundColor = CELL_COLORS.END;
    grid.setEndCell(endCell);
	// 创建 A* 实例并执行搜索
	const astar = new AStar(grid);
	astar.search(startCell, endCell);
  }
} */
// 创建网格
function createGrid(rows = 10, cols = 10, containerId = 'grid') {
    const grid = new Grid(rows, cols, containerId);
    grid.createCells();
	return grid;
}

// 获取按钮元素
var button = document.getElementById('clearCell');
 
// 为按钮添加click事件监听
button.addEventListener('click', function() {
    console.log('111');
	grid.clear();
});

// 执行寻路
// 获取按钮元素
var button = document.getElementById('seekingWay');
 
// 为按钮添加click事件监听
button.addEventListener('click', function() {
    // 设置起点
	 startCell = grid.getStartCell();
	 // 设置终点
	 endCell = grid.getEndCell();
	 // 创建 A* 实例并执行搜索
	 const astar = new AStar(grid);
	 astar.search(startCell, endCell);
});

  • 32
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值