算法学习之DFS(深度优先搜索)+并查集模板

DFS概念

深度优先搜索(缩写DFS)有点类似广度优先搜索,也是对一个连通图进行遍历的算法。它的思想是从一个顶点V0开始,沿着一条路一直走到底,如果发现不能到达目标解,那就返回到上一个节点,然后从另一条路开始走到底,这种尽量往深处走的概念即是深度优先的概念。

算法学习之BFS(广度优先搜索)-CSDN博客

深度与广度的比较

我们假设一个节点衍生出来的相邻节点平均的个数是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)。

  • 20
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值