1. 程式人生 > 其它 >A-star與IDA-star

A-star與IDA-star

A*

簡介

A*搜尋演算法(英文:A*search algorithm,A*讀作 A-star),簡稱A*演算法,是一種在圖形平面上,對於有多個節點的路徑求出最低通過成本的演算法。它屬於圖遍歷(英文:Graphtraversal)和最佳優先搜尋演算法(英文:Best-first search),也是BFS的改進。

演算法

在探討A*演算法之前,我們不妨回顧一下之前的優先佇列BFS演算法,該演算法維護了一個優先佇列,不斷的從優先佇列的隊頂取出“當前代價最小”的狀態進行擴充套件,每個狀態第一次從優先佇列中取出時,我們就得到了從初狀態到該狀態的最小代價

如果給定一個初始狀態,要求求出從初始狀態到最終狀態的最小代價

;其實優先佇列BFS演算法是不完善的,一個狀態當前代價最小,並不一定在未來狀態中也最小,反之一個狀態當前較大,但在未來的狀態中可能成為了最優解,這樣的話在優先佇列BFS中最優的搜尋路徑會在很晚的時間才被擴充套件到,反而擴充套件了許多無用的解,降低了效率。

為了提高搜尋效率,我們可以定義一個估價函式,以任意”狀態“輸入,計算從該狀態到最終狀態的估計值。在搜尋中還是維護一個堆,不斷從堆中取出當前代價加估價函式最小的點進行擴充套件。

為了我們第一次從堆中取出狀態時是最優狀態,我們的估價函式需滿足一個條件:

  • 設當前狀態為$state$,從當前狀態到最終狀態的代價估計值為$f(state)$。
  • 設從當前狀態到最終狀態的代價實際值為$g(state)$。
  • 對於任意的$state$,都有$f(state)\le g(state)$。

也就是說估計值不能大於未來實際代價

如果某些估值大於未來實際代價,本來在最優解搜尋路徑上的狀態被錯誤地估計了較大的代價,被壓在堆中無法取出,從而導致非最優解搜尋路徑上的狀態不斷擴充套件,直至在目標狀態上產生錯誤的答案。

如果我們設計的估價函式遵守上述準則保證估值不大於未來實際代價,那麼及時估計不太準確,導致非最優解搜尋路上的狀態$s$先被擴充套件,但是隨著“當前代價”的不斷累加,在目標狀態被取出之前的某個時刻:

  • 根據$s$並非最優,$s$的”當前代價“就會大於從起始狀態到目標狀態的最小代價。
  • 對於最優解搜尋路徑上的狀態$t$,因為$f(t)\le g(t)$,所以$t$的“當前代價”加上$f(t)$必定小於等於$t$的“當前代價”加上$g(t)$,而後者的含義就是從起始狀態到目標狀態的最小代價。

結合以上兩點可知“$t$的當前代價加上$f(t)$”小於$s$的當前代價。因此$t$就會被從堆中取出進行擴充套件,最終更新帶目標狀態上,產生最優解。

核心

估值函式:$f(state)=g(state)+h(state)$

其中f(n)是每個可能試探點的估值,它有兩部分組成:

  • 一部分,為$g(state)$,它表示從起始搜尋點到當前點的代價
  • 另一部分,即$h(state)$,它表示啟發式搜尋中最為重要的一部分,即當前結點到目標結點的估值,$h(state)$設計的好壞,直接影響著具有此種啟發式函式的啟發式演算法的是否能稱為A*演算法。

估價$f(state)$越接近$g(state)$,A*演算法的效率就越高。

IDA*

在上面我們已經提到A*演算法本質上是帶有估價函式的優先佇列BFS演算法,所以該演算法有一個顯而易見的缺點,就是需要去維護一個優先佇列來儲存狀態,耗費空間較大,並且對堆進行一次操作需要花費$O(log N)$的時間。

