DFS概念
深度优先搜索(缩写DFS)有点类似广度优先搜索,也是对一个连通图进行遍历的算法。它的思想是从一个顶点V0开始,沿着一条路一直走到底,如果发现不能到达目标解,那就返回到上一个节点,然后从另一条路开始走到底,这种尽量往深处走的概念即是深度优先的概念。
深度与广度的比较
我们假设一个节点衍生出来的相邻节点平均的个数是N个,那么当起点开始搜索的时候,队列有一个节点,当起点拿出来后,把它相邻的节点放进去,那么队列就有N个节点,当下一层的搜索中再加入元素到队列的时候,节点数达到了N2,你可以想想,一旦N是一个比较大的数的时候,这个树的层次又比较深,那这个队列就得需要很大的内存空间了。
于是广度优先搜索的缺点出来了:在树的层次较深&子节点数较多的情况下,消耗内存十分严重。广度优先搜索适用于节点的子节点数量不多,并且树的层次不会太深的情况。
那么深度优先就可以克服这个缺点,因为每次搜的过程,每一层只需维护一个节点。但回过头想想,广度优先能够找到最短路径,那深度优先能否找到呢?深度优先的方法是一条路走到黑,那显然无法知道这条路是不是最短的,所以你还得继续走别的路去判断是否是最短路?
于是深度优先搜索的缺点也出来了:难以寻找最优解,仅仅只能寻找有解。其优点就是内存消耗小,克服了刚刚说的广度优先搜索的缺点。
伪代码
bool DFS(Node n, int d){
if (d == 4){//路径长度为返回true,表示此次搜索有解
return true;
}
for (Node nextNode in n){//遍历跟节点n相邻的节点nextNode,
if (!visit[nextNode]){//未访问过的节点才能继续搜索
//例如搜索到V1了,那么V1要设置成已访问
visit[nextNode] = true;
//接下来要从V1开始继续访问了,路径长度当然要加
if (DFS(nextNode, d+1)){//如果搜索出有解
//例如到了V6,找到解了,你必须一层一层递归的告诉上层已经找到解
return true;
}
//重新设置成未访问,因为它有可能出现在下一次搜索的别的路径中
visit[nextNode] = false;
}
//到这里,发现本次搜索还没找到解,那就要从当前节点的下一个节点开始搜索。
}
return false;//本次搜索无解
}
一些例子
2316.统计无向图中无法互相到达点对数
给你一个整数 n
,表示一张 无向图 中有 n
个节点,编号为 0
到 n - 1
。同时给你一个二维整数数组 edges
,其中 edges[i] = [ai, bi]
表示节点 ai
和 bi
之间有一条 无向 边。
请你返回 无法互相到达 的不同 点对数目 。
示例 1:
输入:n = 3, edges = [[0,1],[0,2],[1,2]] 输出:0 解释:所有点都能互相到达,意味着没有点对无法互相到达,所以我们返回 0 。
示例 2:
输入:n = 7, edges = [[0,2],[0,5],[2,4],[1,6],[5,4]] 输出:14 解释:总共有 14 个点对互相无法到达: [[0,1],[0,3],[0,6],[1,2],[1,3],[1,4],[1,5],[2,3],[2,6],[3,4],[3,5],[3,6],[4,6],[5,6]] 所以我们返回 14 。
提示:
1 <= n <= 105
0 <= edges.length <= 2 * 105
edges[i].length == 2
0 <= ai, bi < n
ai != bi
- 不会有重复边。
这个题的思路一共两种
- 并查集
- DFS
并查集:
并查集的C++实现及优化 - 简书 (jianshu.com)
#include<iostream>
#include<vector>
#include <numeric> //iota头文件 用来批量递增赋值vector的元素的
using namespace std;
//1.实现并查集(使用路径压缩)
class UnionFind{
private:
vector<int>parents;
vector<int>sizes;
public:
UnionFind(int n) : parents(n),sizes(n,1){
iota(parents.begin(),parents.end(),0);
}
int Find(int x){
if(parents[x]==x){
return x;
}
return parents[x]=Find(parents[x]);
}
void Union(int x,int y){
int rx=Find(x),ry=Find(y);
if(rx!=ry){ //这两个父节点不一样
if(sizes[rx]>sizes[ry]){
parents[ry]=rx; //把节点数少的合并成多的节点的子节点
sizes[rx]+=sizes[ry];
}else{
parents[rx]=ry; //一样就无所谓
sizes[ry]+=sizes[rx];
}
}
}
int GetSize(int x){
return sizes[x];
}
};
class Solution {
public:
long long countPairs(int n, vector<vector<int>>& edges) {
UnionFind uf(n);
for(const auto&edge : edges){
uf.Union(edge[0],edge[1]); //先把这些点都合并
}
long long res=0;
for(int i=0;i<n;i++){
res+=n-uf.GetSize(uf.Find(i)); //然后找每个点的父节点=>再求父节点下合并的节点数目
}
return res/2;
}
};
int main(){
int n=7;
// edges = [[0,2],[0,5],[2,4],[1,6],[5,4]]
vector<vector<int>> edges={{0,2},{0,5},{2,4},{1,6},{5,4}};
Solution t;
cout<<t.countPairs(n, edges);
return 0;
}
DFS
c++的function用法 - balabalahhh - 博客园 (cnblogs.com)
#include<iostream>
#include<vector>
#include <numeric> //iota头文件 用来批量递增赋值vector的元素的
#include <functional>
using namespace std;
class Solution {
public:
long long countPairs(int n, vector<vector<int>>& edges) {
//1.构建邻接表
vector<vector<int>> graph(n);
for(const auto& edge : edges){
graph[edge[0]].push_back(edge[1]);
graph[edge[1]].push_back(edge[0]);
}
vector<bool> visited(n,false);
function<int(int)> dfs=[&](int x)->int{
visited[x] = true;
int count=1;
for(auto y:graph[x]){
if(!visited[y]){
count+=dfs(y);
}
}
return count;
};
long long res=0;
for(int i=0;i<n;i++){
if(!visited[i]){
long long count=dfs(i);
res+=count*(n-count);
}
}
return res/2;
}
};
int main(){
int n=7;
// edges = [[0,2],[0,5],[2,4],[1,6],[5,4]]
vector<vector<int>> edges={{0,2},{0,5},{2,4},{1,6},{5,4}};
Solution t;
cout<<t.countPairs(n, edges);
return 0;
}
时间复杂度:O(m+n),其中 m是边数。构造临接表消耗 O(m+n),dfs 消耗 O(m+n)。
空间复杂度:O(m+n)。