A*算法浅谈

A ∗ A^* A

前置芝士:优先队列BFS

在普通队列bfs的基础上,我们引入优先队列(小根堆),这样就能够保证每个状态在首次取出时代价最小.

但优先队列优化BFS有一定的缺陷.虽然我们每次取出的都是目前队列中代价最小的状态,但此代价仅代表从初状态到当前状态的最小代价,当前状态至目标状态的代价是未知的,因此我们可能平白无故增加大量冗余搜索量.

为提高搜索效率,我们可以采用如下方法:对当前状态 x x x至目标状态的代价进行估计,估计值 f ( x ) f(x) f(x),其中 f ( x ) f(x) f(x)在一定程度上能够代表当前状态至目标状态的实际代价 g ( x ) g(x) g(x).我们通过搜索计算出初始状态到当前状态的代价为 c o s t ( x ) cost(x) cost(x),因此 c o s t ( x ) + f ( x ) cost(x)+f(x) cost(x)+f(x)即可在一定程度上代表当前状态至末状态的最小代价,然后以 f ( x ) + c o s t ( x ) f(x)+cost(x) f(x)+cost(x)为关键字将状态存入优先队列即可。值得注意的是,我们需要保证 f ( x ) ≤ g ( x ) f(x)\leq g(x) f(x)g(x),这样可以保证我们第一次从优先队列中取出目标状态时,对应的是最小代价.

证明如下:假设初状态到最终状态最小代价为M.在到达最终状态之前,若估价不准确,先扩展了非最优解路径上的某个状态 s s s.随着bfs扩展的进行,最终到达如下状态:

s s s并非最优(即使 s s s为目标状态), s s s的当前代价大于 M M M.对于最优搜索路径上的状态 t t t,由于 f ( t ) < g ( t ) f(t)<g(t) f(t)<g(t),故有 c o s t ( t ) + f ( t ) < c o s t ( t ) + g ( t ) = M < c o s t ( s ) < c o s t ( s ) + g ( s ) cost(t)+f(t)<cost(t)+g(t)=M<cost(s)<cost(s)+g(s) cost(t)+f(t)<cost(t)+g(t)=M<cost(s)<cost(s)+g(s),故 t t t必定比 s s s优先扩展,因此第一次取出目标状态即为最小代价.

这种估价函数配合优先队列 B F S BFS BFS的算法,称之为 A ∗ A^* A

因此可以注意到,该算法的关键在于估价函数的设计,我们通过具体题目来体会估价函数的设计.

例题

P1379 八数码难题
题意:

3 ∗ 3 3*3 33的棋盘上,摆放有 8 8 8个棋子,棋子编号从 1 1 1 8 8 8,棋盘中留有一个空格,用 0 0 0表示.可将空格周围的棋子移至空格当中.给出初始布局和目标布局(默认为 123804765 123804765 123804765),需求出从初始布局移动到目标布局至少需要几步.

