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

问题描述 投票:0回答:5

让我们考虑一个简单的网格,其中任何点最多与其他 4 个点(东北-西-南邻域)连接。

我必须编写程序,计算从选定的初始点到连接的任意目标点的最小路线(任意两个目标之间存在由目标点组成的路线)。当然,网格上可能存在障碍。

我的解决方案非常简单:我使用带有可变启发式函数 h(x) 的 A* 算法 - 从 x 到最近目标点的曼哈顿距离。为了找到最近的目标点,我必须进行线性搜索(O(n),其中 n - 目标点的数量)。这是我的问题:是否有更有效的解决方案(启发式函数)来动态查找最近的目标点(其中时间< O(n))?

或者也许 A* 不是解决这个问题的好方法?

algorithm search path-finding a-star heuristics
5个回答
9
投票

有多少个目标,几十个还是几千个?如果有数十个,您的方法就可以正常工作,如果有数千个,那么最近邻居搜索将为您提供有关设置数据以进行快速搜索的想法。

权衡是显而易见的,在空间上组织数据进行搜索需要时间,而且在小集合上,暴力将更容易维护。由于您不断评估,我认为以非常低的点数构建数据是值得的。

实现此目的的另一种方法是修改洪水填充算法,一旦在洪水期间到达目的地点,该算法就会停止。


2
投票

首先,决定是否需要优化,因为任何优化都会使您的代码变得复杂,并且对于少量目标,您当前的解决方案可能适合曼哈顿距离这样的简单启发式方法。

在采取第一步之前,计算每个目标的启发式。记住最近的目标作为当前选定的目标,并向其移动,但从所有其他距离中减去任何目标的最大可能进度。您可以将第二个值视为“元启发式”;这是对其他目标启发式的乐观估计。

在后续步骤中,计算当前目标的启发式,以及具有小于或等于启发式的“元启发式”的任何目标。其他目标不可能有更好的启发式,因此您不需要计算它们。最近的目标成为新的当前目标;朝着这个目标前进,从其他人身上减去最大可能的进步。重复直到达到目标。


1
投票

使用 Dijkstra 算法,该算法输出所有可到达点的最小成本。然后您只需从输出中选择目标点即可。


0
投票

您可以考虑这篇文章如果您的目标不是太多并且想要简单的方法

如果您想搜索多个目标中的任何一个,请构造一个启发式 h'(x) 是 h1(x)、h2(x)、h3(x)、... 中的最小值,其中 h1、h2、h3 是对附近每个地点的启发。

思考这个问题的一种方法是我们可以添加一个新的零成本边缘 从每个目标到一个新的图节点。到该新节点的路径 必然会经过目标节点之一。

如果您想寻找实现所有多个目标的路径,您最好 选项可能是 Dijkstra 算法,当你找到所有内容时提前退出 目标。可能有 A* 的变体可以计算这些 路径;我不知道。

如果您想搜索单个目标附近的地点,请使用 A* 搜索 找到一条通往球门区域中心的路径。处理节点时 从 OPEN 集中,当您拉出足够近的节点时退出。


0
投票

您可以使用最近的目标计算 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();
      // 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);
  }

以及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);
  }
}

© www.soinside.com 2019 - 2024. All rights reserved.