此篇文章讲解了图的两种数据结构,怎样构造图,和图遍历的模板。
由此引出leetcode 797所有可能通路 ,leetcode 207 课程表 ,leetcode 210 课程表II的解答。
解决了图问题中常出现:DFS遍历图,回溯法遍历图,环检测,拓扑排序的问题。
目录
四、图的遍历和回溯两种方法解决 leetcode 797 所有可能的路径
一、 图的基础
(1)图有两种数据结构构成。
1)邻接矩阵 :即用V-V矩阵来代表每个顶点和边,V代表顶点。如果是无加权图则用简单的二维布尔数组表示。加权图则可以用二维数组来表示边的距离。它的好处是访问很快,缺点是费空间。如果图有很多顶点但是边不多,则造成不必要的空间损失。这时候,接邻列表就很有优势了。
2)接邻列表:每个顶点接一个列表,代表相邻的顶点。这样空间使用就从上一个表示方法 V * V
降到了 V + E. 每次添加边,提供了固定的时间。 同时节约了空间。
(2)leetcode中图常用的构造方法
leetcode中使用邻接表的构图比较多,以下是建立图的函数。
List<Integer>[] buildGraph(int numVertex, int[][] edges){
List<Integer>[] graph = new LinkedList[numCourses];
for(int[] edge : edges){
int from = edge[0];
int to = edge[1];
graph[from].add(to);
}
(3)图的遍历方法
图的数据结构就是多叉树的延伸,多叉树就是二叉树的延伸,二叉树就是链表的延伸。我们二叉树的数据结构中也有两种:一是用数组表示的堆,二是更加常见的用列表表示的树。因为leetcode中更加常出现的结构是邻接列表,也就是底层数据结构是列表。所以与二叉树的遍历存在着非常高的相似性。我们一起来看一下。
二叉树的遍历:
class TreeNode{
int val;
TreeNode leftchild;
TreeNode rightchild;
}
traverse(TreeNode root){
if(root == null) return;
//前序遍历
traverse(root.leftchild);
//中序遍历
traverse(root.rightchild);
//后序遍历
}
与多叉树的遍历十分的类似。多叉树的遍历框架:
class TreeNode{
int val;
TreeNode[] children;
}
traverse(TreeNode root){
if(root == null) return;
for(TreeNode node : children){
traverse(node);
}
}
图和多叉树不同之处在于,图可能有环的。你从图的一个节点开始遍历,很可能又会到这个节点。所以我们会加入visit[] 数组来避免你走回头路:
class Vertex{
int val;
Vertex[] neighbours;}
void traverse(Graph g, Vertex s){
if(visit[s]) return
// 将当前节点 s 标记为已经遍历
visit[s] = true;
for(Vertex v : g.neighbours(s)){
traverse(g, v)
}
}
那有些图不是所有的节点都是相连怎么办呢?加一个for循环将所有节点都作为起点都调用一下traverse()函数就可以了。如下图:
class Vertex{
int val;
Vertex[] neighbours;}
void traverseAllGraph(Graph g, int v){
visited = new boolean[v];
for(int i=0; i< v; i++){
traverse(g,i);
}
}
void traverse(Graph g, Vertex s){
if(visit[s]) return
// 将当前节点 s 标记为已经遍历
visit[s] = true;
for(Vertex v : g.neighbours(s)){
traverse(g, v)
}
}
我想知道哪里有环怎么办呢?这就引出了判断有向图是否存在环的问题。
递归函数看成一个在递归树上游走的指针的话,traverse就是在图上游走的指针,只需要在添加一个bool