Performance 曼哈顿距离启发式A*算法
我一直在用C语言编写一个15个谜题的求解器。我的代码使用了大量内存,因此我遇到了一些问题 我不会发布我的代码,因为它太长了。。。我已经实现了我正在使用的大多数库,它可能只会给您带来困惑。让我们从基础开始 我现在使用的东西是:(所有这些都是用C实现的) -斐波那契堆: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
/* 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位。但是,扩展状态会变得更加昂贵,因为必须转换为不同的表示才能生成移动
- 避免使用禁止的运算符完全存储已关闭的列表。(有关更多详细信息,请参阅上一篇广度优先启发式搜索文章或上一篇文章。)