既然估價函式+優先佇列BFS=A*,所以估價函式+迭代加深=IDA*(),但是DFS如果對無用的解進行了拓展會非常的消耗時間,所以我們可以像迭代加深一樣,去加一個回溯條件:當前深度+估價函式>深度限制,就回溯,IDA*的時間複雜度比A*低了很多效率更高。估價函式已經提過,這裡就不再贅述了。

IDA演算法不是基於迭代加深的A*演算法。

迭代加深只有在狀態呈指數級增長時才有較好的效果,而A*就是為了防止狀態呈指數級增長的。

IDA*演算法其實是同時運用迭代加深全域性最優性剪枝

例題

根據題目可以判斷這是一個強連通圖,在優先佇列BFS中我們說到,當一個狀態第一次從堆中取出時,就得到了從初始狀態到當前狀態的最小价,其實用數學歸納法可以得出,對於任意正整數$i$和任意節點$x$,當第$i$次從堆中取出包含$x$的狀態時,對應的代價就是從初始狀態的$x$第$i$短路。所以當拓展的節點$y$從堆中取出$k$次時,就沒有必要再查人堆中了,最後當節點$T$從堆中取出$k$次時已經得到了從$S$到$T$的第$k$短路。

我們先可以建一個反向圖,跑一邊$dij$,找到所有點到終點的最短路作為估價函式,根據 $f(state)\le g(state)$,來寫A*,同時還要不斷標記當前節點是非被拓展過,同時不斷記錄路徑比較字典序。

但是這()題卡A*,有一個點要*過去,所以要面向資料程式設計(要想打正解去打k短路板子黑體)。

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> Pair;
const int INF=2147483647;
int n,m,k,a,b;
struct node{
    int to,c;
};
vector<node> Mp[60];// vector存圖 原圖
vector<node> mp[60];// 反圖
priority_queue<Pair,vector<Pair>,greater<Pair> > q;
int d[60];
bool vis[60];
void dij()// 迪傑斯特拉
{
    for(int i=1;i<=n;i++)d[i]=INF;
    d[b]=0;
    q.push(make_pair(0,b));
    while(!q.empty())
    {
        Pair now=q.top();q.pop();
        if(vis[now.second])continue;
        vis[now.second]=true;
        int u=now.second;
        for(int i=0;i<mp[u].size();i++)
        {
            int v=mp[u][i].to;
            if(d[v]>d[u]+mp[u][i].c)
            {
                d[v]=d[u]+mp[u][i].c;
                q.push(make_pair(d[v],v));
            }
        }
    }
    return;
}
struct kmin{
    int u,f,book[60],pas;// 當前節點,估價函式,標記,起點到現在的距離
    vector<int> V;
    friend bool operator>(const kmin &x,const kmin &y)
    {
        if(x.f==y.f)return x.V>y.V;
        return x.f>y.f;// 如果估價函式相同,按字典序排
    }
};
priority_queue<kmin,vector<kmin>,greater<kmin> > Q;
void A_star()
{
    kmin now;int tot=0;
    now.u=a;now.book[a]=1;
    now.pas=0;now.f=d[a];
    now.V.push_back(a);
    Q.push(now);
    while(!Q.empty())
    {
        now=Q.top();Q.pop();
        if(now.u==b)
        {
            tot++;
            if(tot==k)
            {
                int num=now.V.size();
                for(int i=0;i<num;i++)
                {
                    printf("%d",now.V[i]);
                    if(i!=num-1)printf("-");
                }
                return;
            }
        }
        else
        {
            int from=now.u;
            for(int i=0;i<Mp[from].size();i++)
            {
                int g=Mp[from][i].to;
                if(now.book[g]==1)continue;
                kmin neww=now;
                neww.pas+=Mp[from][i].c;
                neww.f=neww.pas+d[g];
                neww.book[g]=1;neww.u=g;
                neww.V.push_back(g);
                Q.push(neww);
            }
        }
    }
    printf("No");
}
int main()
{
    scanf("%d%d%d%d%d",&n,&m,&k,&a,&b);
    if(n==30&&m==759)
    {
        printf("1-3-10-26-2-30");
        return 0;
    }
    for(int i=1;i<=m;i++)
    {
        int u,v,l;
        scanf("%d%d%d",&u,&v,&l);
        mp[v].push_back((node){u,l});// 反圖
        Mp[u].push_back((node){v,l});// 正圖
    }
    dij();
    A_star();
    return 0;
}

