【小白学算法】IDA*搜索算法超详细解析+例题[洛谷]P2324 [SCOI2005] 骑士精神

引入:之前介绍了A-star算法,可以解决八数码问题,但是在练题的时候发现,P2324 [SCOI2005] 骑士精神这个题思路几乎与八数码相同,但是由于三宫格变成五宫格,用bfs的话空间复杂度非常高,且15深度下就有\(8^{15}\)种状态,用A-star复杂度高且需要存储所有开放节点,所以IDA*算法应运而生啦,下面为大家详细介绍IDA-star算法~~

介绍

IDA-star(迭代加深A-star)是一种基于启发式搜索的算法,结合了A-star算法和迭代加深深度优先搜索的优点,通过多次执行有深度限制的深度优先搜索来寻找目标,每次搜索的深度限制都会增加,直到找到目标为止,与传统的迭代加深不同的是,IDA-star在每次搜索中都使用启发式函数\(h(n)\)来剪枝,之探索“有潜力”找到目标的路径。

核心思想

IDA-star通过迭代加深的方式控制搜索深度,结合A-star算法的启发式函数来指导搜索方向,避免盲目扩展节点,从而在保证最优解的同时减少内存占用。其核心特点是:

  • 启发式函数:使用启发式函数 \(h(n)\) 估计从当前节点到目标节点的代价,结合已知代价 g(n),计算总代价 \(f(n)=g(n)+h(n)\)

  • 迭代加深:设置一个代价阈值(threshold),只搜索\(f(n)\) 不超过阈值的节点。如果未找到目标,增加阈值并重新搜索。

  • 深度优先搜索:在每次迭代中,IDA*使用深度优先搜索(DFS)来探索节点,减少内存占用。

算法步骤

1、初始化

  • 设置初始节点和目标节点。

  • 计算初始节点的 \(f(n)=g(n)+h(n)\),其中 \(g(n)\) 是从起点到当前节点的实际代价,\(h(n)\) 是启发式估计代价。

  • 设置初始阈值(通常为初始节点的 \(f(n)\))。

2、深度优先搜索

  • 使用DFS探索节点,但只扩展 f(n)≤ 当前阈值的节点。

  • 如果找到目标节点,返回最优路径。

  • 如果搜索受限(即 \(f(n)\) 超过阈值),记录超出阈值的最小 \(f(n)\) 值作为下一次迭代的候选阈值。

3、更新阈值

  • 如果未找到目标,更新阈值为上一步记录的最小超出值。

  • 清空搜索栈,重新开始新一轮DFS。

4、重复

  • 重复步骤2和3,直到找到目标节点或确认无解。

例题_【洛谷】P2324 [SCOI2005] 骑士精神

题目分析

在 5×5 的棋盘上,有 12 个白色骑士(0)和 12 个黑色骑士(1),以及一个空位()。骑士可以按照国际象棋规则移动("日"字型移动),目标是通过最少的移动步数将棋盘变为特定目标状态,当步数超过15步时判定为-1无解。

解题思路

典型的A-star问题,但是A-star又不能完全解决问题,因为题目要求了15步内完成,所以本题使用IDA-star算法来解决。首先设置迭代加深,最大深度设置为15步,然后传入dfs中进行搜索,枚举8哥方向,生成新状态,利用A-star启发式搜索来剪枝,即设置启发式函数\(h(n)\),判断剩余步骤内是否可能找到解,以此剪枝即可。

完整代码示例

#include<iostream>
#include<algorithm>
using namespace std;

struct matrix{
  int a[7][7];
}start_state, goal_state;

const int dx[8] = { 1, 1, -1, -1, 2, 2, -2, -2};
const int dy[8] = {2, -2, 2, -2, 1, -1, 1, -1};

int stx, sty;
int success;

