IDA* Algorithm 用四道例题教会你IDA*算法

IDA*

一、概念

简单的说,IDA算法就是迭代加深版的A算法。

二、分析

设计一个估价函数 f ( s t a t e ) < = 真 实 步 数 f(state)<=真实步数 f(state)<=
当 前 步 数 + f ( s t a t e ) > 上 界 当前步数 + f(state) > 上界 +f(state)> 真实步数必然大于上界,可以提前退出。

三、模板代码

bool dfs()
{
    if(now_depth + f() > max_depth) return false;
}
int depth=0;
while(!dfs(0,depth)) depth++;

四、实例代码

Question:AcWing 180.排书
Question Link:acwing.com/problem/content/182
Question Analysis:

连续选择k个书,一共有 n − k + 1 n - k + 1 nk+1 种选法,总共有 n − k n - k nk种放法,那么一共有 ( 15 ∗ 14 + 14 ∗ 13 + . . . + 2 ∗ 1 ) / 2 = 560 (15*14 + 14*13 + ... + 2*1)/2 = 560 (1514+1413+...+21)/2=560 种操作
暴力最坏时间复杂度 O ( 56 0 4 ) O(560^4) O(5604)
每次操作会断开三个连接,然后引入三个连接。即每次操作最多会将三个连接修正
判断 n − 1 n - 1 n1个连接中有多少个错误的连接,记为 t o t tot tot,若全部修正至少需要 [ t o t / 3 ] [tot/3] [tot/3]次操作。(’[ ]'为上取正符号)
那么我们可以设计出估价函数,在当前状态之下,最少进行多少次操作,才可以变成有序序列,即 f ( s t a t e ) = [ t o t / 3 ] f(state)=[tot/3] f(state)=[tot/3]

Diagram:

0.初始
请添加图片描述

1.将 [ L , R ] 抽出来
请添加图片描述
2.把[ R + 1 , K ]补到前面去
请添加图片描述

3.把[ L , R ]插入位置
请添加图片描述

Code:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=15;
int n,q[N];
int w[5][N];//辅助数组(交换用)
int f()
{
    int tot=0;
    for (int i = 0; i+1 < n; i++)
    {
        if(q[i+1]!=q[i]+1) tot++;
    }
    return (tot+2)/3; //即(tot/3)的上取整
}
bool check()
{
    for (int i = 0; i < n; i++)
    {
        if(q[i]!=i+1) return false;//q[0]=1
    }
    return true;
}
bool dfs(int depth,int max_depth)
{
    if(depth + f() > max_depth) return false;
    if(check()) return true;
    //拓展状态,搜索每个分支
    for (int len = 1; len <= n; len++)//枚举长度
    {
        for (int l = 0; l+len-1 < n; l++)//枚举起点
        {
            int r=l+len-1;
            for (int k = r+1; k < n; k++)
            //枚举放置位置(因为放在前面和后面对称等价,故规定放在后面)
            {
                memcpy(w[depth],q,sizeof q);
                int x,y;
                for(x=r+1,y=l;x<=k;x++,y++) q[y]=w[depth][x];//Diagram 2
                for(x=l;x<=r;x++,y++) q[y]=w[depth][x];//Diagram 3
                if(dfs(depth+1,max_depth)) return true;
                memcpy(q,w[depth],sizeof q);
            }
            
        }
        
    }
    return false;
}
int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        cin >> n;
        for (int i = 0; i < n; i++)
        {
            cin >> q[i] ;
        }
        int depth=0;
        while(depth<5&&!dfs(0,depth)) depth++;//迭代加深
        if(depth>=5) puts("5 or more");
        else cout << depth << endl; 
    }
}

代码思路与分析来自于y总视频


Question:AcWing 181.回转游戏
Question Link:acwing.com/problem/content/183
Question Analysis:

估价函数:把中间 8 8 8个数中出现最多次数的出现次数记为 k k k ,每次操作最多会加入一个相同的数,即最多 8 − k 8 - k 8k 次。 f ( s t a t e ) = 8 − k f(state) = 8 - k f(state)=8k;
剪枝:避免枚举和上次相反的操作
最小字典序:按照字典序进行搜索
打表设计:编号,把八种操作对应的这一列数存下
在这里插入图片描述
题目例图
在这里插入图片描述

Code:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=24;
int op[8][7]={
    {0,2,6,11,15,20,22},
    {1,3,8,12,17,21,23},
    {10,9,8,7,6,5,4},
    {19,18,17,16,15,14,13},
    {23,21,17,12,8,3,1},
    {22,20,15,11,6,2,0},
    {13,14,15,16,17,18,19},
    {4,5,6,7,8,9,10},
         };