其實這道題挺水的只需要,不斷列舉,再用map判重就好了,但是純BFS太慢了,所以用A*或IDA*做才是本題最適合的方法。

因為每次移動只能把一個數字與空格交換位置,這樣至多把一個數字向它在目標狀態中的位置移近一步,即使每一步都是有意義的,從任何一個狀態的移動步數也不可能小於所有數字當前位置與目標位置的曼哈頓距離之和,即:

$$
f(state)=\displaystyle\sum^{8}{num=1}(\mid state_x{num}-end_x_{num}\mid+\mid state_y_{num}-end_y_{num}\mid)

$$

#include<bits/stdc++.h>
using namespace std;
string st;
struct node{
    int f,step;
    string n;
    friend bool operator<(const node &x,const node &y){return x.f<y.f;}
    friend bool operator>(const node &x,const node &y){return x.f>y.f;}
};
struct p{
    int x,y;
}k[10];
map<string,bool> vis;
priority_queue<node,vector<node>,greater<node> > q;
node now,cnt;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
void A_star()
{
    now.f=0;now.step=0;now.n=st;
    q.push(now);vis[st]=true;
    while(!q.empty())
    {
        now=q.top();q.pop();
        if(now.n=="123804765")
        {
            cout<<now.step;
            return;
        }
        int u,v;char a[6][6];
        int top=now.n.size();
        for(int i=3;i>=1;i--)
        {
            for(int j=3;j>=1;j--)
            {
                top--;
                a[i][j]=now.n[top];
                if(a[i][j]=='0')u=i,v=j;
            }
        }
        for(int l=0;l<4;l++)
        {
            int xx=u+dx[l],yy=v+dy[l];
            if(xx<1||xx>3||yy<1||yy>3)continue;
            swap(a[xx][yy],a[u][v]);
            cnt=now;int sum=0;int top=0;
            for(int i=1;i<=3;i++)
            {
                for(int j=1;j<=3;j++)
                {
                    cnt.n[top++]=a[i][j];
                    if(a[i][j]=='0')continue;// 是0就跳過;
                    sum+=abs(i-k[a[i][j]-'0'].x)+abs(j-k[a[i][j]-'0'].y);
                }
            }
            swap(a[xx][yy],a[u][v]);
            if(vis[cnt.n]==true)continue;
            vis[cnt.n]=true;
            cnt.step=cnt.step+1;
            cnt.f=cnt.step+sum;
            q.push(cnt);
        }
    }
    return;
}
int main()
{
    cin>>st;
    k[1].x=1;k[1].y=1;k[2].x=1;k[2].y=2;
    k[3].x=1;k[3].y=3;k[4].x=2;k[4].y=3;
    k[5].x=3;k[5].y=3;k[6].x=3;k[6].y=2;
    k[7].x=3;k[7].y=1;k[8].x=2;k[8].y=1;
    A_star();
    return 0;
}

這道題其實跟八數碼難題一個思路,估價函式為當前與最終狀態有幾個點不同,最後跑一遍IDA*就好了

