一般搜索算法的流程框架
DFS和BFS与一般搜索流程的关系
如果一般搜索算法流程4使用的是stack栈结构(先进后出,后进先出)那么就会越搜越深。即,DFS,DFS只保存当前一条路径,其目的是枚举出所有可能性。反之,如果流程4使用的是queue队列结构(先进先出)那么就会先处理相邻的,然后再进入下一层,即,BFS
递归DFS的流程框架
用栈的DFS和用递归的DFS区别
思路
DFS + 回溯
code
#include<iostream>
using namespace std;
const int N = 10;
int n = 3;
//最终输出
int path[N];
//记录当前使用过的数
int st[N];
void dfs(int u)
{
//达到阈值
if (u == n)
{
for (int i = 0; i < n; i++)printf("%d ", path[i]);
puts("");
return;
}
for (int i = 1; i <= n; i++)
{
if (!st[i])
{
//存入当前内容
path[u] = i;
//标记当前数已使用
st[i] = true;
//进入下一层
dfs(u + 1);
//dfs结束恢复现场
path[u] = 0;
st[i] = false;
}
}
}
int main()
{
dfs(0);
return 0;
}
DFS代码深层分析
1.首先需要两个全局定义,一个用来存储已经使用过的内容st,一个用来存储需要的内容path
2.首选处理第一个需要处理的位置位置u == 0,进入dfs中首先判断是否达到既定目标,u == n,如果没有达到,说明还没有走到尽头,继续执行
3.在进入for循环中后,首先针对u == 0需求位置,寻找合适的内容填入,for第一层循环i = =1开始进行判断,判断i == 1是否使用过,如果没有使用过,就把它加入到需求记录path[u]中,并且标记为i == 1已经使用过,本次需求位置u == 0的位置结束,进入下一个需求位置 u + 1(u == 1)
4.进入新的需求的位置后,依然判断是否达成条件,没达成继续进入for循环,此时全局记录的位置i = =1被使用过,所以来到i = = 2处使用,当本次需求位置处理完毕后 u == 1,继续进入下一个需要处理的位置 u + 1 (u == 2)
5.当i == 3时候所有需求处理完毕,在进入循环(第四次处理的时候)触发了满足条件,直接输出了完整的path[u]内容,然后回到了上次处理,因为for中i = 3了,所以此次处理结束,回溯到上一次,并且重置条件,即 i = 2相关全部清理,然后继续i++ 为3,此时的需求依然是u + 1 = 2的情况,只不过是for为3把i = 3的情况加入到了需求位置,然后进入了下一层u + 1的循环,这样做的目的是for宗到i++保证了是一直向前的状态,再次进入到u + 1中处理的就是最后一个需求位置了,所以当查询到i = 2的时候就执行填入了,满足了所有需求输出。以此类推,输出全部可能性。
class Solution {
public:
vector<vector<int>> ans;
vector<bool> st;
vector<int> path;
vector<vector<int>> permute(vector<int>& nums) {
for (int i = 0; i < nums.size(); i ++ ) st.push_back(false);
dfs(nums, 0);
return ans;
}
//nums为当前处理序列,u为当前要处理的位置
void dfs(vector<int> &nums, int u)
{
if (u == nums.size())
{
ans.push_back(path);
return ;
}
for (int i = 0; i < nums.size(); i ++ )
if (!st[i])
{
st[i] = true;
path.push_back(nums[i]);
dfs(nums, u + 1);
st[i] = false;
path.pop_back();
}
}
};
N皇后问题
#include <iostream>
using namespace std;
const int N = 20;
int n;
char g[N][N];
bool col[N], dg[N], udg[N];
void dfs(int u)
{
//u代表当前处理的行
if (u == n)
{
for (int i = 0; i < n; i ++ ) puts(g[i]);
puts("");
return;
}
//对每一行的每个列位置进行枚举处理
for (int i = 0; i < n; i ++ )
if (!col[i] && !dg[u + i] && !udg[n - u + i])
{
g[u][i] = 'Q';
col[i] = dg[u + i] = udg[n - u + i] = true;
dfs(u + 1);
col[i] = dg[u + i] = udg[n - u + i] = false;
g[u][i] = '.';
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
g[i][j] = '.';
dfs(0);
return 0;
}
思路
递归求解:
当前树的最大深度等于左右子树的最大深度加1。
时间复杂度分析:树中每个节点只被遍历一次,所以时间复杂度是 O(n)。
code
class Solution {
public:
int maxDepth(TreeNode* root) {
if(!root)return 0;
return max(maxDepth(root->left),maxDepth(root->right)) + 1;
}
};
思路
深度只对叶子节点有效,对内部节点无效。
对于每个节点:
如果树根为空,则返回0。
如果没有子节点,说明是叶节点,则返回1;
如果有子节点,说明是内部结点,则返回子节点的深度的最小值 + 1(加上根节点这层);
时间复杂度分析:每个节点仅被遍历一次,且遍历时所有操作的复杂度是 O(1),所以总时间复杂度是 O(n)。
以本题20为计算节点高度,其左子树高度为1,右子树高度为1,取min 然后加上20这一层,20的高度为2。递推到3的时候 9的高度为1,20的高度为2,min为1,加上1为2。为最终答案
code
class Solution {
public:
int minDepth(TreeNode* root) {
if (!root) return 0;
int res = INT_MAX;
if (root->left) res = min(res, minDepth(root->left) + 1);
if (root->right) res = min(res, minDepth(root->right) + 1);
if (res == INT_MAX) res = 1;
return res;
}
};
思路
code
//非递归方式
class Solution {
public:
string chars[8] = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
vector<string> letterCombinations(string digits) {
//判空
if(digits.empty())return vector<string>();
vector<string>state(1,"");
for(auto u:digits)
{
vector<string> now;
//char[u - '2'] 用来确定当前号码是哪个区间取值
for(auto c:chars[u - '2'])
for(auto s:state)
//(s + r)采用字符追加方式
now.push_back(s + c);
//更新外部存储
state = now;
}
return state;
}
};
//递归方式
class Solution {
public:
vector<string> ans;
string strs[10] = {
"", "", "abc", "def",
"ghi", "jkl", "mno",
"pqrs", "tuv", "wxyz",
};
vector<string> letterCombinations(string digits) {
if (digits.empty()) return ans;
dfs(digits, 0, "");
return ans;
}
void dfs(string& digits, int u, string path) {
if (u == digits.size()) ans.push_back(path);
else {
for (auto c : strs[digits[u] - '0'])
dfs(digits, u + 1, path + c);
}
}
};
思路:
因为中序遍历采用【左根右】的形式,从根结点向子树深处搜索,所以使用DFS进行深度优先遍历。
code
class Solution {
public:
vector<int>res;
vector<int> inorderTraversal(TreeNode* root) {
dfs(root);
return res;
}
void dfs(TreeNode* root)
{
if(!root)return;
dfs(root->left);
res.push_back(root->val);
dfs(root->right);
}
};
思路:Flood Fill算法
class Solution {
public:
vector<vector<char>> g;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int numIslands(vector<vector<char>>& grid) {
g = grid;
int cnt = 0;
for (int i = 0; i < g.size(); i ++ )
for (int j = 0; j < g[i].size(); j ++ )
if (g[i][j] == '1') {
dfs(i, j);
cnt ++ ;
}
return cnt;
}
void dfs(int x, int y) {
//染色
g[x][y] = 0;
for (int i = 0; i < 4; i ++ ) {
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < g.size() && b >= 0 && b < g[a].size() && g[a][b] == '1')
dfs(a, b);
}
}
};