【问题标题】:Iterative Deepening A Star (IDA*) to solve n-puzzle (sliding puzzle) in Java用迭代深化 A Star (IDA*) 解决 Java 中的 n-puzzle(滑动拼图)
【发布时间】:2012-08-15 12:33:13
【问题描述】:

我已经实现了一个能够用 A* 解决 n-puzzle problem 的程序。由于状态空间太大,我无法预编译它,我必须在运行时计算可能的状态。以这种方式,A* 对于 3 拼图工作正常,但对于 4 拼图可能需要太长时间。使用线性冲突调整的曼哈顿距离,如果最优解需要大约 25 次移动仍然很快,大约 35 需要 10 秒,对于 40 需要 180 秒。我还没有尝试更多。
我认为那是因为我必须保留所有访问过的状态,因为我使用的函数是可接受的,但(我认为)不一致(我也尝试了 Hamming 和 Gaschnig 距离等等)。由于解决方案的空间是一个图,因此启发式也必须是一致的,否则算法可能会循环或不是最优的。这就是为什么我保留所有访问过的节点(它也写在“AI:一种现代方法”一书中)。但无论如何,这个存储一点也不慢。缓慢的是保持要访问的节点队列有序。
所以我决定尝试 IDA*,正如我所见,它不需要这种存储(但我仍然必须保留所有访问过的状态以避免循环)。对于需要 35 次或更少移动的解决方案,它会更快,但对于需要 40 次移动的解决方案则要慢得多。
这是我的代码。我是不是做错了什么?

public static State solveIDAStar(State initialState) {
    int limit = initialState.getManhattanDistance() + 2 * initialState.getLinearConflicts();
    State result = null;
    while(result == null) {
        visitedStates.add(initialState); // It's a global variable
        result = limitedSearch(initialState, limit);
        limit = newLimit;
        visitedStates.clear();
    }
    return result;
}

public static State limitedSearch(State current, int limit) {
    for(State s : current.findNext()) {
        if(s.equals(GOAL)) {
            s.setParent(current);
            return s;
        }
        if(!visitedStates.contains(s)) {
            s.setPathCost(current.getPathCost() + 1);
            s.setParent(current);
            int currentCost = s.getManhattanDistance() + 2 * s.getLinearConflicts() + s.getPathCost();
            if(currentCost <= limit) {
                visitedStates.add(s);
                State solution = limitedSearch(s, limit);
                if(solution != null)
                    return solution;
            } else {
                if(currentCost < newLimit)
                    newLimit = currentCost;
            }
        }
    }
    return null;
}

【问题讨论】:

  • 你在哪里修改current?正如我所看到的 - 只有当源与目标之间存在直接边缘时,算法才会返回答案 - 但我可能在这里遗漏了一些东西。你能澄清一下吗?
  • @amit 我不修改对象本身,但我递归地调用该函数,每次给出当前的继任者。这是一个正常的递归 DFS,具有深度限制(使用启发式方法选择)。
  • 是的,我因为某种原因错过了递归调用。感谢您的澄清。

标签: java artificial-intelligence puzzle graph-algorithm heuristics


【解决方案1】:

旧东西下移了。

更改以便 newLimit 可以跳过步骤(最好的解决方案):

State bestSolution; // add this global

public static State solveIDAStar(State initialState) {
    int limit = initialState.getManhattanDistance() + 2 * initialState.getLinearConflicts();
    bestSolution = null; // reset global to null
    State result = null;
    while(result == null) {
        visitedStates.add(initialState); // It's a global variable
        newLimit = INFINITY;
        result = limitedSearch(initialState, limit);
        limit = newLimit;
        visitedStates.clear();
    }
    return result;
}

public static State limitedSearch(State current, int limit) {
    for(State s : current.findNext()) {
        if(s.equals(GOAL)) {
            s.setParent(current);
            return s;
        }
        if(!visitedStates.contains(s)) {
            s.setPathCost(current.getPathCost() + 1);
            s.setParent(current);
            int currentCost = s.getManhattanDistance() + 2 * s.getLinearConflicts() + s.getPathCost();
            if(currentCost <= limit) {
                visitedStates.add(s);
                State solution = limitedSearch(s, limit);
                if(solution != null &&
                   (bestSolution == null || solution.getPathCost() < bestSolution.getPathCost()))
                        bestSolution = solution; // cache solution so far
            } else {
                if(currentCost < newLimit)
                    newLimit = currentCost;
            }
        }
    }
    return null;
}

原答案

所以我找到了一个开源实现。神奇的是,它也在java中。

可以在这里测试应用程序: http://n-puzzle-solver.appspot.com/

具体相关的源代码是: http://code.google.com/p/julien-labs/source/browse/trunk/SlidingPuzzle/src/be/dramaix/ai/slidingpuzzle/server/search/IDAStar.java

不确定下面建议的第一次更改可能会改变多少时间,但我很确定您需要进行第二次更改。