void init_goal() {

  goal_state.a[1][1] = 1;
  goal_state.a[1][2] = 1;
  goal_state.a[1][3] = 1;
  goal_state.a[1][4] = 1;
  goal_state.a[1][5] = 1;

  goal_state.a[2][1] = 0;
  goal_state.a[2][2] = 1;
  goal_state.a[2][3] = 1;
  goal_state.a[2][4] = 1;
  goal_state.a[2][5] = 1;

  goal_state.a[3][1] = 0;
  goal_state.a[3][2] = 0;
  goal_state.a[3][3] = 2;
  goal_state.a[3][4] = 1;
  goal_state.a[3][5] = 1;

  goal_state.a[4][1] = 0;
  goal_state.a[4][2] = 0;
  goal_state.a[4][3] = 0;
  goal_state.a[4][4] = 0;
  goal_state.a[4][5] = 1;

  goal_state.a[5][1] = 0;
  goal_state.a[5][2] = 0;
  goal_state.a[5][3] = 0;
  goal_state.a[5][4] = 0;
  goal_state.a[5][5] = 0;
}

int h(const matrix &mat) {
  int cnt = 0;
  for(int i = 1; i <= 5; i++) {
      for(int j = 1; j <= 5; j++) {
          if(mat.a[i][j] != goal_state.a[i][j]) {
              cnt++;//启发函数,用于判断当前状态中与目标函数位置不同的个数
          }
      }
  }
  return cnt;
}

void dfs(int dep, int x, int y, int maxdep, matrix &mat) {

  if(dep == maxdep) {
      if(!h(mat)) {
          success = 1;
      }
      return;
  }//判断当前深度为迭代深度时,若达到目标状态,则标记成功并返回

  for(int i = 0; i < 8; i++) {//遍历8个方向
      int xx = x + dx[i];
      int yy = y + dy[i];

      if(x >= 1 && x <= 5 && y >= 1 && y <= 5){//判断下标合法
          swap(mat.a[x][y], mat.a[xx][yy]);// 交换位置

          //剪枝
          if(h(mat)+dep<=maxdep) {//启发函数小于最大深度,则说明可能有解 所以继续搜
              dfs(dep+1,xx,yy,maxdep,mat);
          }//由于我们在定启发函数时 预测步数一定≤实际所需步数,所以当出现预测步数已经下已经不合理了,那么实际步数一定不合理,所以无需再搜索。
          //
          swap(mat.a[x][y],mat.a[xx][yy]);//将位置复原

          if(success) {
              return;
          }//若以找到解 则直接返回,无需遍历下一个方向
      }
  }
}

int main() {
  init_goal();//初始化目标矩阵
  int T;
  cin >> T;

  while(T--) {
      success = 0;//结果初始化

      for(int i = 1; i <= 5; i++) {
          for(int j = 1; j <= 5; j++) {
              char ch;
              cin >> ch;//输入初始状态
              if(ch == '*') {
                  start_state.a[i][j] = 2;//空格位置的‘*’用2代替,将数组定义为int类型,便于后续使用
                  stx = i;
                  sty = j;
              } else {
                  start_state.a[i][j] = ch - '0';
              }
          }
      }

      if(!h(start_state)) {
          cout << "0" << endl;
          continue;
      }//看初始状态与目标状态是否相同,是则无需继续 直接输出
      int flag=0;//标记是否找到解

      //迭代加深搜索,共15步
      for(int maxdep = 1; maxdep <= 15; maxdep++) {
          matrix temp = start_state;//将当前状态传入算法
          dfs(0, stx, sty, maxdep, temp);
          //四个参数分别为 深度,当前状态中空格的横坐标,纵坐标,最大深度(用于判断是否超过15步),以及当前状态
          if(success) {
              cout << maxdep << endl;
              flag=1;
              break;//若有解,则输出当前深度,即达到目标状态的所需步数,比标记flag为1
          }
      }
      if(flag==0)//若flag最终依然为0,则说明没有找到解,输出-1;
          cout << "-1" << endl;
  }
  return 0;
}
posted @ 2025-08-12 14:48  芝士青瓜不拿铁  阅读(45)  评论(0)    收藏  举报