介绍
深度优先搜索(Depth-First Search,DFS)是一种用于遍历或搜索树或图的算法。它从根节点开始,沿着树的深度遍历子节点,当遍历到叶子节点或者没有未访问的子节点时,回溯到上一个节点,继续遍历未访问的节点,直到遍历完整棵树或图。
DFS 可以用递归或者栈来实现。在递归实现中,算法会先访问根节点,然后递归访问每个子节点。在栈实现中,算法会先访问根节点,然后将根节点的子节点入栈,然后继续访问栈顶节点的子节点,直到遍历完整棵树或图。
DFS 适用于许多问题,如寻找图的连通分量、寻找图的环、寻找图的路径、寻找图的最短路径等。
在树的问题中,DFS 通常用于寻找树的直径、寻找树的最短路径、寻找树的最大深度等。
当我们使用深度优先搜索(DFS)算法时,我们从树(或图)的某个起始节点开始,然后沿着树的深度尽可能远地访问节点,直到到达树的末端。然后我们回溯到上一个节点,尝试访问尚未访问的节点,直到我们访问完整个树(或图)。
下面是一个使用递归实现的深度优先搜索的示例代码,我们假设树的节点使用整数进行标识:
#include <iostream>
#include <vector>
using namespace std;
vector<int> tree[10005];
bool visited[10005];
void dfs(int node) {
visited[node] = true;
cout << node << " "; // 访问节点
for (int i = 0; i < tree[node].size(); i++) {
int nextNode = tree[node][i];
if (!visited[nextNode]) {
dfs(nextNode); // 递归访问下一个节点
}
}
}
int main() {
// 假设树的节点和边是通过输入得到的
int n;
cin >> n;
for (int i = 0; i < n - 1; i++) {
int a, b;
cin >> a >> b;
tree[a].push_back(b);
tree[b].push_back(a);
}
// 从节点1开始深度优先搜索
dfs(1);
return 0;
}
在上面的示例代码中,我们使用递归实现了深度优先搜索。我们从节点1开始深度优先搜索整个树,访问了每一个节点,并输出了访问的顺序。
需要注意的是,为了避免重复访问节点,我们使用了一个数组 visited 来标记节点是否已经被访问过。
深度优先搜索是一个非常重要的算法,它在树和图的遍历、路径搜索、连通性判断等问题中都有着重要的应用。
dfs常考题目:
P1030 [NOIP2001 普及组] 求先序排列
题目描述
给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,且二叉树的节点个数 ≤8≤8)。
输入格式
共两行,均为大写字母组成的字符串,表示一棵二叉树的中序与后序排列。
输出格式
共一行一个字符串,表示一棵二叉树的先序。
输入输出样例
输入 #1复制
BADC BDCA
输出 #1复制
ABCD
说明/提示
【题目来源】
NOIP 2001 普及组第三题
1.后序遍历中,最后一个节点一定是根节点(对于每一颗子树也成立);
2.既然这题要求先序遍历,那么我们只需一次输出访问的父节点即可;
这样的话,我们只要递归将一棵大树分成两颗子树,让后找他们的父节点,不断递归输出;
3.那么难点就在这了,如何通过一个中序和后序遍历中找出两段子树的后序遍历序列(后序,因为只有后序我们才方便找到父节点)呢?
自己可以拿几个样例做一做,耐性点就会发现它的套路,我这里简单说一下:
在中序遍历中找到当前父节点后,我们可以分别求出他的左子树节点数和右子树节点数,因为中序遍历访问的顺序是左子树,父节点,右子树,所以可以直接计算出;
然后,由于我们对结点的访问一定是先访问一颗子树,在访问另一颗,所以在我们的原后序遍历串右边界中减掉右子树节点个数再减一即为新的左子树右边界,在原后序遍历串左边界加上左子树节点个数即为新的右子树左边界;
当然右子树右边界和左子树左边界这个非常好确定,就不在多说,自己看代码吧
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
char s1[10];
char s2[10];
int len;
inline int find(char ch)
{
for(int i=0;i<len;i++)
{
if(s1[i]==ch) return i;
}
}
void dfs(int l1,int r1,int l2,int r2)
{
int m=find(s2[r2]);
cout<<s2[r2];
if(m>l1) /*具有左子树*/dfs(l1,m-1,l2,r2-r1+m-1);//r1-m为右子树结点数
if(m<r1) /*具有右子树*/dfs(m+1,r1,l2+m-l1,r2-1);//m-l1为左子树节点数
}
int main()
{
cin>>s1;
cin>>s2;
len=strlen(s1);
dfs(0,len-1,0,len-1);
}
P1331 海战
题目背景
在峰会期间,武装部队得处于高度戒备。警察将监视每一条大街,军队将保卫建筑物,领空将布满了 F-2003 飞机。
此外,巡洋船只和舰队将被派去保护海岸线。不幸的是,因为种种原因,国防海军部仅有很少的几位军官能指挥大型海战。因此,他们培养了一些新海军指挥官。军官们选择了“海战”游戏来帮助他们学习。
题目描述
在一个方形的盘上,放置了固定数量和形状的船只,每只船却不能碰到其它的船。在本题中,我们认为船是方形的,所有的船只都是由图形组成的方形。
求出该棋盘上放置的船只的总数。
输入格式
第一行为两个整数 RR 和 CC,用空格隔开,分别表示游戏棋盘的行数和列数。
接下来 RR 行,每行 CC 个字符,为 #
或 .
。#
表示船只的一部分,.
表示水。
输出格式
一行一个字符串,如果船的位置放得正确(即棋盘上只存在相互之间不能接触的方形,如果两个 #
号上下相邻或左右相邻却分属两艘不同的船只,则称这两艘船相互接触了)。就输出 There are S ships.
,SS 表示船只的数量。否则输出 Bad placement.
。
输入输出样例
输入 #1复制
6 8 .....#.# ##.....# ##.....# .......# #......# #..#...#
输出 #1复制
There are 5 ships.
说明/提示
对于 100%100% 的数据,1≤R,C≤10001≤R,C≤1000。
/*这道题的难点在于判断是否有船相邻。
通过自己模拟的数据可以得出结论:
如果图是不和法的,一定存在如下结构:
# #
. #
或
# #
# .
或
# .
# #
或
. #
# #
即在一个2*2的方格中有三个#。所以就能得出代码:*/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int r,c;
char map[1010][1010];
int fx[4]={0,-1,1,0};
int fy[4]={-1,0,0,1};
int dfs(int x,int y){
map[x][y]='*';
for(int i=0;i<4;i++){
if(x+fx[i]>0&&x+fx[i]<=r&&y+fy[i]>0&&y+fy[i]<=c&&
map[x+fx[i]][y+fy[i]]=='#')dfs(x+fx[i],y+fy[i]);
}
}//把与#连通的所有点改成*因为它们是同一艘船
bool d(int i,int j){
int c=0;
if(map[i][j]=='#')c++;
if(map[i+1][j]=='#')c++;
if(map[i][j+1]=='#')c++;
if(map[i+1][j+1]=='#')c++;
if(c==3)return 0;
return 1;
}//判断是否合法
int main(){
scanf("%d%d",&r,&c);
register int i,j;
for(i=1;i<=r;i++){
for(j=1;j<=c;j++){
cin>>map[i][j];
}
}
int s=0;
for(i=1;i<=r;i++){
for(j=1;j<=c;j++){
if(i<r&&j<c&&d(i,j)==0){
printf("Bad placement.");
return 0;//不合法后面就没必要继续了
}
}
}
for(i=1;i<=r;i++){
for(j=1;j<=c;j++){
if(map[i][j]=='#'){
s++;
dfs(i,j);
}//因为前面已经确保了是合法的,现在只需统计船的数量
}
}
printf("There are %d ships.",s);
return 0;
}
P1451 求细胞数量
题目描述
一矩形阵列由数字 00 到 99 组成,数字 11 到 99 代表细胞,细胞的定义为沿细胞数字上下左右若还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。
输入格式
第一行两个整数代表矩阵大小 nn 和 mm。
接下来 nn 行,每行一个长度为 mm 的只含字符 0
到 9
的字符串,代表这个 n×mn×m 的矩阵。
输出格式
一行一个整数代表细胞个数。
输入输出样例
输入 #1复制
4 10 0234500067 1034560500 2045600671 0000000089
输出 #1复制
4
说明/提示
数据规模与约定
对于 100%100% 的数据,保证 1≤n,m≤1001≤n,m≤100。
介绍一种思考难度稍大的方法
1.Begin
(看见题面马上就知道是Floodfill
但因为一些原因,NOIP暂停,我想到了区间维护就换了种做法
注意!开始讲题(敲黑板)
样例搬运~
4 10
0234500067
1034560500
2045600671
0000000089
我们可以认为,在某行的数据可表示为(如第一行)
数值:0,2,3,4,5,0,0,0,6,7
位置:1,2,3,4,5,6,7,8,9,10
在[2,5] , [9,10]处有数据
同理,第二行中,[1,1],[3,6],[8,8]有数据
你看看
这一行的[3,6]与上一行的[2,5]有交集,所以他们应该在同一联通块中
2.Naive
你要真的这么想,就图样图森破了
那么我想反问一句,为什么不从下往上连呢
当然可以
因为往下连边可以减少find次数期望,推倒不难。但笔者不会
LATEXLATEX
就不用了罢
以下是代码(直接复制并粘贴有惊喜哦)
//能不用尽量别用,O(N*M)
//27ms
#include<cstdio>
int n,m,a[101][101];//a就是地图
int tot=0,fa[10010],stlst/*上一行的起始*/,stnow/*这一行的起始*/,qj[10010][2];
int find(int u)
{
return u==fa[u]?u:fa[u]=find(fa[u]);//板子
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%1d",&a[i][j]);//鸣谢最上面的题解
for(int i=1;(i<<1)<=n*m;i++) fa[i]=i;//最多N*M/2个块
for(int i=1;i<=n;i++)
{
stlst=stnow;
stnow=tot;
for(int j=1;j<=m;j++)
if(a[i][j])
{
qj[tot][0]=j;
while(a[i][j]) j++;
qj[tot][1]=j-1;
for(int k=stlst;k<stnow;k++)
if(qj[k][1]>=qj[tot][0]&&qj[k][0]<=qj[tot][1]) fa[find(k)]=tot;
tot++;
}
}
int ans=0;
for(int i=tot-1;i>=0;i--) if(fa[i]==i)/*我上面没有人*/ ans++;
printf("%d",ans);
}
//People who copied will AK IOI!