您可以使用最近的目标来计算F分数。正如其他人所说,对于天真的方法,您可以直接计算当前节点距离所有目标的距离并选择最小值,如果您只有少量目标要搜索。对于超过100个目标,您可以使用 KDTree以加速进程找到最近的。
这里是Dart语言的样例代码。
Iterable<Vector2> getPath(Vector2 from, Iterable<Vector2> targets,
{double? maxDistance, bool useAStar = false}) {
targets = targets.asSet();
clearPoints();
var projectedTargets = addPoints(targets).toSet();
var tree = useAStar ? IKDTree(targets) : null;
var q = PriorityQueue<Node>(_priorityQueueComparor);
Map<Vector2, Node> visited = {};
var node = Node(from);
visited[from] = node;
q.add(node);
while (q.isNotEmpty) {
var current = q.removeFirst();
for (var edge in getEdges(current.point)) {
if (visited.containsKey(edge.dest)) continue;
var distance = current.distance + edge.distance;
if (maxDistance != null && distance > maxDistance) continue;
if (projectedTargets.contains(edge.dest)) {
return reconstructPath(visited, current, edge.dest);
}
if (!_polygonPoints.contains(edge.dest)) continue;
var f = 0.0;
if (tree != null) {
var nearest = tree
.nearest(edge.dest, maxDistance: maxDistance ?? double.infinity)
.firstOrNull;
f = nearest != null ? edge.dest.distanceToSquared(nearest) : 0.0;
}
node = Node(edge.dest, distance, current.count + 1, current.point, f);
visited[edge.dest] = node;
q.add(node);
}
}
return [];
}
Iterable<Vector2> reconstructPath(
Map<Vector2, Node> visited, Node prev, Vector2 point) {
var path = <Vector2>[point];
Node? currentNode = prev;
while (currentNode != null) {
path.add(currentNode.point);
currentNode = visited[currentNode.prev];
}
return path.reversed;
}
int _priorityQueueComparor(Node p0, Node p1) {
int r;
if (p0.f > 0 && p1.f > 0) {
r = ((p0.distance * p0.distance) + p0.f)
.compareTo((p1.distance * p1.distance) + p1.f);
if (r != 0) return r;
}
r = p0.distance.compareTo(p1.distance);
if (r != 0) return r;
return p0.count.compareTo(p1.count);
}
以及KD树的实现
class IKDTree {
final int _dimensions = 2;
late Node? _root;
IKDTree(Iterable<Vector2> points) {
_root = _buildTree(points, null);
}
Node? _buildTree(Iterable<Vector2> points, Node? parent) {
var list = points.asList();
if (list.isEmpty) return null;
var median = (list.length / 2).floor();
var axis = 0;
var aabb = AABB.fromPoints(list);
for (var i = 1; i < _dimensions; i++) {
if (aabb.range[i] > aabb.range[axis]) {
axis = i;
}
}
list.sort(((a, b) => a[axis].compareTo(b[axis])));
var point = list[median];
var node = Node(point.clone());
node.parent = parent;
node.left = _buildTree(list.sublist(0, median), node);
node.right = _buildTree(list.sublist(median + 1), node);
update(node);
return node;
}
void addPoint(Vector2 point, [bool allowRebuild = true]) {
_root = _addByPoint(_root, point, allowRebuild, 0);
}
Node? _addByPoint(
Node? node, Vector2 point, bool allowRebuild, int parentDim) {
if (node == null) {
node = Node(point.clone());
node.dimension = (parentDim + 1) % _dimensions;
update(node);
return node;
}
_pushDown(node);
if (point[node.dimension] < node.point[node.dimension]) {
node.left = _addByPoint(node.left, point, allowRebuild, node.dimension);
} else {
node.right = _addByPoint(node.right, point, allowRebuild, node.dimension);
}
update(node);
bool needRebuild = allowRebuild && criterionCheck(node);
if (needRebuild) node = rebuild(node);
return node;
}
void _pushDown(Node? node) {
if (node == null) return;
if (node.needPushDownToLeft && node.left != null) {
node.left!.treeDownsampleDeleted |= node.treeDownsampleDeleted;
node.left!.pointDownsampleDeleted |= node.treeDownsampleDeleted;
node.left!.treeDeleted =
node.treeDeleted || node.left!.treeDownsampleDeleted;
node.left!.deleted =
node.left!.treeDeleted || node.left!.pointDownsampleDeleted;
if (node.treeDownsampleDeleted) {
node.left!.downDeletedNum = node.left!.treeSize;
}
if (node.treeDeleted) {
node.left!.invalidNum = node.left!.treeSize;
} else {
node.left!.invalidNum = node.left!.downDeletedNum;
}
node.left!.needPushDownToLeft = true;
node.left!.needPushDownToRight = true;
node.needPushDownToLeft = false;
}
if (node.needPushDownToRight && node.right != null) {
node.right!.treeDownsampleDeleted |= node.treeDownsampleDeleted;
node.right!.pointDownsampleDeleted |= node.treeDownsampleDeleted;
node.right!.treeDeleted =
node.treeDeleted || node.right!.treeDownsampleDeleted;
node.right!.deleted =
node.right!.treeDeleted || node.right!.pointDownsampleDeleted;
if (node.treeDownsampleDeleted) {
node.right!.downDeletedNum = node.right!.treeSize;
}
if (node.treeDeleted) {
node.right!.invalidNum = node.right!.treeSize;
} else {
node.right!.invalidNum = node.right!.downDeletedNum;
}
node.right!.needPushDownToLeft = true;
node.right!.needPushDownToRight = true;
node.needPushDownToRight = false;
}
}
void _removeNode(Node? node, bool allowRebuild) {
if (node == null || node.deleted) return;
_pushDown(node);
node.deleted = true;
node.invalidNum++;
if (node.invalidNum == node.treeSize) {
node.treeDeleted = true;
}
var parent = node.parent;
if (parent != null) {
updateAncestors(parent);
bool needRebuild = allowRebuild && criterionCheck(parent);
if (needRebuild) parent = rebuild(parent);
}
}
void updateAncestors(Node? node) {
if (node == null) return;
update(node);
updateAncestors(node.parent);
}
void _removeByPoint(Node? node, Vector2 point, bool allowRebuild) {
if (node == null || node.treeDeleted) return;
_pushDown(node);
if (node.point == point && !node.deleted) {
node.deleted = true;
node.invalidNum++;
if (node.invalidNum == node.treeSize) {
node.treeDeleted = true;
}
return;
}
if (point[node.dimension] < node.point[node.dimension]) {
_removeByPoint(node.left, point, false);
} else {
_removeByPoint(node.right, point, false);
}
update(node);
bool needRebuild = allowRebuild && criterionCheck(node);
if (needRebuild) rebuild(node);
}
void update(Node node) {
var left = node.left;
var right = node.right;
node.treeSize = (left != null ? left.treeSize : 0) +
(right != null ? right.treeSize : 0) +
1;
node.invalidNum = (left != null ? left.invalidNum : 0) +
(right != null ? right.invalidNum : 0) +
(node.deleted ? 1 : 0);
node.downDeletedNum = (left != null ? left.downDeletedNum : 0) +
(right != null ? right.downDeletedNum : 0) +
(node.pointDownsampleDeleted ? 1 : 0);
node.treeDownsampleDeleted = (left == null || left.treeDownsampleDeleted) &&
(right == null || right.treeDownsampleDeleted) &&
node.pointDownsampleDeleted;
node.treeDeleted = (left == null || left.treeDeleted) &&
(right == null || right.treeDeleted) &&
node.deleted;
var minList = <Vector2>[];
var maxList = <Vector2>[];
if (left != null && !left.treeDeleted) {
minList.add(left.aabb.min);
maxList.add(left.aabb.max);
}
if (right != null && !right.treeDeleted) {
minList.add(right.aabb.min);
maxList.add(right.aabb.max);
}
if (!node.deleted) {
minList.add(node.point);
maxList.add(node.point);
}
if (minList.isNotEmpty && maxList.isNotEmpty) {
node.aabb = AABB()
..min = minList.min()
..max = maxList.max();
}
if (left != null) left.parent = node;
if (right != null) right.parent = node;
}
final minimalUnbalancedTreeSize = 10;
final deleteCriterionParam = 0.3;
final balanceCriterionParam = 0.6;
bool criterionCheck(Node node) {
if (node.treeSize <= minimalUnbalancedTreeSize) return false;
double balanceEvaluation = 0.0;
double deleteEvaluation = 0.0;
var child = node.left ?? node.right!;
deleteEvaluation = node.invalidNum / node.treeSize;
balanceEvaluation = child.treeSize / (node.treeSize - 1);
if (deleteEvaluation > deleteCriterionParam) return true;
if (balanceEvaluation > balanceCriterionParam ||
balanceEvaluation < 1 - balanceCriterionParam) return true;
return false;
}
void rebuildAll() {
_root = rebuild(_root);
}
Node? rebuild(Node? node) {
if (node == null) return null;
var parent = node.parent;
var points = flatten(node).toList();
deleteTreeNodes(node);
return _buildTree(points, parent);
}
Iterable<Vector2> flatten(Node? node) sync* {
if (node == null) return;
_pushDown(node);
if (!node.deleted) yield node.point;
yield* flatten(node.left);
yield* flatten(node.right);
}
void deleteTreeNodes(Node? node) {
if (node == null) return;
_pushDown(node);
deleteTreeNodes(node.left);
deleteTreeNodes(node.right);
}
double _calcDist(Vector2 a, Vector2 b) {
double dist = 0;
for (var dim = 0; dim < _dimensions; dim++) {
dist += math.pow(a[dim] - b[dim], 2);
}
return dist;
}
double _calcBoxDist(Node? node, Vector2 point) {
if (node == null) return double.infinity;
double minDist = 0;
for (var dim = 0; dim < _dimensions; dim++) {
if (point[dim] < node.aabb.min[dim]) {
minDist += math.pow(point[dim] - node.aabb.min[dim], 2);
}
if (point[dim] > node.aabb.max[dim]) {
minDist += math.pow(point[dim] - node.aabb.max[dim], 2);
}
}
return minDist;
}
void _search(Node? node, int maxNodes, Vector2 point, BinaryHeap<Result> heap,
double maxDist) {
if (node == null || node.treeDeleted) return;
double curDist = _calcBoxDist(node, point);
double maxDistSqr = maxDist * maxDist;
if (curDist > maxDistSqr) return;
if (node.needPushDownToLeft || node.needPushDownToRight) {
_pushDown(node);
}
if (!node.deleted) {
double dist = _calcDist(point, node.point);
if (dist <= maxDistSqr &&
(heap.size() < maxNodes || dist < heap.peek().distance)) {
if (heap.size() >= maxNodes) heap.pop();
heap.push(Result(node, dist));
}
}
double distLeftNode = _calcBoxDist(node.left, point);
double distRightNode = _calcBoxDist(node.right, point);
if (heap.size() < maxNodes ||
distLeftNode < heap.peek().distance &&
distRightNode < heap.peek().distance) {
if (distLeftNode <= distRightNode) {
_search(node.left, maxNodes, point, heap, maxDist);
if (heap.size() < maxNodes || distRightNode < heap.peek().distance) {
_search(node.right, maxNodes, point, heap, maxDist);
}
} else {
_search(node.right, maxNodes, point, heap, maxDist);
if (heap.size() < maxNodes || distLeftNode < heap.peek().distance) {
_search(node.left, maxNodes, point, heap, maxDist);
}
}
} else {
if (distLeftNode < heap.peek().distance) {
_search(node.left, maxNodes, point, heap, maxDist);
}
if (distRightNode < heap.peek().distance) {
_search(node.right, maxNodes, point, heap, maxDist);
}
}
}
Iterable<Vector2> nearest(Vector2 point,
{int maxNodes = 1, double maxDistance = double.infinity}) sync* {
var heap = BinaryHeap<Result>((e) => -e.distance);
_search(_root, maxNodes, point, heap, maxDistance);
var found = math.min(maxNodes, heap.content.length);
for (var i = 0; i < found; i++) {
yield heap.content[i].node.point;
}
}
int get length => _root?.length ?? 0;
int get height => _root?.height ?? 0;
}
class Result {
final Node node;
final double distance;
const Result(this.node, this.distance);
}
class Node {
Vector2 point;
int dimension = 0;
Node? parent;
Node? left;
Node? right;
int treeSize = 0;
int invalidNum = 0;
int downDeletedNum = 0;
bool deleted = false;
bool treeDeleted = false;
bool needPushDownToLeft = false;
bool needPushDownToRight = false;
bool treeDownsampleDeleted = false;
bool pointDownsampleDeleted = false;
AABB aabb = AABB();
Node(this.point);
int get length {
return 1 +
(left == null ? 0 : left!.length) +
(right == null ? 0 : right!.length);
}
int get height {
return 1 +
math.max(
left == null ? 0 : left!.height,
right == null ? 0 : right!.height,
);
}
int get depth {
return 1 + (parent == null ? 0 : parent!.depth);
}
}