C++数据结构-树的深度优先搜索及树形模拟法运用(进阶篇)

1. DFS简介

深度优先搜索算法(英语:Depth-First-Search,简称DFS)是一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况算法时间复杂度为O(!n),DFS搜索的过程访问可以称之为DFS序。

如图:

对于一颗这样的树,我们的DFS序可以为:abdefc(即时对于同一颗的树,其DFS序不一定唯一),即访问a之后访问a的子结点b,再在b的基础上依次访问它的子结点def,最后回退到a处访问c。

这与前文花大篇幅介绍的先序,中序,后续遍历如出一辙,唯一的不同就是这样的一棵树并不只存在有左右两个结点,它可以是多枝的。

而即时对于一颗深度为n的二树,在没有任何优化的情况下适用DFS去搜索访问数据,其算法的时间复杂度也高达O(2^n),在数据较大的情况下DFS是无法满足程序的时间要求,这就会涉及到一个思路——剪枝,即通过现有的数据判断接下来的数据无法再满足解,直接将当前结点以后的所有数据舍弃,遍历不再访问,通过精心设计的剪纸可以使得DFS搜索的效果得到很大提升。

2. 模板:

对于一个标准的DFS模板而言,其包括了以下的内容:

bool check(参数)
{
    if(满足条件)
        return true ;
    return false;
}
  
void dfs(int step)
{
        判断边界
        {
            相应操作
        }
        尝试每一种可能
        {
               满足check条件
               标记
               继续下一步dfs(step+1)
               恢复初始状态(回溯的时候要用到)
        }
}

3. 模拟法简介

在前面的文章已经提到过模拟这个思维,模拟的思维无处不在,就树形的DFS算法而言,我们更多的情况并非建立一棵树,这对我们书写和易用性而言太差了,我们通常会适用多个数组进行模拟,树也是可以利用数组进行模拟的。

如下图:

 上面一排表示数组下标,下面一排表示数据,其实通过这样的简单方式,也能够设计出一个模拟的二叉树,其具体的表示为:

 仔细对比以下数据,可以发现数组中的数据存储的就是与之相联系的数组下标,通过这样的方式可以方便的建立一颗简单的模拟法的树,同时再创建一个数组用来存储具体的值,两个数组保持一一对应的关系就可以简单完成,这就是通过模拟算法来模拟树结构的核心思路,这个思路进行扩展其实会发现与建立树,甚至是后文才会学的图并没有什么核心的区别。

4. 例题—全排列

从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。

对于常规的思路而言,我们需要对0~9一共十个数字进行全排列,就需要列出10层for循环,每一层分别表示不同的数字,同时还需要利用if语句进行判重,因此写出来的代码就显得非常的累赘,其格式大致如下:

for()
    for()
        for()
            …………    省略N层for循环
                for()
                      if()

 不要小看这样的代码,对于一个新手而言,能够有这样大胆的思维,其实是很难得的,不过就后面而言,这样的思维显得莫过于臃肿了,而且极其不相似一种成熟的代码,实际上,利用DFS算法,我们将10个数通过数组进行拆分,需要多少就调用多少,同时再建立一个判断的数组,这样也不需要我们写出累赘的if语句了,利用函数的递归实现多层的for功能。

本例题的代码为:

#include<iostream>
#include<cmath>
using namespace std;
  
int p[10]= {0};
bool vis[10]= {0};
int n;
void dfs(int x) {
    if (x==n+1) {
        for(int i=1; i<=n; i++)
            cout<<p[i]<<" ";
        cout<<endl;
        return ;
    }
  
    for (int i=1; i<=n; i++) {
        if (vis[i]==false  ) {
            p[x] = i;
            vis[i] = true;
            dfs(x+1);
            vis[i] = false;
        }
    }
}
  
int main() {
    while (cin>>n) {
        dfs(1);
    }
    return 0;
}

 PS:这样的全排列C++的STL库已经封装好了,称之为next_permuitation(n,n+a);

 5. 例题—程序员爬楼梯

爬楼梯的问题更像我们常规的算法问题,以是否能够走到第4格楼梯为例,我们初始的状态是走0个楼梯,建立二叉树,根结点设置为0,随后对于0这个状态而言,有两种形态,走一步和走三步,这分别对应二叉树的左孩子和右孩子,在走完之后,我们成功的引出了两种状态,共走1步和共走3步 ,我们再分别在这个基础上重新建立左孩子和右孩子引出两个状态,便可以逐渐构建出这颗二叉树,最终,通过数最终的状态我们可以知道一共有3个答案满足条件。

 套用DFS的模板可以轻易求解,本例题代码为:

#include<iostream>
  
using namespace std;
  
typedef long long ll;
ll ans,n;
  
void dfs(ll k) {
    if(k==n) {
        ans++;
        return;
    } else if(k>n) {
        return;
    }
    dfs(k+1);
    dfs(k+3);
}
  
int main() {
    while(cin>>n) {
        ans=0;
        dfs(0);
        cout<<ans<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dola_Pan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值