【图论#01】邻接表的应用以及深度优先搜索

先说一个结论:图论没什么高级的

二叉树也是图的一种,你不是天天见吗。。。

当然,既然图论是一个比二叉树更大的概念,那么二者肯定还是有不同的,详见

不废话了直接上题

所有可能的路径

力扣题目链接(opens new window)

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

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

img

提示:

  • n == graph.length
  • 2 <= n <= 15
  • 0 <= graph[i][j] < n
  • graph[i][j] != i(即不存在自环)
  • graph[i] 中的所有元素 互不相同
  • 保证输入为 有向无环图(DAG)

在正式做题之前要先补充一些前置知识

知识点:邻接表

图论问题中,我们的操作对象是一个“图”,因此我们需要一个新的数据结构来存放“图”

二叉树可以用ListNode来表示,这是一种特例

邻接表就是一种解决方案

简单来说,邻接表就是一个二维数组,用于保存图中节点之间的关系,这种关系是固定的

为了方便理解,还是以树结构来举例

假设有以下一棵树:

   1
 / | \
2  3  4
  / \ 
 5   6

我们可以按照如下方式将这棵树存入graph中:

Codevector<vector<int>> graph;
graph.resize(7);  // 根据节点个数创建大小为7的二维向量

graph[1].push_back(2);  // 节点1的邻接节点有2
graph[2].push_back(1);  // 节点2的邻接节点有1
graph[1].push_back(3);  // 节点1的邻接节点有3
graph[3].push_back(1);  // 节点3的邻接节点有1
graph[1].push_back(4);  // 节点1的邻接节点有4
graph[4].push_back(1);  // 节点4的邻接节点有1
graph[3].push_back(5);  // 节点3的邻接节点有5
graph[5].push_back(3);  // 节点5的邻接节点有3
graph[3].push_back(6);  // 节点3的邻接节点有6
graph[6].push_back(3);  // 节点6的邻接节点有3

这样,我们就成功地将树的邻接关系存储在了graph中。例如,graph[1]表示节点1的邻接节点列表,其值为{2, 3, 4},表示节点1与节点2、3、4相邻。

接下来,我们可以使用graph中的数据进行相关操作。比如遍历图的邻接关系:

Codeint node = 1;  // 从根节点1开始遍历
for (int neighbor : graph[node]) {
    cout << "Node " << node << " is connected to Node " << neighbor << endl;
}

输出结果为:

1 is connected to Node 2
Node 1 is connected to Node 3
Node 1 is connected to Node 4

通过graph可以方便地访问每个节点的相邻节点

思路与代码

有了上述前置知识,现在我们可以使用图论的思想去进行深度优先搜索(dfs)

关于深度优先搜索dfs和广度优先搜索bfs以及它们和前/中/后序遍历的关系简单来说如下:

前/中/后序遍历是dfs在二叉树这种数据结构中的应用;

层序遍历则是bfs在二叉树中的应用。

代码风格和之前回溯的差不多:确定递归函数和参数、确定终止条件、处理单层逻辑

这里我还是像解释一下发生了什么,以官方示例来说

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

graph作为邻接表记录了图的每个节点之间的连接关系

二维数组graph中,每个元素都是一个列表,每个元素的下标对应着一个节点(一共有5个元素,对应从0到4的五个节点)

[4,3,1]代表节点0的连接关系:与节点0直接连接的有节点4、3、1(见示例图)
[3,2,4]代表节点1的连接关系:与节点1直接连接的有节点3、2、4
以此类推

所以邻接表其实是通过元素下标来对应节点,然后节点里面的值又作为索引从邻接表中寻找下一个节点

见下面的代码实现

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    void dfs(vector<vector<int>>& graph, int node){
        if(node == graph.size() - 1){//确定终止条件
            res.push_back(path);
            return;
        }

        //处理单层逻辑,通过传入的参数node确定要遍历的节点列表并开始遍历
        for(int i = 0; i < graph[node].size(); ++i){
            //取出节点列表中保存的graph[node][i]节点加入path中
            path.push_back(graph[node][i]);
            //以该节点作为下一层递归的输入参数
            dfs(graph, graph[node][i]);
            path.pop_back();
        }
    }
    vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
        path.push_back(0); // 无论什么路径已经是从0节点出发
        dfs(graph, 0);
        return res;
    }
};

关于ACM下的实际应用,详见https://www.cnblogs.com/DAYceng/p/17614609.html

posted @ 2023-08-10 17:29  dayceng  阅读(95)  评论(0编辑  收藏  举报