C++之DFS深度优先搜索学习

自创!自创!自创!禁止抄袭! 

目录

1. 算法思想

 2.枚举方法

2.1指数型枚举

2.2 排列型枚举

2.3 组合型枚举

 3. 剪枝优化

4. 图的搜索


1. 算法思想

DFS算法的核心理念可被概括为一种深度优先的遍历策略。具体而言,该算法选定一个顶点作为起点,标记并访问它,随后沿着与此顶点直接相连且未被探索过的邻接点继续深入遍历。这一过程不断重复,直至所有可达的顶点均被探访完毕。若图中仍存在未探访的顶点,算法将选择一个新的未访问顶点作为新的起点,再次启动遍历流程。这样的操作循环进行,直至图中的每一个顶点都被纳入访问列表中,确保了全面而彻底的遍历。

在实施DFS算法时,必须注意几个关键点。首先,每个顶点在遍历过程中仅被访问一次,这避免了重复工作和可能的无限循环。其次,算法需要一种机制来追踪已访问的顶点以及待访问的顶点,通常借助于栈或递归调用来实现。此外,DFS特别适合于解决路径发现问题、拓扑排序等图论问题,其高效性在于能够快速深入顶点的连接结构,揭示图的内在特性。

综上所述,DFS算法是一种强大的图遍历技术,通过深度优先的方式系统地探索图的结构,从而为各种图论问题的求解提供了基础。

总而言之,DFS就是一条路走到黑,不行就回头再走

 2.枚举方法

2.1指数型枚举

 这不是洛谷啊!QwQ#5. 递归实现指数型枚举

指数型枚举是一种算法设计策略,其核心思想在于对一系列元素进行选择或不选择的操作。具体来说,当有n个独立的数时,每个数都有被选中或不被选中的两种可能性。这种二元选择的特性导致了可能的组合总数呈指数增长,即2^n种组合。

通过这样的枚举过程,我们能够系统地探索所有可能的选择组合,尽管这可能导致计算量随n的增加而急剧增长。这种方法在解决需要全面考察所有可能性的问题时非常有用,但同时也需要注意其潜在的高时间复杂度问题。

 接下来,上代码!

#include<bits/stdc++.h>
using namespace std;
int n;
int vis[100005];
void DFS(int x)
{
	//表示已经n个数都被判断过了,一种方案已经搜索完成
    if(x>=n-1){
        for(int i = 1;i <= n;i++)
        {
            if(vis[i] == 1)
            {
                cout<<i<<' ';
            }    
        }
        cout<<endl;
        return;
    }
    vis[x]=2;//表示不选
    DFS(x+1);//继续搜下一个
    vis[x]=0;//回溯
    vis[x]=1;//表示选
    DFS(x+1);//继续搜下一个
    vis[x]=0;//回溯

}
int main(){
    cin>>n;
    DFS(1);
    return 0;
}

2.2 排列型枚举

排列型枚举,作为一种算法技巧,允许我们生成一个特定集合的所有可能排列。这种概念并不陌生,因为在中学时期,我们就已经开始接触和学习排列组合的相关理论了。排列的关键在于顺序的区分,例如,当我们考虑数字1、2和3时,'1,2,3'与'1,3,2'被视为两种不同的排列方式。

现在,让我们通过一个具体的例题来进一步探讨这一概念. 

 #7. 递归实现排列型枚举.....这不是洛谷!QwQ

在处理生成n个数字的全排列方案的问题时,我们通常需要采用一种有效的算法来确保每个可能的组合都被考虑到。为了实现这一目标,我们可以引入一个额外的数组,称之为vis数组,它的主要作用是跟踪每个数字是否已经被选择过。这个数组起到了标记的作用,帮助我们在生成排列的过程中避免重复和遗漏。

具体来说,vis数组的长度与待排列的数字数量相同,初始时,每个元素都设置为false,表示对应的数字尚未被选中。当我们在排列过程中选择一个数字时,就将vis数组中对应位置的值更新为true,表明该数字已经被考虑过。这样一来,在接下来的排列组合尝试中,我们就可以通过查看vis数组的状态来确定哪些数字还可以被选择,从而确保所有可能的排列都能被生成。

这种方法不仅适用于生成数字的全排列,还可以扩展到其他需要生成组合的场景中。通过维护一个状态数组,我们可以更加灵活和高效地处理各种排列组合问题,无论是在编程实践还是在算法设计中,这种技术都是非常实用的。

 上代码!

#include <bits/stdc++.h>
using namespace std;
int vis[15];
int a[15];
int n;
void DFS(int x){
	//表示n个数字都已经选过了
    if(x>=n-1){
        for(int i=1;i<=n;i++){
            cout<<a[i];
        }
        cout<<endl;
        return ;
    }
    for(int i=1;i<=n;i++){
        if(!vis[i])
        {
            vis[i]=1;//选过标记为1
            a[x]=i;//表示该数字被选上了
            DFS(x+1);//继续选下一个数字
            vis[i]=0;//回溯重置该数字的状态
            a[x]=0;//,也可以不写,因为数据可以直接覆盖
        }
    }
}

int main(){
    cin>>n;
    DFS(1);
    return 0;
}

输入的文本描述了一个算法过程,即通过枚举和回溯来遍历所有可能的数的组合。这个过程通常用于解决组合优化问题,例如寻找满足特定条件的数字序列。以下是对原文的创新表述:

