今天带来两道图论的题目:547. 省份数量797. 所有可能的路径

547.省份数量 题目描述:

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

示例:

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2

本题不算很难,但是因为是没有接触过的类型,整体会觉得很陌生,应该多练几题熟悉一下应该就会渐渐懂了。

本题需要使用到并查集,在c++中我们一般使用哈希表来当做并查集。

并查集和树比较类似,树每个节点会记录其子节点,而并查集会记录其父节点。

在这道题目中我们可以把相连的城市在并查集中相连接起来,储存方式是当前节点与其父亲的键值对。

unordered_map<int, int> father;
首先我们需要先把节点先放入并查集中,再遍历给出的isConnected数组来判断这个节点是否有与其他结点连接,再来判断是否要与其他结点在并查集中相连。这里我们就需要两个函数了,一个是添加函数,一个是连接函数。
添加函数中,每个节点刚刚加入并查集时其父节点应该都为空,直到之后的连接函数再将其与其他节点连接。
void add(int x){
        if (!father.count(x)){
            father[x] = -1;
            nums++;
        }
    }

每次有单独城市的省份出现计数nums就可以加一,之后连接时再减一即可。

在连接函数中,如果我们发现两个节点相通,也就是他们的祖先应该是相通的,我们需要再通过另一个find函数来寻找两个相通节点的祖先节点,如祖先节点相同即可合并连接。

void merge(int x,int y){
        int root_x = find(x);
        int root_y = find(y);
        if (root_x != root_y){
            father[root_x] = root_y;
            nums--;
        }
    }

find函数也比较简单,不断向上遍历父节点即可找到祖先节点了。

int root = x;
while (father[root] != -1){
            root = father[root];
        }

但是还是有可以优化的地方,如果我们的树很深的话,需要遍历很多次才能找到祖先节点,我们可以将一条树上所有节点的父节点全都指向祖先节点,以实现路径压缩使查找时间缩短。

while (x != root){
            int original_father = father[x];
            father[x] = root;
            x = original_father;
        }

注意前面已经遍历过一遍x,已经查找到祖先节点就为root。

这样最主要的三个函数就完成了,只需要在主函数中遍历每个节点将其加入并查集中,再按照isConnected数组中的连接情况来合并连接即可。

class UnionFinde{
public:
    int find(int x){
        int root = x;
        while (father[root] != -1){
            root = father[root];
        }
        while (x != root){
            int original_father = father[x];
            father[x] = root;
            x = original_father;
        }
        return root;
    }
    void merge(int x,int y){
        int root_x = find(x);
        int root_y = find(y);
        if (root_x != root_y){
            father[root_x] = root_y;
            nums--;
        }
    }
    void add(int x){
        if (!father.count(x)){
            father[x] = -1;
            nums++;
        }
    }
    int get_nums(){
        return nums;
    }
private:
    unordered_map<int, int> father;
    int nums = 0;
};
class Solution {
public:
    int findCircleNum(vector<vector<int>>& isConnected) {
        UnionFinde uf;
        for (int i = 0; i < isConnected.size(); i++){
            uf.add(i);
            for (int j = 0; j < i; j++){
                if (isConnected[i][j]){
                    uf.merge(i, j);
                }
            }
        }
        return uf.get_nums();
    }
};

797.所有可能得路径 题目描述:

给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序

 graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。

示例:

输入:graph = [[1,2],[3],[3],[]]
输出:[[0,1,3],[0,2,3]]
解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3

本题主要考察的还是深度优先遍历dfs的应用,也算是回溯算法的应用。

回溯算法主要的步骤就是:

1.确定终止条件;

2.递归运算;

3.回溯(返回到递归之前的状态)。

本题的终止条件很明显,就是要找到节点n – 1,只要递归到n – 1即可return

与以往做的回溯算法不太一样的是,这次要在数组表示的有向图上面进行遍历,所以我们首先要明确好这个递归的参数。graph[0]代表的就是节点0能够到达的节点,我们向graph[0]中的元素继续向下递归即可。这样一来我们可以将递归的参数决定为,这个graph数组以及其下标。

结果的储存我们需要用到一个二维数组result,还有一个记录路径的一维数组path,每次遍历到n – 1节点时就可以将path数组加入到result中了

vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<vector<int>>& g, int x){
        if (x == g.size() - 1){//遍历到n - 1节点
            result.push_back(path);
            return;
        }
        for (int i = 0; i < g[x].size(); i++){
            path.push_back(g[x][i]);
            backtracking(g, g[x][i]);
            path.pop_back();//进行回溯
        }
    }

这个回溯的操作在一开始接触时,会因为陌生而比较难以理解,就像上一题的并查集一样,但多训练几题就会比较清晰的感受到了。

最后就是调用回溯函数计算即可

vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
        path.push_back(0);
        backtracking(graph, 0);
        return result;
    }

今天其实还做了一道比较难的Dijkstra算法的题目,但感觉太难,要是今晚也写进来的话,又要写到很晚了,我感觉写日志这种事最好还是不要全堆到晚上的一整块时间来写,这样的话如果一写写太多了就消耗太长时间了,而且还影响休息。最好还是能够抽出零碎的时间来写写,但有时候就是到中午还没怎么刷题,零碎的时间也拿来玩手机了,所以现在留那么一道题,先消化消化,到明天再拿零碎的时间来写到日志里来。虽然一口气写完后可以听着网站放的小曲,惬意地看看自己的网站里自己写的东西,但为了不影响休息还是抽零碎时间来写吧。