回溯算法详解

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

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

二,回溯算法的定义

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

三,思考问题方向:

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、付费专栏及课程。

余额充值