简体   繁体   English

A-star:多个目标的启发式

[英]A-star: heuristic for multiple goals

Let's consider a simple grid, where any point is connected with at most 4 other points (North-East-West-South neighborhood).让我们考虑一个简单的网格,其中任何点最多与其他 4 个点(东北-西-南邻域)相连。

I have to write program, that computes minimal route from selected initial point to any of goal points, which are connected (there is route consisting of goal points between any two goals).我必须编写程序,计算从选定的初始点到任何连接的目标点的最小路线(有任何两个目标之间的目标点组成的路线)。 Of course there can be obstacles on grid.当然,网格上可能会有障碍。

My solution is quite simple: I'm using A* algorithm with variable heuristic function h(x) - manhattan distance from x to nearest goal point.我的解决方案非常简单:我使用具有可变启发式 function h(x) - 从 x 到最近目标点的曼哈顿距离的 A* 算法。 To find nearest goal point I have to do linear search (in O(n), where n - number of goal points).为了找到最近的目标点,我必须进行线性搜索(在 O(n) 中,其中 n - 目标点数)。 Here is my question: is there any more efficient solution (heuristic function) to dynamically find nearest goal point (where time < O(n))?这是我的问题:是否有更有效的解决方案(启发式函数)来动态找到最近的目标点(时间 < O(n))?

Or maybe A* is not good way to solve that problem?或者也许 A* 不是解决该问题的好方法?

How many goals, tens or thousands?多少个进球,几十个还是几千个? If tens your way will work fine, if thousands then nearest neighbor search will give you ideas on setting up your data to search quickly.如果您的方法是十个,则您的方法可以正常工作,如果千个,则最近邻搜索将为您提供有关设置数据以快速搜索的想法。

The tradeoffs are obvious, spatially organizing your data to search will take time and on small sets brute force will be simpler to maintain.权衡是显而易见的,在空间上组织您的数据进行搜索需要时间,而在小集合上,蛮力将更易于维护。 Since you're constantly evaluating I think that structuring the data will be worthwhile at very low numbers of points.由于您一直在评估,我认为在非常低的点数上构建数据是值得的。

An alternate way to do this would be a modified flood fill algorithm that stops once it reaches a destination point during the flood.执行此操作的另一种方法是修改洪水填充算法,该算法在洪水期间一旦到达目的地点就会停止。

First, decide whether you need to optimize, because any optimization is going to complicate your code, and for a small number of goals, your current solution is probably fine for a simple heuristic like Manhattan distance.首先,决定您是否需要优化,因为任何优化都会使您的代码复杂化,并且对于少数目标,您当前的解决方案可能适用于像曼哈顿距离这样的简单启发式算法。

Before taking the first step, compute the heuristic for each goal.在迈出第一步之前,计算每个目标的启发式。 Remember the nearest goal as the currently selected goal, and move toward it, but subtract the maximum possible progress toward any goal from all the other distances.记住最近的目标作为当前选定的目标,然后朝它移动,但从所有其他距离中减去朝着任何目标的最大可能进度。 You can consider this second value a "meta-heuristic";您可以将第二个值视为“元启发式”; it is an optimistic estimate of the heuristic for other goals.这是对其他目标启发式的乐观估计。

On subsequent steps, compute the heuristic for the current goal, and any goals with a "meta-heuristic" that is less than or equal to the heuristic.在后续步骤中,计算当前目标的启发式,以及“元启发式”小于或等于启发式的任何目标。 The other goals can't possibly have a better heuristic, so you don't need to compute them.其他目标不可能有更好的启发式方法,因此您不需要计算它们。 The nearest goal becomes the new current goal;最近的目标成为新的当前目标; move toward it, subtracting the maximum possible progress from the others.朝着它前进,从其他人中减去最大可能的进步。 Repeat until you arrive at a goal.重复直到达到目标。

Use Dijkstra's algorithm, which has as it's output the minimal cost to all reachable points.使用 Dijkstra 算法,该算法对所有可达点的输出成本最低。 Then you just select the goal points from the output.然后您只需从输出中选择目标点。

you may consider this article If your goals not too much and want simple ways如果您的目标不是太多并且想要简单的方法,您可以考虑这篇文章