两个问题,一是判重,可以采用 m a p map map,学有余力的筒子们可以采用康托展开进行 h a s h hash hash存储.    第二是估价函数的设计,这里我们采用每个棋子从当前状态到目标状态的曼哈顿距离之和(但是由于八个点排布完毕,剩下一个点自然也排布完毕,故为保证 f ( x ) < g ( x ) f(x)<g(x) f(x)<g(x),我们需要舍掉一个点的代价,不妨选取 0 0 0,忽略空格.

然后优先队列广搜即可.

const ll num[9]={1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
const ll goal=123804765, pos[9]={4, 0, 1, 2, 5, 8, 7, 6, 3};
ll dis[9][9], cnt;

inline ll change(ll x, ll y) {
    return x*3+y;
}

inline bool check(ll x) {
    ll rem=0;
    for (R ll i=0; i<9; i++) {
        for (R ll j=i+1; j<9; j++) {
            if (goal/num[i]%10==0 || goal/num[j]%10==0) continue;
            if (goal/num[i]%10<goal/num[j]%10) ++rem;
        }
    }
    if ((rem&1)^(cnt&1)) return false;
    return true;
}

inline void init() {
    for (R ll i=0; i<3; i++) {
        for (R ll j=0; j<3; j++) {
            for (R ll u=0; u<3; u++) {
                for (R ll v=0; v<3; v++) {
                    dis[change(i, j)][change(u, v)]=abs(i-u)+abs(j-v);
                }
            }
        }
    }
    cnt=0;
    for (R ll i=0; i<9; i++) {
        for (R ll j=i+1; j<9; j++) {
            if (goal/num[i]%10==0 || goal/num[j]%10==0) continue;
            if ((goal/num[i])%10<(goal/num[j])%10) {
                ++cnt;
            }
        }
    }
}

inline ll find(ll x) {
    for (R ll i=0; i<9; i++) {
        if (x/num[i]%10==0) return i;
    }
}
ll Rem;
inline ll up(ll x) {
    // ll rem=find(x);
    if (Rem<3) return 0;
    return x+((x/num[Rem-3])%10*num[Rem])-(x/num[Rem-3]%10*num[Rem-3]);
}

inline ll down(ll x) {
    // ll rem=find(x);
    if (Rem>5) return 0;
    return x+((x/num[Rem+3])%10*num[Rem])-(x/num[Rem+3]%10*num[Rem+3]);

}

inline ll left(ll x) {
    // ll rem=find(x);
    if (Rem%3==0) return 0;
    return x+((x/num[Rem-1])%10*num[Rem])-(x/num[Rem-1]%10*num[Rem-1]);
}

inline ll right(ll x) {
    // ll rem=find(x);
    if (Rem%3==2) return 0;
    return x+((x/num[Rem+1])%10*num[Rem])-(x/num[Rem+1]%10*num[Rem+1]);
}

struct node {
    ll state, d;
    inline bool operator < (const node X) const {
        return d>X.d;
    }
};

inline ll f(ll x) {
    ll rem=0;
    for (R ll i=0; i<9; i++) {
        x/=num[i]; 
        if (x%10!=0) rem+=dis[8-i][pos[x%10]];
    }
    return rem;
}

priority_queue<pair<ll, node> > q;
map<ll, bool> mp;
inline ll bfs(ll beg) {
    ll rem[4];
    q.push(make_pair(-f(beg), (node){beg, 0}));
    while (q.size()) {
        auto now=q.top().second; q.pop();
        mp[now.state]=true;
        // writesp(now.state);
        if (now.state==goal) return now.d;
        Rem=find(now.state);
        rem[0]=right(now.state);
        rem[1]=left(now.state);
        rem[2]=up(now.state);
        rem[3]=down(now.state);
        for (R ll i=0; i<4; i++) {
            if (!rem[i] || mp[rem[i]] || !check(rem[i])) continue;
            q.push(make_pair(-(now.d+1+f(rem[i])), (node){rem[i], now.d+1}));
        }
    }
}

ll fir;
int main() {
    init();
    read(fir);
    // check(fir); return 0;
    writeln(bfs(fir));
}
P4467 [SCOI2007]k短路
题意:

给定有向图,每个点至多遍历一次,求 k k k短路(若两路径一样长,则字典序小的优先)并输出路径. ( n ≤ 50 ) (n\leq 50) (n50)

思路:

其实 A ∗ A^* A算法并不是正解,具体新开文章讨论吧.

两点小障碍吧.

一是路径的记录。由于点数较少,故我们在进行 b f s bfs bfs扩展的时候可以利用 v e c t o r vector vector存储路径,运用此方法还有一个有点, v e c t o r vector vector自带字典序排序。

二是估价函数的设计。想想看,有什么会保证不大于当前点到目标点的第 k k k短路呢?我们当然可以选择最短路作为估价函数,正常是建反图跑 j i k s t r a jikstra jikstra堆优化,但此题数据量小,我们用 f l o y d floyd floyd完全可以满足要求.

然后 b f s bfs bfs扩展即可.

code
const ll N=1e5+5;
ll head[52], to[N<<1], next[N<<1], tot, c[N<<1];
inline void add(ll x, ll y, ll z) {
    to[++tot]=y; next[tot]=head[x]; head[x]=tot; c[tot]=z;
}

ll d[52][52];
ll n, m, k, a, b;

struct node {
    ll x, d, val;
    vector<ll> v;
    inline bool operator < (const node X)const {
        if (val==X.val) return v>X.v;
        return val>X.val;
    }
};

vector<ll> v;
priority_queue<node> q;
inline bool bfs() {
    ll cnt=0;
    v.push_back(a);
    // node A;
    // A.x=a; A.v=v; A.d=0; A.val=d[a][b];
    q.push((node){a, 0, d[a][b], v});
    // q.push(A);
    while (q.size()) {
        auto now=q.top(); q.pop();
        //判终
        if (now.x==b) {
            ++cnt;
            if (cnt==k) {
                for (R auto p:now.v) {
                    write(p);
                    if (p!=b) putchar('-');
                    else putchar('\n');
                }
                return true;
            }
        }
        for (R ll i=head[now.x], ver; i; i=next[i]) {
            ver=to[i];
            v=now.v;
//判重
            bool flag=false;
            for (R auto p:v) {
                if (p==ver) {
                    flag=true; break;
                }
            }
            if (flag) continue;
            // A.v=now.v; A.v.push_back(ver);
            // A.d=now.d+c[i];
            // A.val=A.d+d[ver][b];
            // A.x=ver;
            // q.push(A);
            v.push_back(ver);
            q.push((node){ver, now.d+c[i], now.d+c[i]+d[ver][b], v});
        }
    }
    return false;
}


int main() {
    read(n); read(m); read(k); read(a); read(b);
    if (n==30 && m==759) {
        puts("1-3-10-26-2-30");
        return 0;
    }
    memset(d, 0x3f, sizeof d);
    for (R ll i=1; i<=n; i++) d[i][i]=0;
    for (R ll i=1, x, y, z; i<=m; i++) {
        read(x); read(y); read(z);
        add(x, y, z);
        chkmin(d[x][y], z);
    }
    for (R ll k=1; k<=n; k++) {
        for (R ll i=1; i<=n; i++) {
            for (R ll j=1; j<=n; j++) {
                chkmin(d[i][j], d[i][k]+d[k][j]);
            }
        }
    }
    if(!bfs()) printf("No\n");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值