搜索(4):A*

启发式搜索算法(A算法)

先来回忆BFS算法

在BFS中, 每当我们找到一个节点, 我们就将这个节点压入队列中, 然后每次开始搜索是我们就从队列中拿出第一个元素, 继续开始上述的操作。

但是这里有一个问题, 虽然这里从对头拿元素这个操作可以满足当前最短这个条件

但在队列中有多个长度相同的当前最短的边的时候,队列只能按照压入的顺序弹出, 不能判断这几个当前最短点中的优劣性

为此我们就引入了A*算法, 对每一步搜索中的结果进行估值, 这样我们就能按照估值函数的大小来近似的判断那个点能更容易的到达终点, 进而我们优先对他进行搜索

A*搜索的最佳结果就是估值函数完全精准, 这就能使完全遍历的的BFS函数变为直奔树根的确定性函数

但这基本是不可能的

·
·

将BFS改为A*

将BFS改为A算法

在BFS算法中,若对每个状态n都设定估价函数f(n)=g(n)+h(n)

并且每次从Open表中选节点进行扩展时,都选取f值最小的节点,则该搜索算法为启发式搜索算法,又称A算法。(注意:这是A算法

g(n) : 从起始状态到当前状态n的代价
h(n) : 从当前状态n到目标状态的估计代价

将A算法改为A*算法

A算法中的估价函数若选取不当,则可能找不到解,或找到的解也不是最优解。

因此,需要对估价函数做一些限制,使得算法确保找到最优解(步数,即状态转移次数最少的解)。

A*算法即为对估价函数做了特定限制,且确保找到最优解的A算法。

A*算法分析

f (n)=g(n) +h(n) ,且满足以下限制:

g(n)是从s0到n的真实步数(未必是最优的),因此:
g(n)>0 且g(n)>=g*(n)

h(n)是从n到目标的估计步数。估计总是过于乐观的
即 h(n)<= h*(n)

且h(n)相容,则A算法转变为A*算法。A*正确性证明略。

`
*********************h(n)的相容:**************************

如果h函数对任意状态s1和s2还满足:

h(s1) <= h(s2) + c(s1,s2)

c(s1,s2)是s1转移到s2的步数,则称h是相容的。

h相容能确保随着一步步往前走,f递增,这样A*能更高效找到最优解。

h相容
=>g(s1) + h(s1)
= >g(s1) + h(s2) +c(s1,s2) = g(s2)+h(S2)
=>f(s1) <= f(s2)
即f是递增的。

A*算法伪代码

open=[Start]
closed=[]
while open不为空 
{
    从open中取出估价值f最小的结点n
    if n == Target then
        return 从Start到n的路径 // 找到了!!!
    else 
    {
        for n的每个子结点x
         {
            if x in open 
            {
                计算新的f(x)
                比较open表中的旧f(x)和新f(x)
                if 新f(x) < 旧f(x) 
                {
                    删掉open表里的旧f(x),加入新f(x)
                }
            }
            else if x in closed 
            {
                计算新的f(x)
                比较closed表中的旧f(x)和新f(x)
                if 新f(x) < 旧f(x) 
                {
                    remove x from closed
                    add x to open
                }
            }
            else 
            {
                // x不在open,也不在close,是遇到的新结点
                计算f(x) add x to open
            }
        }
        add n to closed
    }
}
//open表为空表示搜索结束了,那就意味着无解!

优劣性判断

A*算法的搜索效率很大程度上取决于估价函数h(n)。一般说来,在满足h(n)≤h*(n)的前提下,h(n)的值越大越好


八数码问题:
方案一. h(n) 为不在位的数字个数
方案二. h(n) 为不在位的数字到其该呆的位置的曼哈顿距离和

后者优于前者

HDU 1667

#include
#include
#include

using namespace std;
int rot[8][7] =
{
    0, 2, 6, 11, 15, 20, 22,//A
    1, 3, 8, 12, 17, 21, 23,//B
    10, 9, 8, 7, 6, 5, 4,//C
    19, 18, 17, 16, 15, 14, 13,//D
    23, 21, 17, 12, 8, 3, 1,//E
    22, 20, 15, 11, 6, 2, 0,//F
    13, 14, 15, 16, 17, 18, 19,//G
    4, 5, 6, 7, 8, 9, 10//H
};

int res[] = { 5,4,7,6,1,0,3,2 };
int depth;

bool check(char s[])
{
    char ch = s[6];
    for (int i = 0; i < 3; i++)
        if (ch != s[i + 6] || ch != s[i + 15])
            return false;
    return ch == s[11] && ch == s[12];
}

void rotate(int k, char s[])
{
    char ch = s[rot[k][0]];
    for (int i = 0; i<6; i++)
        s[rot[k][i]] = s[rot[k][i + 1]];
    s[rot[k][6]] = ch;
}

bool IDAstar(int k, char st[], char op[], int la)
{
    int cnt[4];
    cnt[1] = cnt[2] = cnt[3] = 0;
    for (int i = 0; i<3; i++)
        cnt[st[i + 6] - '0']++, cnt[st[15 + i] - '0']++;
    cnt[st[11] - '0']++, cnt[st[12] - '0']++;
    cnt[0] = max(max(cnt[1], cnt[2]), cnt[3]);
    if (k + 8 - cnt[0] >= depth)
        return false;
    for (int i = 0; i < 8; i++)
    {
        if (la != -1 && res[i] == la)
            continue;
        op[k] = 'A' + i;
        rotate(i, st);
        if (check(st))
        {
            op[k + 1] = '\0';
            return true;
        }
        else if (IDAstar(k + 1, st, op, i))
            return true;
        rotate(res[i], st);
    }
    return false;
}

int main(void)
{
    char ch;
    while (scanf(" %c", &ch), ch != '0')
    {
        char st[25];
        st[0] = ch;
        for (int i = 1; i < 24; i++)
            scanf(" %c", &st[i]);
        depth = 1;
        if(check(st))
            printf("No moves needed\n%c\n", st[6]);
        else
        {
            char op[200];
            op[0] = 0;
            while (!IDAstar(0, st, op, -1))
                depth++;
            printf("%s\n%c\n", op, st[6]);
        }
    }   
    return 0;
}

你可能感兴趣的:(搜索)