在探索数字组合的广阔宇宙中,我们踏上了一段旅程,目标是揭示隐藏在n个数背后的奥秘。我们的探险从精心挑选这些数的一个子集开始,每一步都像是在绘制一张复杂的网络图,每个节点代表一个独特的选择。一旦某个特定的组合被选中,我们便沿着这条路径深入挖掘,直到它的潜力被彻底探索。然后,我们并不停留,而是回溯到决策的起点,重新审视那些未被采纳的分支,确保每一个可能性都被仔细考量。

在这个过程中,我们不仅仅是在寻找答案,更是在体验一场思维的舞蹈,每一个回溯都是对先前选择的反思,每一次新的尝试都充满了发现未知的可能。这种方法,既系统又充满创造性,确保了没有任何一个潜在的解决方案会被遗漏,同时也体现了对问题的深刻理解和全面探索。

这样的表述不仅保留了原文的意义和主旨,即通过枚举和回溯来探索所有可能的数的组合,还增添了文学色彩,使读者能够更加生动地感受到这一过程的复杂性和美丽。


2.3 组合型枚举

 组合是一种数学概念,它指的是从n个不同元素中取出m(m≤n)个元素的所有取法。在组合中,元素的顺序并不重要,也就是说,1 2 3和1 3 2被视为同一种方案。这种不考虑元素顺序的特性使得组合成为一种独特的数学工具,可以用来解决许多实际问题。

 这不是洛谷!.... #6. 递归实现组合型枚举

在这次的深度优先搜索(DFS)算法实现中,我们设计了两个关键参数。第一个参数负责追踪已经枚举的数字数量,而第二个参数则指明了从哪一个数字起开始向后选择。由于本次枚举采用的是组合方式,因此我们可以确保数字的顺序符合特定的规则。

例如,如果我们选择了数字序列1、3、2,那么在这之前,序列1、2、3肯定已经被考虑过了。这意味着在枚举过程中,像1、3、2这样的组合是不会出现的。这是因为我们从指定的数字开始,只会选择那些字典序大于当前数字的后续数字。这种策略保证了不会有字典序较大的数字排在字典序较小的数字前面的情况发生。

为了实现这一点,我们必须记录下从哪个数字开始进行选择。这个信息对于维护正确的枚举顺序至关重要。通过这种方式,我们确保了枚举过程的逻辑性和高效性,同时也避免了不必要的重复和错误的序列生成。

总结来说,这两个参数不仅帮助我们控制了枚举的过程,还确保了枚举结果的正确性和有序性。通过精心设计的算法,我们能够高效地探索所有可能的组合,而不会发生逻辑上的错误或遗漏。

#include <bits/stdc++.h>
using namespace std;
int a[100005];
int n,r;
void DFS(int x,int start){
	//已经选够的情况
    if(x>=r-1){
        for(int i = 1;i<=r;i++){
            cout<<a[i];
        }
        cout<<'\n';
        return;
    }
    for(int i=start;i<=n;i++){
        a[x]=i;
        DFS(x+1,i+1);//选下一个数字,并且下一个数字的字典序要比本次大,也就是从i+1开始往后选
        a[x]=0;
    }
}
int main(){
    cin>>n>>r;
    DFS(1,1);
    return 0;
}

 3. 剪枝优化

在探索深度优先搜索(DFS)的领域内,剪枝技术如同一把锋利的剑,它的作用在于剔除那些冗余且无效的搜索路径。这种技术的根本宗旨是在搜索进程的早期阶段,敏锐地辨识并排除那些无法导向解答的路径或状态,以此避免在这些无效的道路上耗费宝贵的时间与计算资源。

深度优先搜索,或者说DFS,本质上是一种极具侵略性的算法。它被广泛认为是一种极端的暴力搜索方法,其时间复杂度往往达到指数级或阶乘级。在这样的背景下,剪枝技术的价值显得尤为突出。如果没有剪枝这一优化手段,DFS很容易因为过度搜索而陷入时间超支的困境。因此,剪枝不仅是提高搜索效率的关键,更是确保算法实用性的保障。

 其实也就是给它一个visit数组,让他记录有没有走过,走过就赋值true。

 题目暂时没有找到....抱歉

我会继续努力的


4. 图的搜索

步骤:
1.选择起始点:从图的某个顶点v开始。
2.标记当前顶点:将当前顶点v标记为已访问,以避免重复访问。
3.遍历邻接点:对于v的每个未访问的邻接点w,递归地执行DFS,从w开始。
4.回溯:当没有更多的邻接点可以遍历时,返回到上一步的顶点。

例题来啦! 

 P1683 入门

 

很简单,我们只需要一个个试就行了,还要判断它是否可以走等.... 

#include <bits/stdc++.h>
using namespace std;
int x[10000001],y[100000001],ans,sum,w,h,qx,qy;
char c[1001][1001];
bool flag[1001][1001];
int py[5]={0,-1,0,1,0},px[5]={0,0,1,0,-1};//上下左右 
int se(int sy,int sx)
{
	for(int i=1;i<=4;i++)
	{
		int dy=py[i]+sy,dx=px[i]+sx;
		if(flag[dy][dx]==0&&c[dy][dx]=='.'&&dy<=h&&dy>0&&dx<=w&&dx>0)
		{
			flag[dy][dx]=1;
			sum++;
			se(dy,dx);
		}
	}
}
int main()
{
	cin>>w>>h;
	for(int i=1;i<=h;i++)
	{
		for(int j=1;j<=w;j++)
		{
			cin>>c[i][j];
			if(c[i][j]=='@')
			{
				qx=j,qy=i;
			}
		}
	}
	sum++;
	flag[qy][qx]=1;
	se(qy,qx);
	cout<<sum<<endl;
	return 0;
}

好了,本期DFS详解到此结束,欢迎大家评论和提出意见。 

记得去洛谷练题哦!OvO bye!

  • 55
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值