int opposite[8]={5,4,7,6,1,0,3,2};//记录反操作
int center[8]={6,7,8,11,12,15,16,17};
int q[N];
int path[100];
int f()
{
    static int sum[4];
    memset(sum ,0 ,sizeof sum);
    for (int i = 0; i < 8; i++) sum[q[center[i]]]++;
    int s=0;
    for (int i = 1; i < 4; i++) s=max(s,sum[i]);
    return 8-s;
}
bool check()
{
    for (int i = 1; i < 8; i++)
    {
        if(q[center[i]]!=q[center[0]]) return false;
    }
    return true;
}
void operate(int x)
{
    int t=q[op[x][0]];
    for (int i = 0; i < 6; i++) q[op[x][i]]=q[op[x][i+1]];
    q[op[x][6]]=t;
}
bool dfs(int depth,int max_depth,int last)//last:last op
{
    if(depth+f()>max_depth) return false;
    if(check()) return true;
    //拓展其他分支
    for (int i = 0; i < 8; i++)
    {
        if(opposite[i]==last) continue;//剪枝
        operate(i);
        path[depth]=i;
        if(dfs(depth+1,max_depth,i)) return true;
        operate(opposite[i]);//返回状态
    }
    return false;
}
int main()
{
    while(cin >> q[0],q[0])
    {
        for (int i = 1; i < N; i++) cin >> q[i];
        int depth=0;
        while(!dfs(0,depth,-1)) depth++;
        if(!depth) printf("No moves needed");//一步也不需要做
        else 
        {
            for (int i = 0; i < depth; i++) printf("%c",path[i]+'A');
        }
        cout << endl << q[6] << endl;
    }
    return 0;
}

代码思路与分析来自于y总视频


Question:AcWing 182.破坏正方形
Question Link:acwing.com/problem/content/184
Question Analysis:

对于初始状态:
边长为1的正方形:n^2
边长为2的正方形:(n-1)^2

边长为n的正方形:1^2
一共有 (n*(n+1)*(2n+1))/6 个正方形
一共 2n(n+1) 根火柴
Target:从现有的火柴中,最少选择多少根火柴,可以使得每个正方形中都被至少选择了一根火柴
重复覆盖问题 一般用 Dancing Links 数据结构解决。
图形化
在这里插入图片描述

问题转换:
至少选择多少行,可以使得每一列至少有一个1(选择一根火柴)
搜索顺序:
每次选择一个还没有被覆盖的正方形(选择最小的一个)(优化),枚举选择它上面的哪一根火柴。
估价函数:
枚举每个正方形,如果当前正方形还是完整的,那么删掉它的所有边,但是只记删除一次。(估价函数>=真实值)
正方形边的序号:
在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=61;
vector<int> square[N];//每个正方形所有的边
bool st[N];//每条边是否被用过
int n,m;
bool check(vector<int> &sq)//检测正方形是否是完整的
{
    for (int i = 0; i < sq.size(); i++)
    {
        if(st[sq[i]]) return false;
    }
    return true;
}
int f()
{
    static bool state[N];//全局变量,只会被开辟一次
    memcpy(state,st,sizeof st);//由于要修改st的值,先保存到另一个
    int res=0;
    for (int i = 0; i < m; i++)
    {
        vector<int> &sq=square[i];
        if(check(sq))
        {
            res++;
            for (int j = 0; j < sq.size(); j++) st[sq[j]]=true;
        }
    }
    memcpy(st,state,sizeof st);
    return res;
}
bool dfs(int depth,int max_depth)
{
    if(depth+f()>max_depth) return false;
    for (int i = 0; i < m; i++)
    {
        vector<int> &sq=square[i];
        if(check(sq))
        {
            for (int j = 0; j < sq.size(); j++)
            {
                int x=sq[j];//枚举选择第x个
                st[x]=true;
                if(dfs(depth+1,max_depth)) return true;
                st[x]=false;//状态回溯
            } 
            return false;
        }
    }
    return true;
}
int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        cin >> n ;
        memset(st,0,sizeof st);
        //得所有正方形的所有边的序号
        m=0;
        for (int len = 1; len <= n; len++)//长度
        {
            for (int a = 1; a+len-1 <= n; a++)//枚举起点
            {
                for (int b = 1; b+len-1 <= n; b++)
                {
                    //得到该正方形的所有边的序号
                    square[m].clear();
                    int d=2*n+1;//公差
                    for (int i = 0; i < len; i++)//枚举每条边,得到正方形的所有边的序号
                    {
                        square[m].push_back(1+(a-1)*d+b-1+i);
                        square[m].push_back(1+(a+len-1)*d+b-1+i);
                        square[m].push_back(n+1+(a-1)*d+b-1+i*d);
                        square[m].push_back(n+1+(a-1)*d+b-1+i*d+len);
                    }
                    m++;
                }
            }
        }
        //PreDestroy
        int k=0;
        cin >> k;
        while (k--)
        {
            int x;
            cin >> x;
            st[x]=true;
        }
        int depth=0;
        while(!dfs(0,depth)) depth++;
        cout << depth << endl;
    }
}

