【问题标题】:Depth first search in parallel并行深度优先搜索
【发布时间】:2017-03-12 11:35:06
【问题描述】:

我正在实现一个 DFS 来寻找迷宫的出口,目前它是单线程的。

我计划通过创建多个使用相同的单线程算法搜索树的线程来提高效率,但是当遇到交叉路口时,我会随机选择哪个方向。

例如,线程遇到可以向东或向西移动的交叉点。他们一半去东方,一半去西方。这一直持续到其中一个线程找到解决方案路径。

这是以并行方式实现 DFS 的有效方式吗?

【问题讨论】:

  • 迷宫会有循环吗?
  • 没有循环或循环
  • 这将是一个搜索,应该可以正常工作。根据标准定义,它不会是 DFS,但这没关系。如果迷宫是一棵树,那么实现起来非常简单。如果是图表,那么同步就变得很重要。

标签: java multithreading optimization concurrency parallel-processing


【解决方案1】:

如果您在 Java 中进行递归并行工作,请使用 Java 7 中引入的 Fork 和 Join API。

public class MazeNode {
  // This class represents a Path from the start of your maze to a certain node. It can be a) a dead end, b) the exit, c) have child Paths
  ...
}

public class MazeTask extends RecursiveTask<MazeNode>
{
  private MazeNode node;

  MazeTask(MazeNode node) {
    this.node = node;
  }


  // Returns null or the exit node
  @Override
  protected MazeNode compute() {    
  if (node.isDeadEnd())
    return null;
  else if (node.isExit())
    return node;
  else { // node has ways to go
    // implement as many directions as you want
    MazeTask left = new MazeTask(node.getLeft());
    MazeTask right = new MazeTask(node.getRight());

    left.fork(); // calculate in parallel

    MazeNode rightNode = right.compute(); // calculate last Task directly to save threads
    MazeNode leftNode = left.join(); // Wait for the other task to complete and get result

    if (rightNode != null)
      return rightNode;
    else
      return leftNode; // This assumes there is only one path to exit
  }
}


public static void main(String[] args) {
  MazeNode maze = ...
  MazeNode exit = new ForkJoinPool().invoke(new MazeTask(maze));
}

【讨论】:

    【解决方案2】:

    [更新1]

    这是我关于线程同步的建议(但根据我们与@IraBaxter 的讨论,我不确定我的方式是否有任何优势):

    当算法开始时只需要一个线程,直到你到达第一个分叉。当这个线程到达那里时,它应该将所有可能的结果(左、右、中间)堆叠并停止。然后由于堆栈中有一些元素,因此激活了几个线程以从存储在堆栈中的边开始。当这些线程中的每一个到达分叉时,所有结果都被放入堆栈并且线程自己停止(不是一次全部,每个都在需要时停止)并从堆栈中获取边缘。等等。每次当任何线程停止时(不管是因为分叉还是死路),它都会切换到等待堆栈中的边缘的模式(或者如果堆栈不为空,则需要一个)。并且每次当一些边缘被添加到堆栈中时,线程都会收到堆栈非空的通知。

    我在这里使用术语“边缘”来表示叉子的位置加上给定叉子的方向。 Stack 为您提供算法的深度优先属性。

    PS:可以通过减少同步点的数量来优化这种方法。我们可以为每个线程在单独的工作列表中收集分叉边缘,并且在它到达死胡同之前不要停止这个线程。我们从这个工作列表中排除了这个线程决定在每个分叉上进行的那些边缘。然后,当线程到达死胡同时,我们将本地工作列表迁移到全局工作列表。因此,空闲线程使用同步从全局工作列表中的点开始。

    【讨论】:

    • 您正在“堆栈”中累积分支(我认为您的意思是任意一组未探索的分支;这通常称为“工作列表”)。要以线程安全的方式执行此操作,您需要同步设置访问。如果生成分支的成本很小(用于 N 谜题甚至国际象棋),同步成本将淹没此算法,串行 DFS 会更快。
    • @IraBaxter,你可能是对的。那么如果我们减少同步点的数量呢?我们可以为每个线程在单独的工作列表中收集分叉边缘,并且在它到达死胡同之前不要停止这个线程。我们从这个工作列表中排除了这个线程决定在每个分叉上进行的那些边缘。然后,当线程到达死胡同时,我们将本地工作列表迁移到全局工作列表。因此,空闲线程使用同步从全局工作列表中的点开始。
    • 恕我直言,更明智的做法是在搜索树的顶部开始并行搜索,并为每个找到的分支分叉。一旦发生足够多的顶级分叉以使所有处理器保持忙碌,只需让每个处理器在其拥有的子树上运行 DFS。因此,您的工作列表(具有同步成本)位于树的顶部,但在分配给处理器的子树中,运行 DFS(与单线程案例一样便宜)。同步开销比率变得非常小,因为搜索树的大小通常是指数级的。
    • @IraBaxter,但您提案中的问题是树可能不平衡,因此如果某个线程完成了其子树,则无法帮助其他线程。
    • 现在没有时间描述解决方案,但这里有一个指向背景想法的链接,然后是一个有效的并行 DFS:stackoverflow.com/a/1344944/120163
    猜你喜欢
    • 1970-01-01
    • 2011-01-31
    • 1970-01-01
    • 1970-01-01
    • 2016-02-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多