图的逻辑结构和具体实现
一幅图由节点和边组成,逻辑结构如下:
我们用邻接表和邻接矩阵表示图。
# 邻接表
graph = [[4,3,1],[3,2,4],[3],[4],[]]
# 邻接矩阵
matrix = [
[False, True, False, True, True],
[False, False, True, True, True],
[False, False, False, True, False],
[False, False, False, False, True],
[False, False, False, False, False]]
// 邻接表
int[][] graph = {{4,3,1},{3,2,4},{3},{4},{}};
// 邻接矩阵
int[][] matrix = {
{false,true,false,true,true},
{false,false,true,true,true},
{false,false,false,true,false},
{false,false,false,false,true},
{false,false,false,false,false}};
邻接表占用的空间少,邻接矩阵可以快速判断两个节点是否相邻。
其他图的模型如加权图、无向图等都是基于有向无权图衍生出来的。
有向加权图的邻接表,不仅仅存储某个节点x的所有邻居节点,还存储x到邻居的权重。邻接矩阵中matrix[x][y]不再是布尔值,而是一个int值,0表示没有连接,其他值表示权重。
无向图其实就是双向图。
图的遍历
图的遍历参考多叉树,多叉树的遍历框架如下:
def traverse(TreeNode root):
if not root:
return
for child in root.children:
traverse(child)
}
图和多叉树的最大区别就是,图可能包含环,从图的某一个节点开始遍历,有可能走了一圈又回到这个节点。
所以,如果图包含环,遍历框架就要一个visited数组进行辅助:
# 图遍历框架
graph = [[4,3,1],[3,2,4],[3],[4],[]]
visited = [False for i in range(5)]
def traverse(graph,s):
if visited[s]:
return
visited[s] = True
print('enter: ',s)
for neighbor in graph[s]:
traverse(graph,neighbor)
visited[s] = False
print('leave: ',s)
traverse(graph,0)
这个框架和回溯算法的框架类似,区别在于回溯算法的「做选择」和「撤销选择」在for循环里,而对visited数组的操作在for循环外。
在for循环里面和外面唯一的区别就是对根节点的处理,在for循环外面会记录所有节点的进入和离开信息,在for里面会忽略根节点的进入和离开信息。
回溯算法关注的不是节点,而是树枝,所以可以忽略根节点。
对于图的遍历,应该把visited的操作放到for循环外面,否则会漏掉起始点的遍历。