AcWing 842. 排列数字(dfs分析本质)

dfs和bfs都可以对整个空间进行搜索,搜索结构如同一棵树。

但是两者搜索顺序不一样

dfs是尽可能往深处搜,当搜索到叶子结点就会回溯,之后搜下一个分支搜到尽头后又进行回溯循环往复,最终回归起点

在这里插入图片描述

“dfs是个执着的人,不论往哪条分支走,一定会走到尽头,不走到尽头是不会回来的,一旦走到尽头,回去的时候是边回溯边判断是否还有下一条分支能够继续往前走,只有确定当前点所有的路都走不通的时候才会回退一步。”

从使用的数据结构来看,dfs使用的是 ,从使用的空间来看,dfs往下搜索的时候,我们只需记录某一条路径上的所有点即可,因此它的空间是和搜索树的高度成正比的。

例题:
在这里插入图片描述
在这里插入图片描述
如何用 dfs 解决全排列问题?

dfs 最重要的是搜索顺序。用什么顺序遍历所有方案。

对于全排列问题,以 n = 3 为例,可以这样进行搜索:
55289_0cd4222d73-深度优先遍历.png

QQ截图20220302101008.png

QQ截图20220302101120.png

QQ截图20220302101157.png

QQ截图20220302101236.png

QQ截图20220302101329.png

QQ截图20220302101401.png

QQ截图20220302101430.png
DFS

#include<iostream>
#define cin std::ios::sync_with_stdio(false); cin.tie(0); cin
using namespace std;
const int N = 8;
int path[N];
bool vis[N];
int n;
void dfs(int u)
{
    if(u==n) 
    {
        for(int i=0;i<n;++i) cout<<path[i]<<' ';
        cout<<endl;
        return;
    }
    for(int i=1;i<=n;++i)
    {
        if(!vis[i])
        {
            path[u]=i;
            vis[i]=true;
            dfs(u+1);
            vis[i]=false;
        }
    }
        
}
int main()
{
    cin>>n;
    dfs(0);
    return 0;
}
//测试代码感受深搜的过程:
#include<iostream>
using namespace std;
#define debug(a) cout<<#a<<" = "<<a<<endl;
int n;
const int N = 10;
int path[N];
bool st[N];
void dfs(int u)
{
    if(u==n)
    {
        for(int i=0;i<n;i++)
            cout<<path[i]<<' ';
        cout<<endl;
        cout<<endl;
        return ;
    } 
    for(int j=1;j<=n;j++)
    {
        cout<<"j "<<j<<" st["<<j<<"] "<<st[j]<<endl;

        if(!st[j])
        {
            st[j]=true;
            debug(j);

            path[u]=j;
            dfs(u+1);
            //把深搜想象成一个鸡思维的人,只有上方递归走到不能走后才会进行下方回溯!
            st[j]=false;
            cout<<"回溯 ";
            debug(j);
            cout<<"回溯 ";
            cout<<" st["<<j<<"] "<<st[j]<<endl;
        }
    }
}
int main()
{
    cin.tie(0),ios::sync_with_stdio(false);
    cin>>n;
    dfs(0);
    return 0;
}
//感受一下输入5时的输出:
j 1 st[1] 0 //当st[j]为0则将j加入当前搜索路径
j = 1
j 1 st[1] 1
j 2 st[2] 0 
j = 2 //因为st[2]=0,因此加入方案
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 0
j = 3 //因为st[3]=0,因此加入方案
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 1
j 4 st[4] 0
j = 4 //因为st[4]=0,因此加入方案
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 1
j 4 st[4] 1
j 5 st[5] 0
j = 5 //因为st[5]=0,因此加入方案
1 2 3 4 5 //每搜索完一个方案接下来则开始回溯

回溯 j = 5
回溯  st[5] 0
回溯 j = 4
回溯  st[4] 0
j 5 st[5] 0 //由于上方j回溯到了4,结束了j=4时的循环,进入到枚举j=5时的循环
j = 5 //st[5]=0,将5纳入当前方案,并进入下一层递归,下面的是在下一层递归中枚举1,2,3,4,5
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 1
j 4 st[4] 0 
j = 4 //将4纳入当前方案
1 2 3 5 4 //层数已超,输出方案

回溯 j = 4
回溯  st[4] 0 //回溯到4,下面for进入j=5
j 5 st[5] 1 //st[5]=1,则不可取
回溯 j = 5 //继续回溯
回溯  st[5] 0
回溯 j = 3 //回溯到3,下面for进入j=4
回溯  st[3] 0 
j 4 st[4] 0 
j = 4 //因为st[4]=0,因此加入方案,下面进入下一层递归,并枚举1,2,3,4,5
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 0
j = 3 //因为st[3]=0,因此加入方案
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 1
j 4 st[4] 1
j 5 st[5] 0
j = 5 //因为st[5]=0,因此加入方案
1 2 4 3 5 //层数已超,输出方案

