文章目录
1.图
图表示"多对多"的关系
- 图:由顶点(vertex)和顶点与顶点间的连接–边(edge)构成。
- 边:是一个顶点对,包括有向边、无向边(即双向边)、有向有权重、无向无权重
- 有向边:只一个顶点指向另一个顶点的边(单行线)
- 不考虑重边和自回路
- 邻接点:有边直接相连的顶点
- 出度:从一个顶点发出的边数
- 入度:指向一个顶点的边数
2. 图的数据结构
图只是一个抽象的辅助解决问题的概念,没有必要一定用以下的邻接表、邻接矩阵表示。
2.1 邻接矩阵
顶点从0 到n-1编号。如果是无权重图,矩阵中值为1,代表行顶点到列顶点的边,值为0,代表没有边;如果是有权重图,矩阵中不为0的值代表权重大小,值为0,代表没有边。
- 不允许自回路,对角线为0;
- 如果是无向边,矩阵对称,会浪费空间
- 数值表示为权重
- 方便找邻接点(扫描此顶点在矩阵中的一行和一列)
- 方便统计出度/入度(对于无向图,对应行非0元素;有向图:行非零数,出度,列非零数,入度)
- 浪费空间,存稀疏图(点多变量少)有大量无效元素;对稠密图很合算
- 浪费时间,统计稀疏图共有多少边
2.2 邻接表
- 顶点
class Node:
def __init__(self,name):
self.name = name
def getName(self):
return self.name
def __str__(self):
return self.name
- 边(一个顶点指向另一个顶点)
#无权重的边
class Edge:
def __init__(self,src,dest):
self.src = src
self.dest = dest
def getSource(self):
return self.src
def getDestination(self):
return self.dest
def __str__(self):
return self.src.getName() + '->' + self.dest.getName()
#有权重的边
class WeightedEdge(Edge):
def __init__(self,src,dest,weight=1.0):
self.src = src
self.dest = dest
self.weight = weight
def getWeight(self):
return self.weight
def __str__(self):
return self.src.getName() + '->(' + str(self.weight)+')'+self.dest.getName()
- 图(图由顶点,和边构成)
#单向图,边用字典结构
class DiGraph:
def __init__(self):
self.nodes=[]
self.edges = {}
def addNode(self,node):
if node in self.nodes:
raise ValueError("Dupicate node!")
else:
self.nodes.append(node)
self.edges[node] = []
def addEdge(self,edge):
src = edge.getSource()
dest = edge.getDestination()
if(src in self.nodes and dest in self.nodes):
self.edges[src].append(dest)
else:
raise ValueError("node not in graph")
def childrenOf(self,node):
return self.edges[node]
def hasNode(self,node):
return node in self.nodes
def __str__(self):
result =''
for src in self.nodes:
for dest in self.edges[src]:
result = result + src.getName() + '->' + dest.getName + '\n'
return result[:-1]
#无向图,是单向图的子类,即双向图
class Graph(DiGraph):
def addEdge(self,edge):
super.addEdge(edge)
newEdge = Edge(edge.getDestination(),edge.getSrc())
super.addEdge(newEdge)
- 在图中对每个顶点,存储一个连接顶点的列表,一定要够稀疏用邻接表表示才合算
- 找任意一个顶点的所有邻接点
- 节约稀疏图的空间
- 对于无向图,方便计算任一顶点的“度”;对有向图,只能计算“出度”,需要构造“逆邻接表”计算“入度”
3.连通图的概念
- 路径:两个顶点v、w,v到w的路径是一系列顶点的序列,序列中相邻顶点间都是邻接点。
- 路径的长度:无权图,是路径中的边数;有权图,是所有边的权重和
- 简单路径:路径中没有重复的顶点
- 回路:起点==终点
- 连通:如果顶点v到顶点w存在一条路径,那么v和w是连通的
- 连通图:图中任意两个顶点均连通
- 对于无向图
- 连通分量:当前无向图的极大连通子图
- 极大顶点数:再加一个顶点就不连通了
- 极大边数:包含子图中所有顶点相连的所有边
- 对于有向图
- 强连通:有向图中顶点v和w之间存在双向路径(例如环),则v和w是强连通的
- 强连通图:有向图中任意两顶点都强连通
- 弱联通图:有向图中将单向变为无向后任意两顶点都连通
- 强连通分量:有向图的极大强连通子图
4.图的遍历
如果图中的顶点都连通,没有单独的顶点
4.1 广度优先(Breadth first search, BFS)
从图的起点开始访问,依次访问起点的邻接点,再访问邻接点的邻接点。
但某些顶点间可能存在回路,我们如果一味的根据当前顶点的邻接点访问,有可能会出现循环,所以要检查一下当前顶点是否在路径中访问过。
from collections import deque
def BFS(graph, startNode):
initPath = deque([startNode])
pathDeque = deque([initPath])
while len(pathDeque) >0:
currPath = pathDeque.popleft()
lastNode = currPath[-1]
print(lastNode.name)
# 在待检查的路径中添加最后一个顶点的邻接点路径
for childNode in graph.childrenOf(lastNode):
# 如果最后一个顶点的邻接点没有在当前路径中遍历过(避免循环遍历)
if childNode not in currPath:
# 新添路径
newPath = currPath + deque([childNode])
pathDeque.append(newPath)
return None
如果用邻接表表示图,时间复杂度为O(N+E),N是顶点数,E是边数;用邻接矩阵表示,复杂度为O(N^2)
4.2 深度优先(depth first search, DFS)
如果当前顶点存在邻接点,访问第一个邻接点,再访问这个邻接点的第一个邻接点。如果不存在邻接点,回溯到父顶点,访问父顶点的定一个邻接点
def DFS(graph,start, path):
path = path + [start]
print(start)
for node in graph.childrenOf(start):
if node not in path:
DFS(graph,node,end,path,shortest)
非递归实现
def myDFS(graph,startNode):
initPath = deque([startNode])
pathDeque = deque([initPath])
while len(pathDeque) > 0:
currPath = pathDeque.popleft()
lastNode = currPath[-1]
# 为该节点的所有邻接点定义顺序
priorPath = deque([])
for childNode in graph.childrenOf(lastNode):
#如果当前邻接点没有在当前路径中走过
if childNode not in currPath:
newPath = currPath + deque([childNode])
priorPath.append(newPath)
pathDeque =priorPath + pathDeque
return bestPath
4.3不连通的图遍历
def ListVertext(graph ):
path = []
for node in graph.nodes:
//如果有点没有被访问过,以此点为起始点进行遍历
if node not in path:
DFS(graph, node, path)