曼哈顿距离
(出租车距离)走网格线的矩形路线
欧氏距离
以空间为基准的两点之间最短距离
切比雪夫距离
维度为3及以上,例,出物理距离外,还要加上财力等权值。
明氏距离
明科夫斯基距离
维度为1时,等价与曼哈顿距离
为2时,等价与欧式距离
3及以上,等价于切比雪夫距离
汉明距离
在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。
八数码问题
八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。所谓问题的一个状态就是棋子在棋盘上的一种摆法。棋子移动后,状态就会发生改变。解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。八数码问题一般使用搜索法来解。
判断是否有解
每一个状态可认为是一个1×9的矩阵,问题即通过矩阵的变换,是否可以变换为目标状态对应的矩阵?由数学知识可知,可计算这两个有序数列的逆序值,即每一个数之前小于它的数字的个数之和(0表示空格,不包括0)。如果两者都是偶数或奇数,则可通过变换到达,否则,这两个状态不可达。
搜索方法
仅为空格制定4种走步,因为只有紧靠空格的棋牌才能移动。
空格移动的唯一约束是不能移出棋盘。
广度BFS 将每次搜索结果放在OPEN表末端
深度DFS将每次搜索结果放在OPEN表前端
往往给出一个节点扩展的最大深度——深度界限。
启发式搜索 重排OPEN表,选择最有希望的节点加以扩展
启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。对位置的估价是十分重要的,采用了不同的估价可以有不同的效果。
启发中的估价是用估价函数表示的,如:f(n) = g(n) +h(n)其中f(n) 是节点n的估价函数,g(n)是在状态空间中从初始节点到n节点的实际代价,h(n)是从n到目标节点最佳路径的估计代价。在此八数码问题中,显然g(n)就是从初始状态变换到当前状态所移动的步数, h(n)为不在目标位的棋子个,即错位数。
A*
在最短路径搜寻效率上,一般有 A*>Dijkstra、双向 BFS,其中Dijkstra、双向 BFS到底哪个算法更优,还得看具体情况。
A*算法最为核心的过程,就在每次选择下一个当前搜索点时,是从所有已探知的但未搜索过点中(可能是不同层,亦可不在同一条支路上),选取 f值最小的结点进行展开。而所有“已探知的但未搜索过点”可以通过一个按 f 值升序的队列(即优先队列)进行排列。
如果重排OPEN表是依据f(n)=g(n)+h(n)进行的,则称该过程为A算法。在A算法中,如果对所有的n存在h(n)≤h*(n),则称h(n)为h*(n)的下界,它表示某种偏于保守的估计。采用h*(n)的下界h(n)为启发函数的A算法,称为A*算法。当h=0时,A*算法就变为有序搜索算法。
在A算法中,如果满足条件:
(1) g(n)是对g*(n)的估计,且g(n)>0;
(2) h(n)是h*(n)的下界,即对任意节点n均有0≤h(n)≤h*(n)则A算法称为A*
A*算法的可纳性,对任一个图,存在从S到目标的路径,如果一个搜索算法总是结束在一条从S到目标的最佳路径上,则称此算法是可采纳的。算法A*保证只要最短路径存在,就一定能找出这条路径,所以算法A*是可纳的。
估价函数:
(1)f(n)=d(n)+w(n)
其中:d(n)为n的深度 w(n)为不在位的棋子数
取h(n)=w(n),则有w(n)≤h*(n),h(n)满足A*算法的限制条件。
(2)f(n)=d(n)+p(n)
启发函数h(n)=p(n),p(n)为不在位的棋子与其目标位置的距离之和,则有p(n)≤h*(n),满足A*算法的限制条件。
w(n)——不在位的棋子数,不够贴切,错误选用节点加以扩展。
更接近于h*(n)的h(n),其值是节点n与目标状态节点相比较,每个错位棋子在假设不受阻拦的情况下,移动到目标状态相应位置所需走步的总和。p(n)比w(n)更接近于h*(n),因为p(n)不仅考虑了错位因素,还考虑了错位的距离(移动次数)。
说明h值越大,启发功能越强,搜索效率越高.特别地
(1)h(n)=h*(n) 搜索仅沿最佳路径进行,效率最高.
(2)h(n)=0 无启发信息, 盲目搜索,效率低.
(3)h(n)>h*(n) 是一般的A算法,效率高,但不能保证找到最佳路径. 有时为求解难题取h(n)>h*(n), 以提高效率.
//节点结构体
typedef struct Node {
int data[9]; 棋盘状态
double f,g; 当前f值和g
struct Node * parent; 指向父节点
}Node,*Lnode;
//OPEN CLOSED 表结构体
typedef struct Stack {
Node * npoint; 所包含的节点
struct Stack * next; 指向下一节点
}Stack,* Lstack;
//选取 OPEN 表上 f 值最小的节点,返回该节点地址
Node * Minf(Lstack * Open) {
Lstack temp = (*Open)->next,min = (*Open)->next,minp = (*Open);
Node * minx;
while(temp->next != NULL)
{ 比较与下一节点的f值 小则更新
if((temp->next ->npoint->f) < (min->npoint->f))
{
min = temp->next;
minp = temp;
}
temp = temp->next;
}
将该节点从OPEN表上删除
minx = min->npoint;
temp = minp->next;
minp->next = minp->next->next;
free(temp);
return minx;
}
//判断是否可解 1表示可解
某状态和目标状态
int Canslove(Node * suc, Node * goal) {
int a = 0,b = 0,i,j;
for(i = 1; i< 9;i++) 对于棋盘数组中的每一个数
for(j = 0;j < i;j++) { 计算之前不为0的小于它的个数之和
if((suc->data[i] > suc->data[j]) && suc->data[j] != 0)
a++;
if((goal->data[i] > goal->data[j]) && goal->data[j] != 0)
b++;
}
if(a%2 == b%2) 若同为偶数或同为奇数
return 1;
else
return 0;
}
//判断节点是否相等 ,1 相等,0 不相等
int Equal(Node * suc,Node * goal) {
for(int i = 0; i < 9; i ++ )
有一个不相等 则返回失败
if(suc->data[i] != goal->data[i])return 0;
return 1;
}
//判断节点是否属于 OPEN 表 或 CLOSED 表,是则返回节点地址,否则返回空地址
Node * Belong(Node * suc,Lstack * list) {
Lstack temp = (*list) -> next ;
if(temp == NULL)return NULL;
while(temp != NULL)
{ 与表中每一个节点比较
if(Equal(suc,temp->npoint))return temp -> npoint;
temp = temp->next;
}
return NULL;
}
//把节点放入 OPEN 或 CLOSED 表中 插入表
void Putinto(Node * suc,Lstack * list)
{
Stack * temp;
temp =(Stack *) malloc(sizeof(Stack));
temp->npoint = suc;
temp->next = (*list)->next;
(*list)->next = temp;
}
///计算 f 值部分-开始//
double Fvalue(Node suc, Node goal, float speed) {
//计算 f 值
double Distance(Node,Node,int);
double h = 0;
累计每个点的错位和
for(int i = 1; i <= 8; i++)
h = h + Distance(suc, goal, i);
return h*speed + suc.g;
//f = h + g(speed 值增加时搜索过程以找到目标为优先因此可能不会返回最优解)
}
double Distance(Node suc, Node goal, int i) {
//计算方格的错位距离
int k,h1,h2;
for(k = 0; k < 9; k++)
{
分别找到该数在两个表的位置
if(suc.data[k] == i)h1 = k;
if(goal.data[k] == i)h2 = k;
}
错位行数与错位列数和
return double(fabs(h1/3 - h2/3) + fabs(h1%3 - h2%3));
}
///计算 f 值部分-结束//
///扩展后继节点部分的函数-开始/
*
int BelongProgram(Lnode * suc ,Lstack * Open ,Lstack * Closed ,Node goal ,float speed)
{//判断子节点是否属于 OPEN 或 CLOSED 表 并作出相应的处理
Node * temp = NULL;
int flag = 0;
if((Belong(*suc,Open) != NULL) || (Belong(*suc,Closed) != NULL))
{
if(Belong(*suc,Open) != NULL) temp = Belong(*suc,Open);
else temp = Belong(*suc,Closed);
取得节点在表中的地址 价值小则更新节点
if(((*suc)->g) < (temp->g))
{
temp->parent = (*suc)->parent;
temp->g = (*suc)->g;
temp->f = (*suc)->f;
flag = 1;
}
}
else {
不在则插入OPEN表 计算f值
Putinto(* suc, Open);
(*suc)->f = Fvalue(**suc, goal, speed);
}
return flag;
}
void Spread(Lnode * suc, Lstack * Open, Lstack * Closed, Node goal, float speed)
{//扩展后继节点总函数
int i;
Node * child;
for(i = 0; i < 4; i++)
{
if(Canspread(**suc, i+1)) //判断某个方向上的子节点可否扩展
{
child = (Node *) malloc(sizeof(Node)); //扩展子节点
child->g = (*suc)->g +1; //算子节点的 g 值
child->parent = (*suc); //子节点父指针指向父节点
Spreadchild(child, i); //向该方向移动空格生成子节点
if(BelongProgram(&child, Open, Closed, goal, speed)) // 判断子节点是否属
于 OPEN 或 CLOSED 表 并作出相应的处理
free(child);
}
}
}
///扩展后继节点部分的函数-结束//
Node * Process(Lnode * org, Lnode * goal, Lstack * Open, Lstack * Closed, float speed)
{//总执行函数
while(1)
{
if((*Open)->next == NULL)return NULL; //判断 OPEN 表是否为空,为空则失败退出
Node * minf = Minf(Open); //从 OPEN 表中取出 f 值最小的节点
Putinto(minf, Closed); //将节点放入 CLOSED 表中
if(Equal(minf, *goal))return minf; //如果当前节点是目标节点,则成功退出
Spread(&minf, Open, Closed, **goal, speed); //当前节点不是目标节点时扩展
当前节点的后继
节点
}
}
int Shownum(Node * result)
{//递归显示从初始状态到达目标状态的移动方法
if(result == NULL)return 0;
else {
int n = Shownum(result->parent);
for(int i = 0; i < 3; i++) {
printf("\n");
for(int j = 0; j < 3; j++)
{
if(result->data[i*3+j] != 0)
printf(" %d ",result->data[i*3+j]);
else printf(" ");
}
}
printf("\n");
return n+1;
}
}