2021-08-06图论


知识点:图论

图的练习


提示:以下是本篇文章正文内容,下面案例可供参考

一、找到最终的安全状态(dfs+三色判断)

题目描述:
在有向图中,以某个节点为起始节点,从该点出发,每一步沿着图中的一条有向边行走。如果到达的节点是终点(即它没有连出的有向边),则停止。

对于一个起始节点,如果从该节点出发,无论每一步选择沿哪条有向边行走,最后必然在有限步内到达终点,则将该起始节点称作是 安全 的。

返回一个由图中所有安全的起始节点组成的数组作为答案。答案数组中的元素应当按 升序 排列。

该有向图有 n 个节点,按 0 到 n - 1 编号,其中 n 是 graph 的节点数。图以下述形式给出:graph[i] 是编号 j 节点的一个列表,满足 (i, j) 是图的一条有向边。
在这里插入图片描述

输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]]
输出:[2,4,5,6]

代码如下(示例):

bool safe(int i,vector<vector<int>>& graph,vector<int>&color){
    if(color[i]>0){
        return color[i]==2;
    }
    color[i]=1;
    vector<int>a=graph[i];
        for(auto &b:a){
            if(!safe(b,graph,color)){
                return false;
            }
        }
        color[i]=2;
        return true;
}
class Solution {
public:
    vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
        int n=graph.size();
        vector<int>ans;
        vector<int>color(n);//0:未走过的点,1:正在判断的点,2:安全的点
        for(int i=0;i<n;i++){
            if(safe(i,graph,color)){
                ans.push_back(i);
            }
        }
        return ans;
    }
};

二、课程表(拓扑排序)

拓扑排序:仅存在于有向无环图,所以可以用来判断图中是否存在环路。
写出拓扑排序可以按照dfs的思想先写出,再将结果倒序输出!!

题目描述:
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false
示例:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。

对于上面该题,解决方法为判断该图中是否存在环路,即是否能写出拓扑排序。利用dfs

class Solution {
public:
    //利用深度遍历实现拓扑排序
    vector<int>visited;
    vector<int>in;
    vector<vector<int>>temp;
    bool flag;
    void dfs(int i){
        if(!flag){
            return;
        }
        visited[i]=1;
        for(auto &a:temp[i]){
            if(visited[a]==1){
                flag=false;//遇到环了
                return;
            }
            dfs(a);
        }
        visited[i]=2;//表示已经遍历完这个值
        if(in[i]==0){
            ans.push_back(i);
            in[i]=1;
        } 
    }
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        temp.resize(numCourses);
        visited.resize(numCourses);
        in.resize(numCourses);
        flag=true;
        for(int i=0;i<prerequisites.size();i++){
            temp[prerequisites[i][0]].push_back(prerequisites[i][1]);
        }
        for(int i=0;i<numCourses;i++){
            if(!flag){
                return {};
            }
            if(visited[i]!=2){
                dfs(i);
            }
        }
        return ans;
    }
    vector<int>ans;
};

三、最小高度树(拓扑排序)

题目描述:
树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
给你一棵包含 n 个节点的树,标记为 0 到 n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条无向边。可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。
请你找到所有的 最小高度树 并按 任意顺序 返回它们的根节点标签列表。
树的 高度 是指根节点和叶子节点之间最长向下路径上边的数量。
在这里插入图片描述
输入:n = 4, edges = [[1,0],[1,2],[1,3]]
输出:[1]
解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。

思路:此题用dfs遍历每个节点的高度会超时,借用拓扑排序的思想:将入度为1的节点依次删掉,直到只剩下不超过两个节点,则为答案。(因为最终答案不会超过两个节点)

class Solution {
public:
    //此题如果用dfs遍历会超时
    //采用拓扑排序的思想,将入读小的节点先删除
    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        if(n==1){
            return {0};
        }
        vector<vector<int>>temp(n);//每一个点的出度节点
        vector<int>degress(n,0);//每一个点的入度
        for(int i=0;i<edges.size();i++){
            temp[edges[i][0]].push_back(edges[i][1]);
            temp[edges[i][1]].push_back(edges[i][0]);
            degress[edges[i][0]]++;
            degress[edges[i][1]]++;
        }
        vector<int>ret;
        for(int i=0;i<n;i++){
            if(degress[i]==1){
            //ret是最终要返回的答案,只存放节点是1的点
                ret.push_back(i);
            }
        }
        int h=n;
        while(h>2){//h代表剩余的节点数量
            vector<int>ans;
            int k=ret.size();
            for(auto &a:ret){
                degress[a]--;
                for(auto &b:temp[a]){
                    if(degress[b]!=0){
                        degress[b]--;
                        if(degress[b]==1){
                            ans.push_back(b);
                        }
                    }
                }
            }
            ret.clear();
            for(auto &a:ans){
                ret.push_back(a);
            }  
             h-=k;
        }
        return ret;
    }
};

四、连通分量(并查集)

题目描述:
树可以看成是一个连通且 无环 的 无向 图。
给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。
在这里插入图片描述

输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]

并查集思想:
初始时,每个节点都属于不同的连通分量。遍历每一条边,判断这条边连接的两个顶点是否属于相同的连通分量。

如果两个顶点属于不同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间不连通,因此当前的边不会导致环出现,合并这两个顶点的连通分量。

如果两个顶点属于相同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间已经连通,因此当前的边导致环出现,为附加的边,将当前的边作为答案返回。

代码如下(示例):

class Solution {
public:
    int find(vector<int>&temp,int x){//查找某个节点的根节点
        if(temp[x]!=x){
            temp[x]=find(temp,temp[x]);
        }
        return temp[x];
    }
    void Union(vector<int>&temp,int x1,int x2){
    //将x1的根节点与x2的根节点连接
        temp[find(temp,x1)]=find(temp,x2);
        return;
    }
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        //并查集:判断每一个结点是否属于同一个连通分量
        int n=edges.size();
        vector<int>temp(n+1);
        for(int i=1;i<=n;i++){
            temp[i]=i;
        }
        for(auto &a:edges){
            int x1=a[0];
            int x2=a[1];
            if(find(temp,x1)!=find(temp,x2)){//不属于同一个连通分量
                Union(temp,x1,x2);
            }
            else{
                return a;
            }
        }
        return {};
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值