代码思路与分析来自于y总视频


Question:AcWing 195.骑士精神
Question Link:acwing.com/problem/content/197
Question Analysis:

估价函数:位置不对的数量

Code:
#include<iostream>
#include<cstring>
using namespace std;
char str[5][5];
char tar[5][5]={'1','1','1','1','1',
                '0','1','1','1','1',
                '0','0','*','1','1',
                '0','0','0','0','1',
                '0','0','0','0','0'};//目标状态
int stari,starj;//初始空白位置坐标
pair<int,int> dire[8];//八种跳法
int f()//估价函数:位置不对的数量(除空白处)
{
    int res=0;
    for (int i = 0; i < 5; i++)
    {
        for (int j = 0; j < 5; j++)
        {
            if(str[i][j]!=tar[i][j]&&str[i][j]!='*') res++;
        }
    }
    return res;
}
bool check()
{
    for (int i = 0; i < 5; i++)
    {
        for (int j = 0; j < 5; j++)
        {
            if(str[i][j]!=tar[i][j]) return false;
        }
    }   
    return true; 
}
bool dfs(int depth,int depth_max,int x,int y)//x,y为当前空白处坐标
{
    if(depth+f()>depth_max) return false;
    if(check()) return true;
    for (int i = 0; i < 8; i++)
    {
        int tx=x+dire[i].first,ty=y+dire[i].second;
        if(tx>=5||tx<0||ty>=5||ty<0) continue;//剪枝
        swap(str[x][y],str[tx][ty]);
        if(dfs(depth+1,depth_max,tx,ty)) return true;
        swap(str[x][y],str[tx][ty]);//状态回溯
    }
    return false;
}                
int main()
{
    //跳法赋值
    dire[0].first=1;dire[0].second=2;
    dire[1].first=1;dire[1].second=-2;
    dire[2].first=-1;dire[2].second=2;
    dire[3].first=-1;dire[3].second=-2;
    dire[4].first=2;dire[4].second=1;
    dire[5].first=2;dire[5].second=-1;
    dire[6].first=-2;dire[6].second=1;
    dire[7].first=-2;dire[7].second=-1;
    int T;
    cin >> T;
    while (T--)
    {
        for (int i = 0; i < 5; i++)
        {
            for (int j = 0; j < 5; j++)
            {
                cin >> str[i][j];
                if(str[i][j]=='*') 
                {
                    stari=i;
                    starj=j;
                }
            }
        }
        int depth=0;
        while(depth<=15&&!dfs(0,depth,stari,starj)) depth++;
        if(depth>15) cout << -1 << endl;
        else cout << depth << endl;
    }
}

如有疑问欢迎在评论区留言或者通过Email联系我

My Email:Wizzy-Ang@qq.com

欢迎大家关注我的个人公众号WizzyAngShare,(还有个人博客)

我会在这里分享编程语言语法,算法,及区块链的相关知识,还有各种奇奇怪怪的小知识等着你~

QRCode
虽然现在这个公众号有亿点草率 ,我会努力更新的~~~

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
A*算法IDA*算法都是启发式搜索算法,它们都是用于解决最短路径问题的常用算法。其中,A*算法在搜索时利用了启发式函数来估计从当前节点到目标节点的距离,从而选择最优的节点进行搜索。而IDA*算法则是在A*算法基础上做出的改进,其主要区别是不使用任何额外的内存空间,而是将估价函数的值作为深度限制,直到找到目标节点或者超过深度限制为止。 具体来说,A*算法维护一个open列表和一个closed列表。open列表中保存了待搜索的节点,closed列表中保存已经被搜索过的节点。每次从open列表中选择一个f值最小的节点进行扩展,并将其加入closed列表中。扩展时,对于当前节点的每个子节点计算估价函数值f(n)=g(n)+h(n),其中g(n)是从起点到当前节点的实际代价,h(n)是从当前节点到目标节点的估计代价。这样,每次选择f值最小的节点进行扩展时,就能够尽可能地朝着目标节点前进。 IDA*算法则是将A*算法中的open列表替换成一个栈,在搜索时将当前搜索深度作为阈值,不断更新阈值来限制搜索深度。具体来说,每次从起点开始,将阈值设置为起点到目标节点的估价函数值,然后进行深度优先搜索。如果搜索到某个节点的f(n)>threshold,则将其返回,并将阈值更新为f(n)。这样,IDA*算法通过不断增加深度限制来逐步逼近最短路径,直到找到目标节点或者搜索到达最大深度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值