迭代加深搜索学习笔记

我们都知道 dfs 是一头搜到底,这样在搜索树深度未知时可能会搜的很慢。而我们又知道 bfs 是一层一层搜,这样可以免去搜索树深度未知的影响。但是 bfs 使用了一个队列维护状态,在状态很大时(比如整个棋盘)就会爆内存。

那有没有两全其美的办法呢?

有,它就是迭代加深搜索。

具体先看一道题:Addition Chains

其实迭代加深搜索很简单,名字挺高大上的,本质上就是一个 dfs 套一个最大深度判断,它在搜索的时候每次会判断当前的深度(搜过的步数)是否大于他设定的最大深度,如果是则直接跳出,最后返回搜到了答案没(满足所有条件)。

对于这道题,假设我们在搜 \(m\leq x\) 时满足条件,搜 \(m \leq x-1\) 时又不满足,则一定 \(m=x\),因为深度大的搜索树一定把深度小的搜索树包含了(类似二分)。

可能不好理解,看一下代码就知道了。

注意,这里是 \(\textit{\textbf{n}}\) 较小时的解法,如果要通过所有数据就得剪枝。

#include <iostream>
using namespace std;

int n;
int a[210];
int b[210];
bool ans;

void dfs(int p,int l) {
    if(ans) {
        return;
    }
    if(p>=l) {
        if(a[p-1]==n) {
            ans=true;
        }
        for(int i=0;i<p;i++) {
            b[i]=a[i];
        }
        return;
    }
    for(int i=0;i<p;i++) {
        for(int j=i;j<p;j++) {
            a[p]=a[i]+a[j];
            if(a[p]>n) {
                break;
            }
            if(a[p]<=a[p-1]) {
                continue;
            }
            dfs(p+1,l);
        }
    }
}

int main() {
    while(cin>>n,n) {
        a[0]=1;
        for(int i=1;;i++) {
            ans=false;
            dfs(1,i);
            if(ans) {
                for(int j=0;j<i;j++) {
                    cout<<b[j]<<" ";
                }
                cout<<endl;
                break;
            }
        }
    }
    return 0;
}

我们知道深度大的搜索树会包含深度小的搜索树,所以那些深度小的状态可能会被搜很多次,在某些情况下会超时。我们设能搜到答案的最大深度最小的这次 dfs 的复杂度大致是 \(O(n^{m})\),则整个迭代加深搜索的复杂度就是 \(O(\sum_{i=1}^{m}n^{i})\),有时还会比普通的 dfs 慢。所以在你觉得答案很大的时候,最好还是不要考虑搜索。对于上面那道题,答案一般是 \(10\) 左右,所以可以使用迭代加深搜索。

习题:全谷只有两道有迭代加深搜索标签的题,自己去做吧......


IDA* 算法就是迭代加深搜索的一个应用。

其实 IDA* 虽然和 A* 名字有点像,但相同点只有估价函数(不懂的一会解释)。主要是因为 IDA* 是 dfs 改良的,A* 是 bfs 改良的。

回归算法本身,要弄懂 IDA,首先得知道估价函数。估价函数其实就是一个评估状态好坏的函数,大部分时候就是离答案的距离(要走多少步才能到答案)。当然,估价函数有一个“估”字,不需要算的太准确。要是能完全算的准确,还要 IDA 干啥。我们设估价函数评估当前状态的返回值为 \(x\),当前走了 \(step\) 步,设置的最大深度为 \(m\),则在 \(x=0\) 时就找到了答案,如果 \(step+x>m\) 就可以直接跳出。

第一个很好理解,第二个是为啥?

因为当前已经走了 \(step\) 步,要走到答案至少要走 \(x\) 步,那么走到答案的深度肯定已经超过了 \(step+x\) ,但 \(step+x\) 都比 \(m\) 大,所以走到答案的深度肯定比 \(m\) 大。

发现了吗,只有估价函数估出的结果小于等于真正的结果才行。所以在写估价函数时一定要注意,不然可能会把正确的答案给跳出。

放一道题:骑士精神

这道题就是 IDA* 的众多模板中的一道。这题的估价函数就是计算当前状态与最终状态不同的位置个数(空格也算),dfs 时三个参数分别表示空格的 \(x\) 坐标,\(y\) 坐标和当前深度,其他的看代码理解。

#include <iostream>
using namespace std;

int T,m;
int a[10][10];
int b[10][10]={
    {0,0,0,0,0,0},
    {0,1,1,1,1,1},
    {0,0,1,1,1,1},
    {0,0,0,2,1,1},
    {0,0,0,0,0,1},
    {0,0,0,0,0,0}
};
int fx[8][2]={
    {-2,1},{-2,-1},{2,1},{2,-1},
    {1,-2},{-1,-2},{1,2},{-1,2}
};
bool flg=false;

int gj() {
    int ans=0;
    for(int i=1;i<=5;i++) {
        for(int j=1;j<=5;j++) {
            if(a[i][j]!=b[i][j]) {
                ans++;
            }
        }
    }
    return ans;
}

bool in(int x,int y) {
    return x>=1&&x<=5&&y>=1&&y<=5;
}

void dfs(int p,int x,int y) {
    int tmp=gj();
    if(tmp==0) {
        flg=true;
    }
    if(flg) {
        return;
    }
    if(p+tmp>m+1) {
        return;
    }
    for(int i=0;i<8;i++) {
        int nx=x+fx[i][0];
        int ny=y+fx[i][1];
        if(!in(nx,ny)) {
            continue;
        }
        swap(a[x][y],a[nx][ny]);
        dfs(p+1,nx,ny);
        swap(a[x][y],a[nx][ny]);
    }
}

void solve() {
    int x,y;
    flg=false;
    for(int i=1;i<=5;i++) {
        for(int j=1;j<=5;j++) {
            char ch;
            cin>>ch;
            if(ch=='*') {
                a[i][j]=2;
                x=i;
                y=j;
            } else {
                a[i][j]=ch-'0';
            }
        }
    }
    for(m=0;m<=15;m++) {
        dfs(0,x,y);
        if(flg) {
            cout<<m<<endl;
            return;
        }
    }
    cout<<-1<<endl;
    return;
}

int main() {
    cin>>T;
    while(T--) {
        solve();
    }
    return 0;
}

习题:全谷只有六道有 IDA* 标签的题,自己去做吧......


感谢观看!

\[\texttt{--End--} \]

posted @   xlaser  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示