If you want to search for any of several goals, construct a heuristic h'(x) that is the minimum of h1(x), h2(x), h3(x), ... where h1, h2, h3 are heuristics to each of the nearby spots.如果你想搜索几个目标中的任何一个,构造一个启发式 h'(x),它是 h1(x), h2(x), h3(x), ... 的最小值,其中 h1, h2, h3 是启发式到附近的每个景点。

One way to think about this is that we can add a new zero-cost edge from each of the goals to a new graph node.一种思考方式是,我们可以将每个目标的新零成本边添加到新的图节点。 A path to that new node will necessarily go through one of the goal nodes.通往该新节点的路径必须经过目标节点之一。

If you want to search for paths to all of several goals, your best option may be Dijkstra's Algorithm with early exit when you find all the goals.如果您想搜索所有多个目标的路径,最好的选择可能是 Dijkstra 算法,当您找到所有目标时提前退出。 There may be a variant of A* that can calculate these paths;可能有 A* 的变体可以计算这些路径; I don't know.我不知道。

If you want to search for spot near a single goal, ask A* search to find a path to the center of the goal area.如果您想搜索单个目标附近的位置,请使用 A* 搜索来查找通往目标区域中心的路径。 While processing nodes from the OPEN set, exit when you pull a node that is near enough.在处理 OPEN 集中的节点时,当您拉出足够接近的节点时退出。

You can calculate the f score using the nearest target.您可以使用最近的目标计算 f 分数。 As others said, for naive approach, you can directly calculate all target distance from current node and pick the minimum, if you only have few targets to search.正如其他人所说,对于天真的方法,如果您只有很少的目标要搜索,您可以直接计算与当前节点的所有目标距离并选择最小值。 For more than 100 targets, you can probably find the nearest by KDTree to speed up the process.对于超过 100 个目标,您可能可以通过KDTree找到最近的目标以加快该过程。

Here is a sample code in dart.这是 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();
      // developer.log(
      //     '${current.point}#${current.distance}: ${getEdges(current.point).map((e) => e.dest)}');
      for (var edge in getEdges(current.point)) {
        if (visited.containsKey(edge.dest)) continue;

        var distance = current.distance + edge.distance;

        // too far
        if (maxDistance != null && distance > maxDistance) continue;

        // it is a target
        if (projectedTargets.contains(edge.dest)) {
          return reconstructPath(visited, current, edge.dest);
        }

        // we only interested in exploring polygon node.
        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);
  }

and the implementation of KDTree以及KDTree的实现


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();

    // Select the longest dimension as division axis
    var axis = 0;
    var aabb = AABB.fromPoints(list);
    for (var i = 1; i < _dimensions; i++) {
      if (aabb.range[i] > aabb.range[axis]) {
        axis = i;
      }
    }
    // Divide by the division axis and recursively build.
    // var list = list.orderBy((e) => _selector(e)[axis]).asList();
    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);
  }

  // void removePoint(Vector2 point, [bool allowRebuild = true]) {
  //   if (node == null) return;
  //   _removeNode(node, allowRebuild);
  // }

  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;
  }

  // checked
  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;
    }

    // update and rebuild parent
    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);
  }

  // checked
  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();
    }

    // TODO: Radius data for search: https://github.com/hku-mars/ikd-Tree/blob/main/ikd-Tree/ikd_Tree.cpp#L1312

    if (left != null) left.parent = node;
    if (right != null) right.parent = node;

    // TODO: root alpha value for multithread
  }

  // checked
  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);
  }

  // checked
  Node? rebuild(Node? node) {
    if (node == null) return null;
    var parent = node.parent;
    var points = flatten(node).toList();
    // log('rebuilding: $node objects: ${objects.length}');
    deleteTreeNodes(node);
    return _buildTree(points, parent);
    // if (parent == null) {
    //   _root = newNode;
    // } else if (parent.left == node) {
    //   parent.left = newNode;
    // } else if (parent.right == node) {
    //   parent.right = newNode;
    // }
  }

  // checked
  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;
  }

  // checked
  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);
      }
    }
  }

  /// Find the [maxNodes] of nearest Nodes.
  /// Distance is calculated via Metric function.
  /// Max distance can be set with [maxDistance] param
  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);
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM