astar 注释
This commit is contained in:
1
demo/libs/framework/framework.d.ts
vendored
1
demo/libs/framework/framework.d.ts
vendored
@@ -61,6 +61,7 @@ declare class PriorityQueue<T extends PriorityQueueNode> {
|
|||||||
constructor(maxNodes: number);
|
constructor(maxNodes: number);
|
||||||
clear(): void;
|
clear(): void;
|
||||||
readonly count: number;
|
readonly count: number;
|
||||||
|
readonly maxSize: number;
|
||||||
contains(node: T): boolean;
|
contains(node: T): boolean;
|
||||||
enqueue(node: T, priority: number): void;
|
enqueue(node: T, priority: number): void;
|
||||||
dequeue(): T;
|
dequeue(): T;
|
||||||
|
|||||||
@@ -418,7 +418,22 @@ var PriorityQueue = (function () {
|
|||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true
|
configurable: true
|
||||||
});
|
});
|
||||||
|
Object.defineProperty(PriorityQueue.prototype, "maxSize", {
|
||||||
|
get: function () {
|
||||||
|
return this._nodes.length - 1;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
PriorityQueue.prototype.contains = function (node) {
|
PriorityQueue.prototype.contains = function (node) {
|
||||||
|
if (!node) {
|
||||||
|
console.error("node cannot be null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (node.queueIndex < 0 || node.queueIndex >= this._nodes.length) {
|
||||||
|
console.error("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return (this._nodes[node.queueIndex] == node);
|
return (this._nodes[node.queueIndex] == node);
|
||||||
};
|
};
|
||||||
PriorityQueue.prototype.enqueue = function (node, priority) {
|
PriorityQueue.prototype.enqueue = function (node, priority) {
|
||||||
|
|||||||
2
demo/libs/framework/framework.min.js
vendored
2
demo/libs/framework/framework.min.js
vendored
File diff suppressed because one or more lines are too long
1
source/bin/framework.d.ts
vendored
1
source/bin/framework.d.ts
vendored
@@ -61,6 +61,7 @@ declare class PriorityQueue<T extends PriorityQueueNode> {
|
|||||||
constructor(maxNodes: number);
|
constructor(maxNodes: number);
|
||||||
clear(): void;
|
clear(): void;
|
||||||
readonly count: number;
|
readonly count: number;
|
||||||
|
readonly maxSize: number;
|
||||||
contains(node: T): boolean;
|
contains(node: T): boolean;
|
||||||
enqueue(node: T, priority: number): void;
|
enqueue(node: T, priority: number): void;
|
||||||
dequeue(): T;
|
dequeue(): T;
|
||||||
|
|||||||
@@ -418,7 +418,22 @@ var PriorityQueue = (function () {
|
|||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true
|
configurable: true
|
||||||
});
|
});
|
||||||
|
Object.defineProperty(PriorityQueue.prototype, "maxSize", {
|
||||||
|
get: function () {
|
||||||
|
return this._nodes.length - 1;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
PriorityQueue.prototype.contains = function (node) {
|
PriorityQueue.prototype.contains = function (node) {
|
||||||
|
if (!node) {
|
||||||
|
console.error("node cannot be null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (node.queueIndex < 0 || node.queueIndex >= this._nodes.length) {
|
||||||
|
console.error("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return (this._nodes[node.queueIndex] == node);
|
return (this._nodes[node.queueIndex] == node);
|
||||||
};
|
};
|
||||||
PriorityQueue.prototype.enqueue = function (node, priority) {
|
PriorityQueue.prototype.enqueue = function (node, priority) {
|
||||||
|
|||||||
2
source/bin/framework.min.js
vendored
2
source/bin/framework.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -3,6 +3,12 @@
|
|||||||
* 计算路径给定的IAstarGraph和开始/目标位置
|
* 计算路径给定的IAstarGraph和开始/目标位置
|
||||||
*/
|
*/
|
||||||
class AStarPathfinder {
|
class AStarPathfinder {
|
||||||
|
/**
|
||||||
|
* 尽可能从开始到目标找到一条路径。如果没有找到路径,则返回null。
|
||||||
|
* @param graph
|
||||||
|
* @param start
|
||||||
|
* @param goal
|
||||||
|
*/
|
||||||
public static search<T>(graph: IAstarGraph<T>, start: T, goal: T){
|
public static search<T>(graph: IAstarGraph<T>, start: T, goal: T){
|
||||||
let foundPath = false;
|
let foundPath = false;
|
||||||
let cameFrom = new Map<T, T>();
|
let cameFrom = new Map<T, T>();
|
||||||
@@ -60,6 +66,12 @@ class AStarPathfinder {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从cameFrom字典重新构造路径
|
||||||
|
* @param cameFrom
|
||||||
|
* @param start
|
||||||
|
* @param goal
|
||||||
|
*/
|
||||||
public static recontructPath<T>(cameFrom: Map<T, T>, start: T, goal: T): T[]{
|
public static recontructPath<T>(cameFrom: Map<T, T>, start: T, goal: T): T[]{
|
||||||
let path = [];
|
let path = [];
|
||||||
let current = goal;
|
let current = goal;
|
||||||
|
|||||||
@@ -33,13 +33,18 @@ class AstarGridGraph implements IAstarGraph<Vector2> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查节点是否可以通过。墙壁是不可逾越的。
|
* 检查节点是否可以通过。walls是不可逾越的。
|
||||||
* @param node
|
* @param node
|
||||||
*/
|
*/
|
||||||
public isNodePassable(node: Vector2): boolean {
|
public isNodePassable(node: Vector2): boolean {
|
||||||
return !this.walls.firstOrDefault(wall => JSON.stringify(wall) == JSON.stringify(node));
|
return !this.walls.firstOrDefault(wall => JSON.stringify(wall) == JSON.stringify(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用AStarPathfinder.search的快捷方式
|
||||||
|
* @param start
|
||||||
|
* @param goal
|
||||||
|
*/
|
||||||
public search(start: Vector2, goal: Vector2){
|
public search(start: Vector2, goal: Vector2){
|
||||||
return AStarPathfinder.search(this, start, goal);
|
return AStarPathfinder.search(this, start, goal);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* graph的接口,可以提供给AstarPathfinder.search方法
|
||||||
|
*/
|
||||||
interface IAstarGraph<T> {
|
interface IAstarGraph<T> {
|
||||||
|
/**
|
||||||
|
* getNeighbors方法应该返回从传入的节点可以到达的任何相邻节点
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
getNeighbors(node: T): Array<T>;
|
getNeighbors(node: T): Array<T>;
|
||||||
|
/**
|
||||||
|
* 计算从从from到to的成本
|
||||||
|
* @param from
|
||||||
|
* @param to
|
||||||
|
*/
|
||||||
cost(from: T, to: T): number;
|
cost(from: T, to: T): number;
|
||||||
|
/**
|
||||||
|
* 计算从node到to的启发式。参见WeightedGridGraph了解常用的Manhatten方法。
|
||||||
|
* @param node
|
||||||
|
* @param goal
|
||||||
|
*/
|
||||||
heuristic(node: T, goal: T);
|
heuristic(node: T, goal: T);
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* 使用堆实现最小优先级队列 O(1)复杂度
|
||||||
|
* 这种查找速度比使用字典快5-10倍
|
||||||
|
* 但是,由于IPriorityQueue.contains()是许多寻路算法中调用最多的方法,因此尽可能快地实现它对于我们的应用程序非常重要。
|
||||||
|
*/
|
||||||
class PriorityQueue<T extends PriorityQueueNode> {
|
class PriorityQueue<T extends PriorityQueueNode> {
|
||||||
private _numNodes: number;
|
private _numNodes: number;
|
||||||
private _nodes: T[];
|
private _nodes: T[];
|
||||||
private _numNodesEverEnqueued;
|
private _numNodesEverEnqueued;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实例化一个新的优先级队列
|
||||||
|
* @param maxNodes 允许加入队列的最大节点(执行此操作将导致undefined的行为)
|
||||||
|
*/
|
||||||
constructor(maxNodes: number) {
|
constructor(maxNodes: number) {
|
||||||
this._numNodes = 0;
|
this._numNodes = 0;
|
||||||
this._nodes = new Array(maxNodes + 1);
|
this._nodes = new Array(maxNodes + 1);
|
||||||
this._numNodesEverEnqueued = 0;
|
this._numNodesEverEnqueued = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从队列中删除每个节点。
|
||||||
|
* O(n)复杂度 所有尽可能少调用该方法
|
||||||
|
*/
|
||||||
public clear() {
|
public clear() {
|
||||||
this._nodes.splice(1, this._numNodes);
|
this._nodes.splice(1, this._numNodes);
|
||||||
this._numNodes = 0;
|
this._numNodes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回队列中的节点数。
|
||||||
|
* O(1)复杂度
|
||||||
|
*/
|
||||||
public get count() {
|
public get count() {
|
||||||
return this._numNodes;
|
return this._numNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回可同时进入此队列的最大项数。一旦你达到这个数字(即。一旦Count == MaxSize),尝试加入另一个项目将导致undefined的行为
|
||||||
|
* O(1)复杂度
|
||||||
|
*/
|
||||||
|
public get maxSize() {
|
||||||
|
return this._nodes.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回(在O(1)中)给定节点是否在队列中
|
||||||
|
* O (1)复杂度
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
public contains(node: T): boolean {
|
public contains(node: T): boolean {
|
||||||
|
if (!node){
|
||||||
|
console.error("node cannot be null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.queueIndex < 0 || node.queueIndex >= this._nodes.length){
|
||||||
|
console.error("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return (this._nodes[node.queueIndex] == node);
|
return (this._nodes[node.queueIndex] == node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将节点放入优先队列 较低的值放在前面 先入先出
|
||||||
|
* 如果队列已满,则结果undefined。如果节点已经加入队列,则结果undefined。
|
||||||
|
* O(log n)
|
||||||
|
* @param node
|
||||||
|
* @param priority
|
||||||
|
*/
|
||||||
public enqueue(node: T, priority: number) {
|
public enqueue(node: T, priority: number) {
|
||||||
node.priority = priority;
|
node.priority = priority;
|
||||||
this._numNodes++;
|
this._numNodes++;
|
||||||
@@ -31,12 +78,21 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
|||||||
this.cascadeUp(this._nodes[this._numNodes]);
|
this.cascadeUp(this._nodes[this._numNodes]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除队列头(具有最小优先级的节点;按插入顺序断开连接),并返回它。如果队列为空,结果undefined
|
||||||
|
* O(log n)
|
||||||
|
*/
|
||||||
public dequeue(): T {
|
public dequeue(): T {
|
||||||
let returnMe = this._nodes[1];
|
let returnMe = this._nodes[1];
|
||||||
this.remove(returnMe);
|
this.remove(returnMe);
|
||||||
return returnMe;
|
return returnMe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从队列中删除一个节点。节点不需要是队列的头。如果节点不在队列中,则结果未定义。如果不确定,首先检查Contains()
|
||||||
|
* O(log n)
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
public remove(node: T) {
|
public remove(node: T) {
|
||||||
if (node.queueIndex == this._numNodes) {
|
if (node.queueIndex == this._numNodes) {
|
||||||
this._nodes[this._numNodes] = null;
|
this._nodes[this._numNodes] = null;
|
||||||
@@ -52,6 +108,9 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
|||||||
this.onNodeUpdated(formerLastNode);
|
this.onNodeUpdated(formerLastNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查以确保队列仍然处于有效状态。用于测试/调试队列。
|
||||||
|
*/
|
||||||
public isValidQueue(): boolean {
|
public isValidQueue(): boolean {
|
||||||
for (let i = 1; i < this._nodes.length; i++) {
|
for (let i = 1; i < this._nodes.length; i++) {
|
||||||
if (this._nodes[i]) {
|
if (this._nodes[i]) {
|
||||||
@@ -71,24 +130,29 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onNodeUpdated(node: T) {
|
private onNodeUpdated(node: T) {
|
||||||
|
// 将更新后的节点按适当的方式向上或向下冒泡
|
||||||
let parentIndex = Math.floor(node.queueIndex / 2);
|
let parentIndex = Math.floor(node.queueIndex / 2);
|
||||||
let parentNode = this._nodes[parentIndex];
|
let parentNode = this._nodes[parentIndex];
|
||||||
|
|
||||||
if (parentIndex > 0 && this.hasHigherPriority(node, parentNode)) {
|
if (parentIndex > 0 && this.hasHigherPriority(node, parentNode)) {
|
||||||
this.cascadeUp(node);
|
this.cascadeUp(node);
|
||||||
} else {
|
} else {
|
||||||
|
// 注意,如果parentNode == node(即节点是根),则将调用CascadeDown。
|
||||||
this.cascadeDown(node);
|
this.cascadeDown(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private cascadeDown(node: T) {
|
private cascadeDown(node: T) {
|
||||||
|
// 又名Heapify-down
|
||||||
let newParent: T;
|
let newParent: T;
|
||||||
let finalQueueIndex = node.queueIndex;
|
let finalQueueIndex = node.queueIndex;
|
||||||
while (true) {
|
while (true) {
|
||||||
newParent = node;
|
newParent = node;
|
||||||
let childLeftIndex = 2 * finalQueueIndex;
|
let childLeftIndex = 2 * finalQueueIndex;
|
||||||
|
|
||||||
|
// 检查左子节点的优先级是否高于当前节点
|
||||||
if (childLeftIndex > this._numNodes) {
|
if (childLeftIndex > this._numNodes) {
|
||||||
|
// 这可以放在循环之外,但是我们必须检查newParent != node两次
|
||||||
node.queueIndex = finalQueueIndex;
|
node.queueIndex = finalQueueIndex;
|
||||||
this._nodes[finalQueueIndex] = node;
|
this._nodes[finalQueueIndex] = node;
|
||||||
break;
|
break;
|
||||||
@@ -99,6 +163,7 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
|||||||
newParent = childLeft;
|
newParent = childLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查右子节点的优先级是否高于当前节点或左子节点
|
||||||
let childRightIndex = childLeftIndex + 1;
|
let childRightIndex = childLeftIndex + 1;
|
||||||
if (childRightIndex <= this._numNodes) {
|
if (childRightIndex <= this._numNodes) {
|
||||||
let childRight = this._nodes[childRightIndex];
|
let childRight = this._nodes[childRightIndex];
|
||||||
@@ -107,13 +172,17 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果其中一个子节点具有更高(更小)的优先级,则交换并继续级联
|
||||||
if (newParent != node) {
|
if (newParent != node) {
|
||||||
|
// 将新的父节点移动到它的新索引
|
||||||
|
// 节点将被移动一次,这样做比调用Swap()少一个赋值操作。
|
||||||
this._nodes[finalQueueIndex] = newParent;
|
this._nodes[finalQueueIndex] = newParent;
|
||||||
|
|
||||||
let temp = newParent.queueIndex;
|
let temp = newParent.queueIndex;
|
||||||
newParent.queueIndex = finalQueueIndex;
|
newParent.queueIndex = finalQueueIndex;
|
||||||
finalQueueIndex = temp;
|
finalQueueIndex = temp;
|
||||||
} else {
|
} else {
|
||||||
|
// 参见上面的笔记
|
||||||
node.queueIndex = finalQueueIndex;
|
node.queueIndex = finalQueueIndex;
|
||||||
this._nodes[finalQueueIndex] = node;
|
this._nodes[finalQueueIndex] = node;
|
||||||
break;
|
break;
|
||||||
@@ -121,13 +190,20 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当没有内联时,性能会稍微好一些
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
private cascadeUp(node: T) {
|
private cascadeUp(node: T) {
|
||||||
|
// 又名Heapify-up
|
||||||
let parent = Math.floor(node.queueIndex / 2);
|
let parent = Math.floor(node.queueIndex / 2);
|
||||||
while (parent >= 1) {
|
while (parent >= 1) {
|
||||||
let parentNode = this._nodes[parent];
|
let parentNode = this._nodes[parent];
|
||||||
if (this.hasHigherPriority(parentNode, node))
|
if (this.hasHigherPriority(parentNode, node))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// 节点具有较低的优先级值,因此将其向上移动到堆中
|
||||||
|
// 出于某种原因,使用Swap()比使用单独的操作更快,如CascadeDown()
|
||||||
this.swap(node, parentNode);
|
this.swap(node, parentNode);
|
||||||
|
|
||||||
parent = Math.floor(node.queueIndex / 2);
|
parent = Math.floor(node.queueIndex / 2);
|
||||||
@@ -135,14 +211,22 @@ class PriorityQueue<T extends PriorityQueueNode> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private swap(node1: T, node2: T) {
|
private swap(node1: T, node2: T) {
|
||||||
|
// 交换节点
|
||||||
this._nodes[node1.queueIndex] = node2;
|
this._nodes[node1.queueIndex] = node2;
|
||||||
this._nodes[node2.queueIndex] = node1;
|
this._nodes[node2.queueIndex] = node1;
|
||||||
|
|
||||||
|
// 交换他们的indicies
|
||||||
let temp = node1.queueIndex;
|
let temp = node1.queueIndex;
|
||||||
node1.queueIndex = node2.queueIndex;
|
node1.queueIndex = node2.queueIndex;
|
||||||
node2.queueIndex = temp;
|
node2.queueIndex = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果higher的优先级高于lower,则返回true,否则返回false。
|
||||||
|
* 注意,调用HasHigherPriority(节点,节点)(即。两个参数为同一个节点)将返回false
|
||||||
|
* @param higher
|
||||||
|
* @param lower
|
||||||
|
*/
|
||||||
private hasHigherPriority(higher: T, lower: T) {
|
private hasHigherPriority(higher: T, lower: T) {
|
||||||
return (higher.priority < lower.priority ||
|
return (higher.priority < lower.priority ||
|
||||||
(higher.priority == lower.priority && higher.insertionIndex < lower.insertionIndex));
|
(higher.priority == lower.priority && higher.insertionIndex < lower.insertionIndex));
|
||||||
|
|||||||
Reference in New Issue
Block a user