【问题标题】:A* Algorithm with Manhattan Distance Heuristic具有曼哈顿距离启发式的 A* 算法
【发布时间】:2014-06-05 00:49:44
【问题描述】:

我一直在用 C 语言开发一个 15 谜题求解器。我的代码使用的大量内存存在一些问题。

我不会发布我的代码,因为它太长了......我已经实现了我正在使用的大部分库,它可能只会给你带来困惑。让我们从基础开始。

我现在使用的东西是:(全部用 C 实现)

- 斐波那契堆:

/* Struct for the Fibonacci Heap */
typedef struct _fiboheap {
    int  size;           // Number of nodes in the heap
    node min;            // Pointer to the minimun element in the Heap
    funCompare compare;  // Function to compare within nodes in the heap
    freeFunction freeFun;// Function for freeing nodes in the heap
} *fiboheap;


/* Struct of a Fibonacci Heap Node */
typedef struct _node {
    node parent;    // Pointer to the node parent
    node child;     // Pointer to the child node
    node left;      // Pointer to the right sibling
    node right;     // Pointer to the left sibling
    int  degree;    // Degree of the node
    int  mark;      // Mark
    void *key;      // Value of the node (Here is the element)
} *node;

- 哈希表

我唯一没有自己实现的东西,我正在使用 uthash

- 15 个拼图状态表示

这是一个有趣的话题。 在解释这里的情况之前,让我们先思考一下 15-puzzle 问题...众所周知,15-Puzzle 有 16 个图块(我们'将空白瓷砖重新计算为编号为 0 的瓷砖)。那么,有多少种可能的状态有 15-puzzle 问题?好吧,它是阶乘(16)状态。所以,有很多州。这意味着我们希望我们的状态尽可能小……如果我们的初始状态离解太远,我们可能会探索太多的状态,以至于我们的程序内存会爆炸。

所以...我的 15 谜题表示包括使用位和逻辑运算符:

/* Struct for the 15-puzzle States Representation */
typedef struct _state {
    unsigned int quad_1; /* This int represent the first 8 numbers */
    unsigned int quad_2; /* This int represent the last 8 numbers */
    unsigned short zero; /* This is the position of the zero */
} *state;

所以我正在做的是使用逻辑运算符来移动和更改数字,使用最小的空间。

注意这个结构体的大小是12字节(因为它是三个整数)

- 曼哈顿距离

这就是众所周知的曼哈顿距离启发式算法。在这里你基本上计算每个数字当前位置到目标状态下的数字位置的距离之和。

- 与 A* 一起使用的 A* 实现和节点结构

让我们从节点开始。

typedef struct nodo_struct {
    nodo parent;        // Pointer to the parent of the node
    state estado;       // State associated with the node
    unsigned int g : 8; // Cost of the node
    action a;           // Action by which we arrived to this node
                        // If null, it means that this is the initial node
} *nodo;

注意这些节点与斐波那契堆中的节点无关。

现在这个话题的主要原因......我目前正在使用的A*的伪代码。

a_star(state initial_state) {
    q = new_fibo_heap; // Sorted by (Cost g) + (Manhattan Distance)
                       // It will have nodes which contain a pointer to the state
    q.push(make_root_node(initial_state));
    closed = new_hash_table();
    while (!q.empty()) {
        n = q.pop();
        if ((n->state ∉ closed) || (n->g < dist(n->state))) { 
        /* The dist used above is stored in the hash table as well */
            closed.insert(n->state);
            dist(n->state) = n->g;   // Update the hash table
            if (is_goal(n->state)) {
                return extract_solution(n); // Go through parents, return the path
            }
            for <a,s> ∈ successors(n->state) {
            // 'a' is the action, It can be 'l', 'r', 'u', 'd'
            // 's' is the state that is a successor of n
                if (manhattan(s) < Infinity) {
                    q.push(make_node(n,a,s));
                    // Assuming that manhattan is safe
                }
            }
        }
    }
    return NULL;
}

所以我还没能回答的问题是……

如何有效地管理内存?你可以重用状态或节点吗?这会带来什么后果?

我的意思是,如果你看一下伪代码。它不考虑重用状态或节点。它只是不断地分配节点和状态,即使它们之前已经被计算过。

我一直在思考这个问题...每次运行求解器时,它都可以快速扩展数百万个节点。正如我们所知,当您找到另一条成本低于前一条的路径时,A* 可能会重新探索节点。这意味着......如果我们探索 100 万个节点,它将是 2400 万字节(哇)......并且考虑到每个节点都有一个状态,每个节点将是 14 个字节,即 1400 万字节...... .

最后,我需要的是一种重用/释放空间的方法,这样我的计算机在执行此求解器时就不会崩溃。

(PD:对不起,很长的帖子)

【问题讨论】:

    标签: performance artificial-intelligence a-star heuristics memory-efficient


    【解决方案1】:

    您这样做是为了完成任务还是为了好玩?

    如果是为了好玩,那就不要使用 A*,使用 IDA*。 IDA* 会更快,并且几乎不使用内存。此外,您可以使用额外的内存来构建更好的启发式算法,并获得更好的性能。 (如果您在谷歌上搜索“模式数据库”,您应该会找到足够的信息。这些是由 Jonathan Schaeffer 和 Joe Culberson 发明的,但由 Rich Korf 和 A​​riel Felner 进行了非常详细的研究。)

    IDA* 有一些缺点,并不适用于所有领域,但对于滑动拼图来说几乎是完美的。

    另一个可以提供帮助的算法是Breadth-First Heuristic Search。这篇论文和其他论文讨论了如何避免完全存储封闭列表。

    基本上,很多聪明人之前已经解决了这个问题,并且他们已经发布了他们的方法/结果,以便您可以向他们学习。

    以下是提高 A* 的一些技巧:

    • 我发现斐波那契堆并没有太大的速度增益,因此您可能希望使用更简单的数据结构。 (尽管自从我上次尝试后,可用的实现可能有所改进。)

    • 节点的 f-cost 将以 2 为增量跳跃。因此,您可以对 f-costs 进行存储,而只需担心在同一 f-cost 层中对项目进行排序。 FIFO 队列实际上工作得很好。

    • 您可以使用this paper 中的想法将 15 拼图表示转换为排列,完整表示大约需要 43 位。但是,扩展状态变得更加昂贵,因为您必须转换为不同的表示来生成移动。

    • 避免使用禁止的运算符完全存储封闭列表。 (有关详细信息,请参阅之前的广度优先启发式搜索论文或Best-First Frontier search 上的这篇论文。)

    希望这些要点能解决您的问题。如果您需要,我很乐意提供更多链接或说明。

    【讨论】:

    • 嗨内森! :) 这既是一项任务,也是一种爱好。你其实是对的。具有曼哈顿距离的 A* 对于解决 15 个谜题并不是很强大。我也在实施 IDA* 和 PDB。我想做的是一张不同算法和不同启发式的对比图,看看他们解决这个问题的效率如何。顺便说一句,我在我的曼哈顿启发式中发现了一些错误。发现这一点后,内存问题改善了很多。现在我的求解器可以做得更好。
    猜你喜欢
    • 2012-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多