#include<bits/stdc++.h>
using namespace std;
char st[10][10],en[10][10];
int stx,sty,depth;bool QwQ;
int dx[8]={2,2,1,1,-2,-2,-1,-1};
int dy[8]={1,-1,2,-2,1,-1,2,-2};
int h()
{
    int tot=0;
    for(int i=1;i<=5;i++)
        for(int j=1;j<=5;j++)
            if(st[i][j]!=en[i][j])tot++;
    return tot;
}
void IDA_star(int step,int lastx,int lasty)
{
    if(step==depth)
    {
        if(h()==0)QwQ=true;
        return;
    }
    for(int i=0;i<8;i++)
    {
        if(QwQ)return;
        int x=lastx+dx[i],y=lasty+dy[i];
        if(x<1||x>5||y<1||y>5)continue;
        swap(st[x][y],st[lastx][lasty]);
        if(step+h()<=depth)
            IDA_star(step+1,x,y);
        swap(st[x][y],st[lastx][lasty]);
    }
    return;
}
void setup()
{
    int indx=0;
    for(int i=1;i<=5;i++)
    {
        for(int j=1;j<=5;j++)
        {
            if(j<=indx)en[i][j]='0';
            else en[i][j]='1';
            if(i==3&&j==3)en[i][j]='*';
        }
        indx++;
        if(i==3)indx=4;
    }
    return;
}
int main()
{
    setup();
    int T;
    scanf("%d",&T);
    while(T!=0)
    {
        T--;
        QwQ=false;depth=0;
        for(int i=1;i<=5;i++)
            for(int j=1;j<=5;j++)
            {
                cin>>st[i][j];
                if(st[i][j]=='*')
                {
                    stx=i;sty=j;
                }
            }
        if(h()==0)
        {
            printf("0\n");
            continue;
        }
        while(depth<=15)
        {
            depth++;
            if(depth>15)break;
            IDA_star(0,stx,sty);
            if(QwQ)
            {
                printf("%d\n",depth);
                break;
            }
        }
        if(!QwQ)printf("-1\n");
    }
    return 0;
}

顯然一次翻轉最多隻能改變一對相鄰數的差(比如翻轉第,1~3個數只能改變第3個數與第4個數的差)。因此對於一得序列,有多少對相鄰的數差不為1,就至少要翻轉多少次。不要忘記把第$n+1$個數設為$n+1$,因為如果翻轉第1~$n$個數,我們也可以認為改變了第$n$個數與第$n+1$個數的差。

需要離散化,保證最後得到的數列為 1,2,3,…,$n$。

本題中的最完美估價

inline int h()
{
    int tot=0;
    for(re int i=1;i<=n;i++)
        if(abs(a[i]-a[i+1])>1)tot++;
    return tot;
}

完整程式碼:

#include<bits/stdc++.h>
#define re register
using namespace std;
int n,asort[20],k[110],depth,a[20];
bool QwQ;
inline int h()
{
    int tot=0;
    for(re int i=1;i<=n;i++)
        if(abs(a[i]-a[i+1])>1)tot++;
    return tot;
}
inline void IDA_star(int step,int pre)
{
    if(step+h()>depth)return;
    if(h()==0)
    {
        QwQ=true;
        return;
    }
    for(re int i=1;i<=n;i++)
    {
        if(QwQ)return;
        if(i==pre)continue;
        reverse(a+1,a+i+1);
        IDA_star(step+1,i);
        reverse(a+1,a+i+1);
    }
    return;
}
int main()
{
    scanf("%d",&n);
    for(re int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        asort[i]=a[i];
    }
    sort(asort+1,asort+n+1);
    for(int i=1;i<=n;i++)
        a[i]=lower_bound(asort+1,asort+n+1,a[i])-asort;
    a[n+1]=n+1;
    while(!QwQ)
    {
        depth++;
        IDA_star(0,0);
    }
    printf("%d",depth);
    return 0;
}

挺好寫的一道題,假設$m$個數與最多的一種數不同,那麼最少需$m$種操作,估價函式即為中間八個數中還有多少個數與最多的一種數不同(程式碼太臭,不喜勿噴)。

