Performance 曼哈顿距离启发式A*算法

Performance 曼哈顿距离启发式A*算法,performance,artificial-intelligence,a-star,heuristics,memory-efficient,Performance,Artificial Intelligence,A Star,Heuristics,Memory Efficient,我一直在用C语言编写一个15个谜题的求解器。我的代码使用了大量内存,因此我遇到了一些问题 我不会发布我的代码,因为它太长了。。。我已经实现了我正在使用的大多数库,它可能只会给您带来困惑。让我们从基础开始 我现在使用的东西是:(所有这些都是用C实现的) -斐波那契堆: /* Struct for the Fibonacci Heap */ typedef struct _fiboheap { int size; // Number of nodes in the he

我一直在用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个难题。。。正如我们所知道的,15个谜题有16个瓦片(我们把空白的瓦片作为数字0的瓦片)。那么,15个谜题有多少个可能的状态?这是阶乘(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_星(状态初始状态){
q=new_fibo_heap;//按(成本g)+(曼哈顿距离)排序
//它将具有包含指向状态指针的节点
q、 推送(生成根节点(初始状态));
closed=新的散列表();
而(!q.empty()){
n=q.pop();
如果((n)->状态∉ 关闭)| |(n->gstate)){
/*上面使用的dist也存储在哈希表中*/
关闭。插入(n->状态);
dist(n->state)=n->g;//更新哈希表
如果(是_目标(n->state)){
return extract_solution(n);//遍历父级,返回路径
}
对于∈ 继任者(n->state){
//“a”是动作,可以是“l”、“r”、“u”、“d”
//“s”是n的继承者所在的州
如果(曼哈顿<无限){
q、 push(生成节点(n,a,s));
//假设曼哈顿是安全的
}
}
}
}
返回NULL;
}
所以我还不能回答的问题是

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

我是说,如果你看一下伪代码。它没有考虑重用状态或节点。它只是不断地在节点和状态上分配,即使它们以前已经计算过了

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

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


(PD:抱歉发了这么长的帖子)

你这样做是为了作业还是为了好玩

如果是为了好玩,那么不要用A*,用IDA*。IDA*将更快,而且几乎不使用内存。此外,您可以使用额外的内存来构建更好的启发式,并获得更好的性能。(如果你用谷歌搜索“模式数据库”,你会发现足够的信息。这是乔纳森·谢弗(Jonathan Schaeffer)和乔·卡尔伯森(Joe Culberson)发明的,但里奇·科夫(Rich Korf)和阿里尔·费尔纳(Ariel Felner)对其进行了详细研究。)

IDA*有一些缺点,并不适用于所有领域,但它非常适合于滑动瓷砖拼图

另一个可能有用的算法是。本文和其他文章讨论了如何避免完全存储封闭列表

基本上,很多聪明人以前都解决过这个问题,他们已经公布了他们的方法/结果,所以你可以向他们学习

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

  • 我发现Fibonacci堆并没有带来多少速度提升,所以您可能希望使用更简单的数据结构。(尽管自上次尝试以来,可用的实现可能有所改进。)

  • 节点的f成本将以2的增量跳跃。因此,您可以将f-cost存储起来,只需将项目排序到同一f-cost层中即可。FIFO队列实际上工作得很好

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

  • 避免使用禁止的运算符完全存储已关闭的列表。(有关更多详细信息,请参阅上一篇广度优先启发式搜索文章或上一篇文章。)

希望这些要点能解决您的问题。我