回溯算法详解

一,回溯算法与递归方法的对比

递归:关注代码实现 ---- >  回溯:关注问题解决
          方法               ---- >            算法

二,回溯算法的定义

回溯算法 == 问题状态求解树 + 深搜 + 剪枝优化

三,思考问题方向:

1.得到问题状态搜索树
2.切勿妄想一步到位:先实现,再优化
3.搜索剪枝优化:没有固定方法,具体问题,具体分析

典例:八皇后luogup1219

优化方式1:状态压缩
有一表示状态的01数组,那么就可以将数组压缩成一个二进制数
优化方式2:快速枚举
当使用一个二进制数表示01数组,我们可以通过位运算直接取得二进制数中的最末尾的1,避免了无效位的访问。
如何取得二进制数t的最末尾1:最末尾1 = t & (-t);  
如何构建循环,不断去取二进制t中的1:t -= t & (-t);
优化方式3:斜边表示
1.正斜线num:num = i + j - 1;
2.反斜线num: num = i - j + n

典例:奇怪的电梯luoguP1135(bfs better)

当在阴影部分出现绿色节点表示的状态时,停止递归。历史答案可以用数组等结构储存;
典例:P1036选数
组合型选择方式,只关注选择的组合的内容;
典例:P1443马的遍历(bfs)
优化1: 方向数组,记录偏移量
优化2: bfs每一步得到的都是最小步数
#define MAX_N 400
//方向数组
int dir[8][2] = {
    {2, 1}, {-2, 1}, {2, -1}, {-2, -1},
    {1, 2}, {1, -2}, {-1, 2}, {-1, -2}
};
int dis[MAX_N + 5][MAX_N + 5];
struct Node {
    Node(int x, int y, int s) : x(x), y(y), s(s) {}
    int x, y, s;
};
void bfs(int n, int m, int a, int b) {
    queue<Node> q;
    q.push(Node(a, b, 0));
    dis[a][b] = 0;
    while (!q.empty()) {
        Node now = q.front();
        q.pop();
          //方向数组使代码更简洁
        for (int k = 0; k < 8; k++) {
            int dx = now.x + dir[k][0];
            int dy = now.y + dir[k][1];
            if (dx < 1 || dx > n) continue;
            if (dy < 1 || dy > m) continue;
            if (dis[dx][dy] != -1) continue;
            q.push(Node(dx, dy, now.s + 1));
            dis[dx][dy] = now.s + 1;
        }
    }
    return ;
}
int main() {
    int n, m, a, b;
    scanf("%d%d%d%d", &n, &m, &a, &b);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            dis[i][j] = -1;
        }
    }
    bfs(n, m, a, b);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (j > 1) printf(" ");
            printf("%d", dis[i][j]);
        }
        printf("\n");
    }
    return 0;
}

典例:迷宫P1605

典例:吃奶酪P1433

t表示剩余的可选状态(1,3,5), 6则是当前路径的最后一个点,用dp二维数组记录最小历史记录,二维数组的两个引索共同标记着一类历史状态。

典例:单词接龙P1019

//判断s1后缀是否等于s2前缀
int f(string& s1, string s2) {
    for (int i = s1.size() - 1; i >= 1; i--) {
        int flag = 1;
        for (int j = 0; i + j < s1.size(); j++) {
            //s2[s2.size()] == '\0',当j == s2.size(),循环一定会break,不会产生非法访问
            if (s1[i + j] == s2[j])continue;
            flag = 0;
            break;
        }
        if (flag == 1)return s1.size() - i;
    }
    return 0;
}

典例:P1032 字串变换

一、迭代加深搜索
1.迭代加深搜索 简介
迭代加深是一种每次限制搜索深度的深度优先搜索(DFS)。
它的本质还是深度优先搜索,只不过在搜索的同时带上了一个深度 d,当d达到设定的深度时就返回,一般用于找最优解。如果一次搜索没有找到合法的解,就让设定的深度加一,重新从根开始。
这里不妨回想一下BFS搜索算法:这里我们要达到的目的都是寻求最优解,那么迭代加深搜索与BFS分别应用于什么样的场景呢?
我们知道 BFS 的基础是一个队列,队列的空间复杂度很大,当状态比较多或者单个状态比较大时,使用队列的 BFS 就显出了劣势。事实上,迭代加深就类似于用 DFS 方式实现的 BFS,它的空间复杂度相对较小。
搜索树的分支比较多时,每增加一层的搜索复杂度会出现指数级爆炸式增长,这时前面重复进行的部分所带来的复杂度几乎可以忽略,这也就是为什么迭代加深是可以近似看成BFS的

适用场景

在大多数的题目中,广度优先搜索还是比较方便的,而且容易判重。当发现广度优先搜索在空间上不够优秀,而且要找最优解的问题时,就应该考虑迭代加深搜索。

3.优点/缺点

1).优点

  1. 空间开销小,每个深度下实际上是一个深度优先搜索,不过深度有限制,而 DFS 的空间消耗小是众所周知的;
  2. 利于深度剪枝。

2).缺点

重复搜索:回溯过程中每次 depth 变大都要再次从头搜索(其实,前一次搜索跟后一次相差是微不足道的)。
#include<iostream>
#include<string>
#include<vector>
#include<unordered_map>
using namespace std;
int ans = 100;
unordered_map<string, int> dp;
void dfs(string a, string &b, int k, int& n, vector<string>& from, vector<string>& to) {
       //得到最优解,不再递归
       if (ans != 100)return;
       dp[a] = k;
       if (a == b) {
              ans = k;
              return;
       }
       //到达递归最深处,直接返回
       if (k >= n)return;
       for (int i = 0, I = from.size(); i < I; i++) {
              int pos = a.find(from[i], 0);
              while (pos != -1) {
                      string temp = a;
                      temp.erase(pos, from[i].size());
                      temp.insert(pos, to[i]);
                      //历史优化剪枝
                      if (dp.find(temp) == dp.end() || dp[temp] >= k + 1) {
                             dfs(temp, b, k + 1, n, from, to);
                      }
                      pos = a.find(from[i], pos + 1);
              }
       }
       return;
}
int main() {
       ios::sync_with_stdio(false);
       cin.tie(NULL);
       string a, b;
       cin >> a >> b;
       vector<string> from, to;
       string temp_from, temp_to;
       while (cin >> temp_from >> temp_to) {
              from.push_back(temp_from);
              to.push_back(temp_to);
       }
       //迭代加深
       for (int i = 1; i <= 10; i++) {
              dfs(a, b, 0, i, from, to);
              if (ans != 100) break;
              
       }
       if (ans <= 10)cout << ans;
       else cout << "NO ANSWER!";
       return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值