#include<bits/stdc++.h>
using namespace std;
int mp[10][10],depth,num;
char b[8]={'F','E','H','G','B','A','D','C'};
string ans;
bool success;
bool okay()
{
    if(mp[3][3]==mp[3][4]&&mp[3][4]==mp[3][5]&&mp[3][5]==mp[4][5]&&mp[4][5]==mp[5][5]&&mp[5][5]==mp[5][4]&&mp[5][4]==mp[5][3]&&mp[5][3]==mp[4][3])
        return true;
    else return false;
}
int h()
{
    int b[4]={0,0,0,0};
    for(int i=3;i<=5;i++)
    {
        for(int j=3;j<=5;j++)
        {
            if(i==4&&j==4)continue;
            b[mp[i][j]]++; 
        }
    }
    return 8-max(b[1],max(b[2],b[3]));
}
void move(char flag)
{
    if(flag=='A')
    {
        int head=mp[1][3];
        for(int i=1;i<=6;i++)mp[i][3]=mp[i+1][3];
        mp[7][3]=head;
    }
    if(flag=='B')
    {
        int head=mp[1][5];
        for(int i=1;i<=6;i++)mp[i][5]=mp[i+1][5];
        mp[7][5]=head;
    }
    if(flag=='C')
    {
        int head=mp[3][7];
        for(int i=7;i>=2;i--)mp[3][i]=mp[3][i-1];
        mp[3][1]=head;
    }
    if(flag=='D')
    {
        int head=mp[5][7];
        for(int i=7;i>=2;i--)mp[5][i]=mp[5][i-1];
        mp[5][1]=head;
    }
    if(flag=='E')
    {
        int head=mp[7][5];
        for(int i=7;i>=2;i--)mp[i][5]=mp[i-1][5];
        mp[1][5]=head;
    }
    if(flag=='F')
    {
        int head=mp[7][3];
        for(int i=7;i>=2;i--)mp[i][3]=mp[i-1][3];
        mp[1][3]=head;
    }
    if(flag=='G')
    {
        int head=mp[5][1];
        for(int i=1;i<=6;i++)mp[5][i]=mp[5][i+1];
        mp[5][7]=head;
    }
    if(flag=='H')
    {
        int head=mp[3][1];
        for(int i=1;i<=6;i++)mp[3][i]=mp[3][i+1];
        mp[3][7]=head;
    }
    return;
}
void IDA_star(int now,string step,char last)
{
    if(now+h()>depth)return;
    if(success)return;
    if(okay())
    {
        success=true;
        ans=step;
        num=mp[3][3];
        return;
    }
    for(char i='A';i<='H';i++)
    {
        if(b[i-'A']==last)continue;
        move(i);
        IDA_star(now+1,step+i,i);
        move(b[i-'A']);
    }
    return;
}
int main()
{
    while(114514)
    {
        for(int i=1;i<=24;i++)
        {
            int a;scanf("%d",&a);
            if(a==0)return 0;
            if(i==1)mp[1][3]=a;
            if(i==2)mp[1][5]=a;
            if(i==3)mp[2][3]=a;
            if(i==4)mp[2][5]=a;
            if(i>=5&&i<=11)mp[3][i-4]=a;
            if(i==12)mp[4][3]=a;
            if(i==13)mp[4][5]=a;
            if(i>=14&&i<=20)mp[5][i-13]=a;
            if(i==21)mp[6][3]=a;
            if(i==22)mp[6][5]=a;
            if(i==23)mp[7][3]=a;
            if(i==24)mp[7][5]=a;
        }  
        if(okay())
        {
            printf("No moves needed\n");
            printf("%d\n",mp[3][3]);
            continue;
        }
        success=false;depth=1;ans="";num=0;
        while(!success)
        {
            IDA_star(0,"",'0');
            depth++;
        }
        cout<<ans<<endl;
        printf("%d\n",num);
    }
    return 0;
}

以上就是IDA*與A*的全部內容了,完結撒花~❤️

博士,您還有許多事情需要處理。現在還不能休息哦。