第一次改变

通过对比代码,你会发现这个函数

private Node depthFirstSearch(Node current, int currentCostBound, State goal)

基本上是你的功能

public static State limitedSearch(State current, int limit)

而 Julien Dramaix 的实现没有:

if(!visitedStates.contains(s)) {
    ...
    visitedStates.add(s);

所以把这两条线拿出来测试一下。


第二次改变

您的函数 public static State solveIDAStar(State initialState) 在 while 循环中做了一些奇怪的事情。

失败一次后,将最大深度(极限)设置为无穷大。基本上,在第 1 次迭代中,您会尝试找到与您的启发式方法一样好的解决方案。然后你试图找到任何解决方案。这不是迭代深化

迭代加深意味着每次尝试时,都要深入一点。

确实,查看public PuzzleSolution resolve(State start, State goal) 中的while 循环,您会发现nextCostBound+=2;。这意味着,每次尝试时,请尝试通过最多 2 个动作找到解决方案。


否则,其他一切看起来都相似(尽管您对 State 类的确切实现可能略有不同)。

如果效果更好,您可能还想在http://code.google.com/p/julien-labs/source/browse/#svn%2Ftrunk%2FSlidingPuzzle%2Fsrc%2Fbe%2Fdramaix%2Fai%2Fslidingpuzzle%2Fclient 尝试其他一些启发式方法。

启发式算法位于 server/search/heuristic 文件夹中。

【讨论】:

  • 我已经看到了。我可以使用的唯一更好的启发式方法是模式数据库,因为通过线性冲突调整的曼哈顿距离已经非常有效。关于第一个更改:我进行检查以避免循环或返回已访问的状态。由于空间是一个图形,我认为启发式方法不一致,所以我必须进行检查。
  • 关于循环:一个有效的实现应该将限制设置为任何超过上一次迭代限制的节点的最小 f 成本(f = 路径成本 + 启发式)。这就是我想做的。其实你是对的,我必须删除newCost = INFINITE(我编辑了代码)。但是现在算法对于需要少量移动的解决方案更快,而对于更大的解决方案则更慢。顺便说一句,使用newLimit += 2 对于小解决方案来说速度较慢,对于更大的解决方案来说速度更快。我不知道为什么。
  • 在第二次更改中,+= 2 是迭代深化的一部分。首先尝试用很少的动作(例如 10 个动作)找到一个解决方案。如果什么都没找到,就把你的工作扔掉,试着用最多 12 个动作找到一个解决方案。如果什么都没找到,就把你的工作扔掉,尝试使用多达 14 个动作找到解决方案……。优点是如果最优解需要 50 步,算法将不会尝试需要 200 步的解(这可能在一个巨大的搜索空间内)。
  • 通过第一次更改,只要您进行了第二次更改,您回到已经访问过的状态并不重要。那是因为随着迭代深化,无论如何你都在限制你的动作。所以你不会一直在寻找。这在一般的图中是否有用,取决于它尝试重新访问一个状态的可能性,以及访问了多少个状态。对于这个问题,我认为进行更改会有所改善,因为将其添加到已访问状态容器中会在每次插入和搜索时强制执行 O(log n) 操作(我假设您在那里有一个自平衡 BST)。
  • @NIck 我也在寻找。经过一番挖掘,找到了它转移到 Github 的位置:github.com/jDramaix/SlidingPuzzle
【解决方案2】:

一个小问题:您说“缓慢的是保持要访问的节点队列有序。”。在这种情况下,您可以使用“优先堆”。这是一个偏序队列,它总是返回 e 队列中的最小(或最大)项,插入、检索 - 删除是 O(log n),因此这可以使您的初始 A* 算法更快一些。这里给你发一个简单的实现,但是是用C#做的,你需要把它翻译成Java...

public class PriorityHeap<T>
{
    private int count;
    private int defaultLength = 10;
    private PriorityHeapNode[] array;
    private bool isMin;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="isMin">true si quiere que la ColaHeap devuelva el elemento de menor Priority, falso si quiere que devuelva el de mayor</param>
    public PriorityHeap(bool isMin)
    {
        this.count = 0;
        this.isMin = isMin;
        this.array = new PriorityHeapNode[defaultLength];
    }
    public PriorityHeap(bool isMin, int iniLength)
    {
        this.count = 0;
        this.isMin = isMin;
        this.defaultLength = iniLength;
        this.array = new PriorityHeapNode[defaultLength];
    }

    public class PriorityHeapNode
    {
        T valor;
        int _priority;

        public PriorityHeapNode(T valor, int _priority)
        {
            this.valor = valor;
            this._priority = _priority;
        }

        public T Valor
        {
            get
            { return this.valor; }
        }
        public double Priority
        {
            get
            { return this._priority; }
        }

    }
    public int Count
    { get { return this.count; } }

    /// <summary>
    /// Devuelve true si la cola devuelve el valor de menor Priority, falso si el de mayor
    /// </summary>
    public bool IsMin
    { get { return isMin; } }

    /// <summary>
    /// Devuelve y quita el Valor Minimo si la cola lo permite,si no, retorna null
    /// </summary>
    /// <returns></returns>
    public PriorityHeapNode GetTopAndDelete()
    {
        PriorityHeapNode toRet;
        if (count > 0)
        {
            if (count == 1)
            {
                toRet = array[0];
                array[0] = null;
                count--;
                return toRet;
            }
            else
            {
                toRet = array[0];
                array[0] = array[count - 1];
                array[count - 1] = null;
                HeapyfiToDown(0);
                count--;
                return toRet;
            }
        }
        else return null;

    }

    /// <summary>
    /// Devuelve el tope pero no lo borra
    /// </summary>
    /// <returns></returns>
    public PriorityHeapNode GetTop()
    {
        return array[0];
    }
    public void Insert(PriorityHeapNode p)
    {
        if (array.Length == count)
            Add(p);
        else array[count] = p;
        count++;
        HeapyfiToUp(count - 1);
    }

    public void Clear()
    {
        count = 0;
    }

    #region Private Functions
    private int GetFather(int i)
    {
        return ((i + 1) / 2) - 1;
    }
    private int GetRightSon(int i)
    { return 2 * i + 2; }
    private int GetLeftSon(int i)
    { return 2 * i + 1; }

    private void Add(PriorityHeapNode p)
    {
        if (array.Length == count)
        {
            PriorityHeapNode[] t = new PriorityHeapNode[array.Length * 2];
            for (int i = 0; i < array.Length; i++)
            {
                t[i] = array[i];
            }
            t[count] = p;
            array = t;
        }
    }

    private void HeapyfiToUp(int i)
    {
        if (isMin)
        {
            int father = GetFather(i);
            if (father > -1 && array[father].Priority > array[i].Priority)
            {
                PriorityHeapNode t = array[father];
                array[father] = array[i];
                array[i] = t;
                HeapyfiToUp(father);
            }
        }
        else
        {
            int father = GetFather(i);
            if (father > -1 && array[father].Priority < array[i].Priority)
            {
                PriorityHeapNode t = array[father];
                array[father] = array[i];
                array[i] = t;
                HeapyfiToUp(father);
            }
        }
    }
    private void HeapyfiToDown(int i)
    {
        if (isMin)
        {
            #region HeapyFi To down Min
            int l = GetLeftSon(i);
            int r = GetRightSon(i);

            if (r < count)
            {
                PriorityHeapNode right = array[r];
                PriorityHeapNode left = array[l];
                int t;
                if (right != null && left != null)
                {
                    t = left.Priority < right.Priority ? l : r;
                }
                else if (right != null)
                    t = r;

                else if (left != null)
                    t = l;
                else return;

                if (array[t].Priority < array[i].Priority)
                {
                    PriorityHeapNode temp = array[t];
                    array[t] = array[i];
                    array[i] = temp;
                    HeapyfiToDown(t);
                }
            }
            else if (l < count)
            {
                PriorityHeapNode left = array[l];
                int t;
                if (left != null)
                    t = l;
                else return;
                if (array[t].Priority < array[i].Priority)
                {
                    PriorityHeapNode temp = array[t];
                    array[t] = array[i];
                    array[i] = temp;
                    HeapyfiToDown(t);
                }
            }
            #endregion
        }
        else
        {
            #region HeapyFi To down NOT Min
            int l = GetLeftSon(i);
            int r = GetRightSon(i);

            if (r < count)
            {
                PriorityHeapNode right = array[r];
                PriorityHeapNode left = array[l];
                int t;
                if (right != null && left != null)
                {
                    t = left.Priority > right.Priority ? l : r;
                }
                else if (right != null)
                    t = r;

                else if (left != null)
                    t = l;
                else return;

                if (array[t].Priority > array[i].Priority)
                {
                    PriorityHeapNode temp = array[t];
                    array[t] = array[i];
                    array[i] = temp;
                    HeapyfiToDown(t);
                }
            }
            else if (l < count)
            {
                PriorityHeapNode left = array[l];
                int t;
                if (left != null)
                    t = l;
                else return;
                if (array[t].Priority > array[i].Priority)
                {
                    PriorityHeapNode temp = array[t];
                    array[t] = array[i];
                    array[i] = temp;
                    HeapyfiToDown(t);
                }
            }
            #endregion
        }
    }
    #endregion
}      

希望这会有所帮助...

【讨论】:

  • 嗨,我已经尝试过使用它 (java.util.PriorityQueue),但没有任何改变。据我所知,最好设置正确的initialCapacity(我不知道是哪个),但我可能是错的......无论如何谢谢:)
猜你喜欢
  • 1970-01-01
  • 2012-08-14
  • 2011-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多