回溯算法(全排列、组合、N皇后问题)
回溯算法(全排列、N皇后问题)
暴力穷举,这是不可避免,因为回溯法没有重复子结构,所以其时间复杂度大于等于O(N!);
前言
本文章内容部分参考公众号labuladong关于回溯算法的讲解,仅为笔者日后复习,或读者参考学习,无商业用途。
实质:决策树的遍历
三要素
- 路径:当前已经做出的决策;
- 选择列表:当前可以做出的决策;
- 结束条件:到达决策树底层,一般就是选择列表为空;
回溯递归框架
result = []
def backtrack(路径, 选择列表):if 满⾜结束条件:result.add(路径)returnfor 选择 in 选择列表:做选择backtrack(路径, 选择列表)撤销选择
核心:for循环中的递归,递归前做出决策,递归后撤销决策
经典例题
全排列
问题:n个非重复数字的全排列?
代码实现
回溯框架
/*
full permutation
track: track path
nums: selection list
*/
void permute(vector<int> track, vector<int> &nums){/* end condition */if(track.size() == nums.size()){result.push_back(track);/* result is the global vector */ }/* backtrack *//* layer one have nums.size() paths */for(auto n :nums){/* make sure the chioce valid */if(find(track, n)){continue;}/* make choice */track.push_back(n);/* to down recursion */permute(track, nums);/* undo choice */track.pop_back();}
}
判断元素是否在选择列表中;
这样判断时间复杂度是O(n),当然我们可以通过set的方式记录,用空间换时间,实际上,我们还可以使用交换的方式;
/*
make sure the chioce valid
*/
bool find(vector<int> track, int n){for(auto t : track){if(t == n){return true;}}return false;
}
打印
/*
vector二维数组打印
*/
void printVec(vector<vector<int>> &obj){for(auto item : obj){for(auto it : item){cout << " " << it;}cout << endl;}
}
主函数
vector<vector<int>> result;
int main(){vector<int> nums;nums.assign({2, 3, 5, 4});permute(vector<int>(), nums);printVec(result);getchar();return 0;
}
全排列经典算法(交换写法)
思路
对{2, 3, 4}进行全排列:
- 保持2不变,对3、4全排列;
- 保持3不变,对4进行全排列;
- 得出2, 3, 4;
- 交换3、4;
- 保持4不变,对3进行全排列;
总结就是:
每个数依次打头,求剩下的n-1个数的全排列,而n-1个数的全排列由n-2个数推出…直到一个数的全排列,开始回溯;
/*
nums:个位数的数组(假设从 1-9),eg:{2, 3, 4};
obj: 记录全排列结果;
pinNum:pinNum+1表示当前固定的数字个数,从1开始;
len:总个位数,res的长度;
*/
void permutation(vector<int> &nums, vector<vector<int>> &res, int pinNum, int len){/* 判断是否到达最后一个数 */if(pinNum+1 == len){/* 将当前排列记录到obj */res.push_back(nums);return;}/* pinNum's full permutation */for(int i=pinNum; i<len; i++){swap(nums[i], nums[pinNum]);permutation(res, obj, pinNum+1, len);swap(nums[i], nums[pinNum]);}
}
组合
代码实现:
class Solution {vector<vector<int>> res;
public:void combine(vector<int> track, int pinNum, int n, int k){/* end condition */if(track.size() == k){res.push_back(track);return ;}/* begin num */for(int i = pinNum; i <= n; i++){track.push_back(i);combine(track, i+1, n, k);/* i+1而不是pinNum+1,因为自身不能选两次 */track.pop_back();}}vector<vector<int>> combine(int n, int k) {combine(vector<int>(), 1, n, k);return res;}
};
N皇后问题
大体意思:
思路
- 套用框架,明确三要素路径、选择列表和结束条件;
- 当Queen成功摆放完第N行结束递归;
代码实现
声明全局数组为结果集
vector<vector<string>> res;
主调函数:
vector<vector<string>> solveNQueen(int n){/* 初始化路径track为'.',可选列表从track[0]开始 */backtrack(vector<string>(n, '.'), 0);return res;
}
套用框架:
路径:当前摆放Queen行之前成功摆放Queen的位置;
选择列表:当前行的所有位置都是选项,不过需要判断是否合法;
结束条件:Queen已经成功摆放完第N行;
void backtrack(vector<string> &track, int row){int n = track.size();/* end condition */if(row == n){res.push_back(track);return;}for(int col = 0; col < n; col++){/* 检查当前位置是否合法 */if(!isValid(track, row, col){continue;}/* make choice */track[row][col] = 'Q';/* recursion */backtrack(track, row+1);/* undo choice */track[row][col] = '.';}
}
检查暴力当前位置是否合法
bool isValid(vector<string> track, int x, int y) {/* 当前列 */for (int i = 0; i < x; i++) {if (track[i][y] == 'Q') {return false;}}/* 左上 */for (int i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--) {if (track[i][j] == 'Q') {return false;}}/* 右上 */for (int i = x - 1, j = y + 1; i >= 0 && j < track[i].size(); i--, j++) {if (track[i][j] == 'Q') {return false;}}return true;
}
哈希表检查是否合法,需要声明一个全局哈希表memo,并在每次做出选择时memo[row] = col,每次撤销选择,删除该键值对;
bool isValid(int row, int col) {for (auto p : memo) {if (p.second == col || abs(p.first - row) == abs(p.second - col)) {return false;}}return true;
}
总结
回溯算法的实质还是多叉树的遍历问题,深度优先遍历,清楚遍历的操作,框架如下:
def backtrack(...)for 选择 in 选择列表make choicebacktrack(...)undo choice
注意
维护好走过的 [路径] 和当前可选的 [选择列表],并写出合适判断选择合法性的函数,最后碰到终止条件,将本次遍历结果存入结果集;
2020/08/06 00:45
@luxurylu
回溯算法(全排列、组合、N皇后问题)相关推荐
- python 动态规划 回溯_回溯算法 - 全排列算法实现(pythondart)
回溯算法 , 就是 穷举 解决一个回溯问题,实际上就是一个决策树的遍历过程. 路径: 也就是已经做出的选择 选择列表: 也就是你当前可以做的选择 结束条件: 也就是到达决策树底层,无法再做选择的条件. ...
- python回溯算法全排列_python 回溯法 子集树模板 系列 —— 11、全排列
问题 实现 'a', 'b', 'c', 'd' 四个元素的全排列. 分析 这个问题可以直接套用排列树模板. 不过本文使用子集树模板.分析如下: 一个解x就是n个元素的一种排列,显然,解x的长度是固定 ...
- 【Leetcode 剑指offer刷题】回溯算法-排列组合77216
目录 leetcode.77.组合 leetcode.216.组合总和 III 两题差异 77题代码 216题代码 参考 leetcode.77.组合 题目链接 给定两个整数 n 和 k,返回 1 - ...
- 回溯算法-排列/组合/子集
文章目录 子集 https://mp.weixin.qq.com/s?subscene=19&__biz=MzAxODQxMDM0Mw==&mid=2247485007&idx ...
- 本题要求实现一个求整数的逆序数的简单函数。_回溯算法:求组合总和(二)...
给「代码随想录」一个星标吧! ❝ 我将公众号文章和学习相关的资料整理到了Github :https://github.com/youngyangyang04/leetcode-master,方便大家在 ...
- 算法实验二 【八皇后问题】(回溯算法)
算法实验二 [八皇后问题](回溯算法) 1007.8皇后问题 时限:1000ms 内存限制:10000K 总时限:3000ms 描述 输出8皇后问题所有结果. 输入 没有输入. 输出 每个结果第一行是 ...
- python 回溯算法总结
python 回溯算法 回溯算法理论基础 组合 组合总数III 电话号码的字母组合 组合总和 组合总和ii 分割回文串 复原IP地址 子集问题 子集问题II 递增序列 全排列 全排列II 重新安排行程 ...
- 「leetcode」最强回溯算法总结篇!历时21天、画了20张树形结构图、14道精选回溯题目精讲
本文 https://github.com/youngyangyang04/leetcode-master 已经收录,里面还有leetcode刷题攻略.各个类型经典题目刷题顺序.思维导图,可以fork ...
- Nuist集训队作业:深度优先搜索(回溯算法)
Nuist集训队第一次作业:深度优先搜索(回溯算法) 引例 深搜基本思想及回溯算法模板 P1706 全排列问题 P1219 八皇后 P1605 迷宫 P1101 单词方阵 小结 引例 国际西洋棋棋手马 ...
- 数字拆分问题算法回溯_回溯算法:求子集问题!
给「代码随想录」一个星标吧! ❝ 认识本质之后,这就是一道模板题 通知:我将公众号文章和学习相关的资料整理到了Github :https://github.com/youngyangyang04/le ...
最新文章
- 面向对象基础知识01
- SAP CRM WebClient UI和Hybris CommerceUI tag的渲染逻辑
- mysql server 5.0安装教程_MySQL Server 5.0安装教程
- python的zip方法_python zip()函数使用方法解析
- 详细地图_一目了然:蒙城学区划分详细地图
- mysql 冗余字段_18.3.3 增加冗余字段
- 开源开放 | OpenKG 更新发布新冠概念、防控和流行病等多个知识图谱
- 光伏并网逆变器意大利CEI 0-21标准
- 通道抠图蒙版置入背景(抠图换背景)
- Access denied for user ‘root‘@‘localhost‘
- 根据行数要求实现展开与隐藏TextView
- java里SQL insert操作的语法_Java含个人总结语法:JDBC,学生表,实体类,集合,增删改查,注入,预处理【诗书画唱】...
- 微信第三方平台【六】微信开放平台帐号管理
- 【halcon】灰度直方图直观理解与应用
- 【LeetCode】345. Reverse Vowels of a String 解题报告
- win10重置进度条不动了_win10重置卡在100%不动没反应怎么办
- 数据库原理及MySQL应用 | 数据库安全加固
- 微型计算机的特点及其主板构成,第1章 计算机基础知识教案
- U盘加密软件怎么选?
- obsidian之快速拆分文件的插件note-refactor操作手册