回溯算法(附练习题)

338 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第26天,点击查看活动详情

什么是回溯算法?

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

递归伴随着回溯

在递归的过程中,我们会经历回溯的过程
回溯的模板是这样的: image.png

回溯法解决的问题都可以抽象为树形结构。

因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。

image.png

讲讲真题:

7-6 全排列(回溯,字典序)(30 分)

题目描述:

对于1~n这n个不同的数,按照一定的顺序把这n个数排列起来(每个数出现一次,且不重复, n<10),将所有的排列列出,称为全排列。

输入格式:

一个数n。

输出格式:

1~n的全排列,每个排列一行(按字典序输出)。

输入样例:

3

输出样例:

1 2 3 
1 3 2 
2 1 3 
2 3 1 
3 1 2 
3 2 1 

代码长度限制   16 KB

时间限制     400 ms

内存限制     64 MB

思路分析:

跟着回溯算法的思路,我们写一个backstaring的回溯函数(核心)

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define FAST_IO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)

vector<vector<int>> result;  // 全部结果集合
vector<int> path;  // 递归树上一条路径的结果
vector<int> nums;  // 存储[1,n]
int n;

void backstaring(const vector<int>& nums, vector<bool>& used) {
  // 终止条件&收集结果
    if(path.size() == nums.size()) {
        result.push_back(path);
        return ;
    }
    for(int i = 0; i < nums.size(); i++) {
      // nums[i]已经使用过了 就跳过本轮循环
        if(used[i] == true) continue;
        // 没使用过 做出选择先标记为使用过
        used[i] = true;
        path.push_back(nums[i]);
        backstaring(nums, used);
        // 撤销选择
        path.pop_back();
        used[i] = false;
    }
}

int main() {
    FAST_IO;
    cin >> n;
    nums.resize(n);
    // 初始化nums数组
    for(int i = 1; i <= n; i++) nums[i-1] = i;
    // used数组用来判断某元素是否已经被使用过了
    vector<bool> used(nums.size(), false);
    backstaring(nums, used);
    for(int i = 0; i < result.size(); i++) {
        for(int j = 0; j < result[i].size(); j++) {
            cout << result[i][j] << " ";
        }
        cout << "\n";
    }
    return 0;
}

PS:成功解题=理清思路+一定的技巧~