回溯 j = 5
回溯  st[5] 0
回溯 j = 3
回溯  st[3] 0
j 4 st[4] 1
j 5 st[5] 0
j = 5
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 0
j = 3
1 2 4 5 3 

回溯 j = 3
回溯  st[3] 0
j 4 st[4] 1
j 5 st[5] 1
回溯 j = 5
回溯  st[5] 0
回溯 j = 4
回溯  st[4] 0
j 5 st[5] 0
j = 5
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 0
j = 3
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 1
j 4 st[4] 0
j = 4
1 2 5 3 4 

回溯 j = 4
回溯  st[4] 0
j 5 st[5] 1
回溯 j = 3
回溯  st[3] 0
j 4 st[4] 0
j = 4
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 0
j = 3
1 2 5 4 3 

回溯 j = 3
回溯  st[3] 0
j 4 st[4] 1
j 5 st[5] 1
回溯 j = 4
回溯  st[4] 0
j 5 st[5] 1
回溯 j = 5
回溯  st[5] 0
回溯 j = 2
回溯  st[2] 0
j 3 st[3] 0
j = 3
j 1 st[1] 1
j 2 st[2] 0
j = 2
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 1
j 4 st[4] 0
j = 4
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 1
j 4 st[4] 1
j 5 st[5] 0
j = 5
1 3 2 4 5 

回溯 j = 5
回溯  st[5] 0
回溯 j = 4
回溯  st[4] 0
j 5 st[5] 0
j = 5
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 1
j 4 st[4] 0
j = 4
1 3 2 5 4 

回溯 j = 4
回溯  st[4] 0
j 5 st[5] 1
回溯 j = 5
回溯  st[5] 0
回溯 j = 2
回溯  st[2] 0
j 3 st[3] 1
j 4 st[4] 0
j = 4
j 1 st[1] 1
j 2 st[2] 0
j = 2
j 1 st[1] 1
j 2 st[2] 1
j 3 st[3] 1
j 4 st[4] 1
j 5 st[5] 0
j = 5
1 3 4 2 5 

回溯 j = 5
回溯  st[5] 0
回溯 j = 2
回溯  st[2] 0
j 3 st[3] 1
j 4 st[4] 1
j 5 st[5] 0
j = 5
j 1 st[1] 1
j 2 st[2] 0
j = 2
1 3 4 5 2

深搜好题分享:

HDU 1518 Square

NC13594 选择困难症

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目链接:https://www.acwing.com/problem/content/4948/ 题目描述: 给定一棵有 $n$ 个结点的树,结点从 $1$ 到 $n$ 编号,每个结点都有一个权值 $w_i$,现在有 $m$ 次操作,每次操作是将树中编号为 $x$ 的结点的权值加上 $y$,然后询问一些节点是否为叶子节点,如果是输出 $1$,否则输出 $0$。 输入格式: 第一行包含两个整数 $n$ 和 $m$。 第二行包含 $n$ 个整数,其中第 $i$ 个整数表示结点 $i$ 的初始权值 $w_i$。 接下来 $n-1$ 行,每行包含两个整数 $a$ 和 $b$,表示点 $a$ 和点 $b$ 之间有一条无向边。 接下来 $m$ 行,每行描述一次操作,格式为三个整数 $t,x,y$。其中 $t$ 表示操作类型,$t=1$ 时表示将编号为 $x$ 的结点的权值加上 $y$,$t=2$ 时表示询问编号为 $x$ 的结点是否为叶子节点。 输出格式: 对于每个操作 $t=2$,输出一个结果,表示询问的结点是否为叶子节点。 数据范围: $1≤n,m≤10^5$, $1≤w_i,y≤10^9$ 样例: 输入: 5 5 1 2 3 4 5 1 2 1 3 3 4 3 5 2 3 0 1 3 100 2 3 0 1 1 100 2 3 0 输出: 1 0 0 算法1: 暴力dfs,每次都重新遍历整棵树,时间复杂度 $O(nm)$ 时间复杂度: 最坏情况下,每次操作都要遍历整棵树,时间复杂度 $O(nm)$,无法通过此题。 算法2: 用一个 vector<int> sons[n+5] 来存储每个点的所有子节点,这样可以用 $O(n)$ 预处理出每个点的度数 $deg_i$,如果 $deg_i=0$,则 $i$ 是叶子节点,否则不是。 对于每个操作,只需要更新叶子节点关系的变化就可以了。如果某个节点的度数从 $1$ 变成 $0$,则该节点变成了叶子节点;如果某个节点的度数从 $0$ 变成 $1$,则该节点不再是叶子节点。 时间复杂度: 每次操作的时间复杂度是 $O(1)$,总时间复杂度 $O(m)$,可以通过此题。 C++ 代码: (算法2)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值