`

利用递归算法并行化解决谜题框架

阅读更多

我们将谜题定义为:包含一个初始位置,一个目标位置,以及用于判断是否是有效移动的规则集。

规则集包含两部分:计算从指定位置开始的所有合法移动,以及每次移动的结果位置。

下面先给出表示谜题的抽象类,其中的类型参数P和M表示位置类和移动类。根据这个接口,我们可以写一个简单的串行求解程序,该程序将在谜题空间Puzzle Space中查找,直到找到一个解答或者找遍了整个空间都没有发现答案。注:一个移动M代表一步

 

 

/** 表示 搬箱子 之类谜题的抽象类*/
public interface Puzzle<P, M> {
    P initialPosition();

    boolean isGoal(P position);

    Set<M> legalMoves(P position);

    P move(P position, M move);
}

 

下面的PuzzleNode代表通过一系列的移动到达的一个位置,其中保存了到达该位置的移动以及前一个Node。只要沿着PuzzleNode链接逐步回溯,就可以重新构建出达到当前位置的移动序列。

 

/** 用于谜题解决框架的链接节点 */
@Immutable
public class PuzzleNode<P, M> {
    final P pos;
    final M move;
    final PuzzleNode<P, M> prev;

    public PuzzleNode(P pos, M move, PuzzleNode<P, M> prev) {
        this.pos = pos;
        this.move = move;
        this.prev = prev;
    }

    List<M> asMoveList() {
        List<M> solution = new LinkedList<M>();
        for (PuzzleNode<P, M> n = this; n.move != null; n = n.prev)
            solution.add(0, n.move);
        return solution;
    }
}

 下面的SequentialPuzzleSolver给出了谜题框架的串行解决方案,它在谜题空间中执行深度优先搜索,当找到解答方案,不一定是最短的解决方案,结束搜索。

/** 串行的谜题解答器*/
public class SequentialPuzzleSolver<P, M> {
    private final Puzzle<P, M> puzzle;
    private final Set<P> seen = new HashSet<P>();

    public SequentialPuzzleSolver(Puzzle<P, M> puzzle) {
        this.puzzle = puzzle;
    }

    public List<M> solve() {
        P pos = puzzle.initialPosition();
        return search(new PuzzleNode<P, M>(pos, null, null));
    }

    private List<M> search(PuzzleNode<P, M> node) {
        if (!seen.contains(node.pos)) {
            seen.add(node.pos);
            if (puzzle.isGoal(node.pos))
                return node.asMoveList();
            for (M move : puzzle.legalMoves(node.pos)) {
                P pos = puzzle.move(node.pos, move);
                PuzzleNode<P, M> child = new PuzzleNode<P, M>(pos, move, node);
                List<M> result = search(child);
                if (result != null)
                    return result;
            }
        }
        return null;
    }
}

 

接下来我们给出并行解决方案,ConcurrentPuzzleSolver中使用了一个内部类SolverTask,这个类扩展了PuzzleNode并实现了Runnable。大多数工作都是在run中完成的:首先计算下一步肯能到达的所有位置,并去掉已经到达的位置,然后判断(这个任务或者其他某个任务)是否已经成功完成,最后将尚未搜索过的位置提交给Executor。

public class ConcurrentPuzzleSolver<P, M> {
    private final Puzzle<P, M> puzzle;
    private final ExecutorService exec;
    private final ConcurrentMap<P, Boolean> seen;
    protected final ValueLatch<PuzzleNode<P, M>> solution = new ValueLatch<PuzzleNode<P, M>>();

    public ConcurrentPuzzleSolver(Puzzle<P, M> puzzle) {
        this.puzzle = puzzle;
        this.exec = initThreadPool();
        this.seen = new ConcurrentHashMap<P, Boolean>();
        if (exec instanceof ThreadPoolExecutor) {
            ThreadPoolExecutor tpe = (ThreadPoolExecutor) exec;
            tpe.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        }
    }

    private ExecutorService initThreadPool() {
        return Executors.newCachedThreadPool();
    }

    public List<M> solve() throws InterruptedException {
        try {
            P p = puzzle.initialPosition();
            exec.execute(newTask(p, null, null));
            // block until solution found
            PuzzleNode<P, M> solnPuzzleNode = solution.getValue();
            return (solnPuzzleNode == null) ? null : solnPuzzleNode.asMoveList();
        } finally {
            exec.shutdown();
        }
    }

    protected Runnable newTask(P p, M m, PuzzleNode<P, M> n) {
        return new SolverTask(p, m, n);
    }

    protected class SolverTask extends PuzzleNode<P, M> implements Runnable {
        SolverTask(P pos, M move, PuzzleNode<P, M> prev) {
            super(pos, move, prev);
        }

        public void run() {
            if (solution.isSet() || seen.putIfAbsent(pos, true) != null)
                return; // already solved or seen this position
            if (puzzle.isGoal(pos))
                solution.setValue(this);
            else
                for (M m : puzzle.legalMoves(pos))
                    exec.execute(newTask(puzzle.move(pos, m), m, this));
        }
    }
}

 

@ThreadSafe
public class ValueLatch<T> {
    @GuardedBy("this")
    private T value = null;
    private final CountDownLatch done = new CountDownLatch(1);

    public boolean isSet() {
        return (done.getCount() == 0);
    }

    public synchronized void setValue(T newValue) {
        if (!isSet()) {
            value = newValue;
            done.countDown();
        }
    }

    public T getValue() throws InterruptedException {
        done.await();
        synchronized (this) {
            return value;
        }
    }
}

 

 比较串行和并行算法可知:并发方法引入了一种新形式的限制并去掉了一种原有的限制,新的限制在这个问题域中更合适。串行版本的程序执行深度优先搜索,因此搜索过程将受限于栈的大小。并发版本程序执行广度优先搜索,因此不会受到栈大小的限制。

 

第一个找到解答的线程还会关闭Executor,从而阻止接受显得任务。要避免处理RejectedExecutionException(等待队列满员或者是Executor关闭后提交的任务),需要将拒绝执行处理器

设置为DiscardPolicy 。

 

如果不存在解答,那么ConcurrentPuzzleSolver就会永远的等待下去,getSolution一直阻塞下去。

通过记录活动任务数量,当该值为零时将解答设置为null,如下:

public class PuzzleSolver<P, M> extends ConcurrentPuzzleSolver<P, M> {
    PuzzleSolver(Puzzle<P, M> puzzle) {
        super(puzzle);
    }

    private final AtomicInteger taskCount = new AtomicInteger(0);

    protected Runnable newTask(P p, M m, PuzzleNode<P, M> n) {
        return new CountingSolverTask(p, m, n);
    }

    class CountingSolverTask extends SolverTask {
        CountingSolverTask(P pos, M move, PuzzleNode<P, M> prev) {
            super(pos, move, prev);
            taskCount.incrementAndGet();
        }

        public void run() {
            try {
                super.run();
            } finally {
                if (taskCount.decrementAndGet() == 0)
                    solution.setValue(null);
            }
        }
    }
}

 

另外,还可以将ValueLatch设置为限时的,将getValue使用await的限时版实现,那么就可以指定多少时间内搜索结果,搜不到就超时中断。

 

本人博客已搬家,新地址为:http://yidao620c.github.io/

分享到:
评论

相关推荐

    数独算法,数独游戏

    数独的解决方法多种多样,其中一种常见的是基于回溯的非递归算法,这也是本项目所采用的方法。 在描述中提到的"非递归算法"通常指的是基于深度优先搜索(DFS)的策略。这种算法并不使用递归函数,而是利用栈或队列...

    最新文明盛世谜题代码c++

    3. 并行计算:如果硬件支持,可以利用多线程或并行计算技术来加速计算过程。 4. 动态规划:如果问题具有重叠子问题,可以使用动态规划来避免重复计算。 总之,这个项目涉及C++编程、组合算法、01转换以及可能的递归...

    数独游戏算法

    - 并行化:对于大型或复杂数独问题,可以考虑使用多线程或并发技术来并行处理不同的格子,提高求解效率。 - AI算法:结合人工智能,如遗传算法、模拟退火等,寻找更高效的解法。 - 用户界面:开发用户友好的图形...

    算法文档无代码探寻深度优先搜索中的优化技巧

    在某些情况下,它可以用来找到所有可能的解或决策路径,例如解决各种类型的谜题或问题。 深度优先搜索在实施时,可以通过递归方法或栈数据结构来实现。在递归实现中,搜索树的节点会递归调用自身来遍历子节点。当...

    sudo_数独求解器_

    数独求解器是一种计算机程序,它利用算法解决数独谜题。在C++中实现一个数独求解器涉及到一系列编程和算法设计的技术。以下是对这个话题的详细阐述: 一、基础知识 1. C++语言:数独求解器是用C++编程语言编写的。...

    MATLAB Sodoku Solver - 改进:更快地解决数独谜题。-matlab开发

    4. **并行化处理**:MATLAB支持多线程和并行计算,通过将大问题分解为小问题并同时处理,可以进一步提升速度。 5. **动态规划**:可能引入了某种形式的动态规划策略,存储已解决的部分,避免重复计算。 在实际实现...

    程序设计实习(田永鸿)清华大学

    课程中讨论了如何使用不同的搜索策略(如BFS vs DFS)来优化解决方案,同时提出了节省存储空间和加速搜索的方法,例如使用A*算法、双向BFS、HASH映射和并行算法等。 - **木棒问题**:涉及到分割给定长度的木棒,以...

    Algorithm-Sudoku-Generator.zip

    6. **并行化**:如果系统支持,可以考虑使用多线程或者并行计算,同时尝试多个可能的解决方案,加快生成速度。 在"Algorithm-Sudoku-Generator-master"这个文件夹中,通常会包含源代码、编译脚本、示例和可能的测试...

    数独谜题:数独谜题-matlab开发

    综上所述,"数独谜题-matlab开发"项目涵盖了MATLAB编程、算法设计、用户界面构建、性能评估等多个方面,是一个综合性的实践课题,有助于提升编程技能和问题解决能力。通过这个项目,不仅可以深入了解数独游戏的内在...

    matlab开发-SudokuPuzzle

    它的核心算法主要包括生成数独谜题和解决数独问题两部分。 生成数独谜题: 1. **随机生成法**:首先填充一个完全数独(所有空位已知),然后随机删除一些数字,保留一定数量的空位作为谜题。关键在于确保删除后仍有...

    数独解迷程序,帮助你解迷数独

    数独解谜程序则是利用计算机算法来辅助玩家解决数独谜题的工具。这里我们将探讨如何用Java编程语言实现这样的程序。 1. **基础数据结构设计** - **棋盘表示**:首先,我们需要设计一个数据结构来表示数独棋盘。...

    Algorithms

    5. **递归与回溯**:递归是一种函数调用自身以解决问题的技术,而回溯则是一种尝试所有可能解决方案并撤销无效尝试的方法,常见于组合优化问题和谜题求解。 6. **数据结构**:Python中的列表、元组、字典、集合和堆...

    Project-Euler-Puzzles:为解决 Project Euler 上发布的问题而编写的程序

    通过分析和学习这个项目,我们可以深入了解如何利用Java解决实际问题,提高算法思维和编程技能,同时也可以锻炼对数学和计算机科学知识的实际应用能力。无论是对于初学者还是经验丰富的开发者,"Project-Euler-...

    SudokuSolver-Go:Go中的数独求解算法实现

    在Go语言中实现这样的算法,可以充分利用其并发特性,虽然在单个数独解算中可能并不明显,但在处理大量数独谜题或优化性能时,可以考虑使用goroutines和channel来并行处理部分计算。 项目"SudokuSolver-Go"可能包含...

    SododuSolver:使用正向检查用Java编写的Sodoku求解器

    此外,对算法进行并行化处理,利用多核CPU的计算能力,也能显著提升性能。 总结,SododuSolver是一个基于Java的高效数独求解器,其核心是正向检查算法。通过理解和掌握这种算法,不仅能够解决数独问题,还能为其他...

    matlab开发-sudokum

    7. **效率优化**:为了提高算法效率,我们可以考虑使用位运算、预计算常量或使用MATLAB的并行计算工具箱来加速数独的解决过程。 8. **用户交互**:在实际应用中,可能还需要添加用户界面(UI)以允许用户输入数独...

    SudokuEight:具有高级功能的数独求解器的 Java 8 实现

    而“SudokuEight”是一个利用 Java 8 实现的高级数独求解器,它不仅能够帮助玩家解决数独谜题,还提供了一系列高级功能,让解题过程更加智能化和高效。本文将深入探讨这个项目的核心技术和实现细节。 首先,我们要...

    一个简单的汉诺塔小游戏

    汉诺塔游戏是一种经典的逻辑谜题,源自印度的古老传说,玩家需要将一系列盘子从一根柱子移动到另一根柱子,遵循“每次只能移动一个盘子”和“大盘子不能位于小盘子之上”的规则。在这个Java实现的汉诺塔游戏中,...

    时空世界:这是时间流逝的世界,角色必须解决一些问题才能生存,他正在同时学习

    在解决游戏中遇到的问题时,玩家可能会接触到一些谜题,这些谜题的逻辑可能由C#的条件语句、循环结构和递归算法实现,以增加游戏的趣味性和挑战性。例如,玩家可能需要根据特定的时间顺序触发事件,或者通过反复试验...

    adventOfCode

    3. **算法实现**:Python支持各种算法,如排序、搜索、图论等,可以用于解决AoC中的逻辑谜题。 4. **测试框架**:`unittest`或`pytest`可以帮助编写和执行测试用例,确保解决方案的正确性。 5. **动态规划和递归**...

Global site tag (gtag.js) - Google Analytics