图
图是研究离散对象的最常用和最基础的概念,这里我们暂不关注其应用,只看相关算法。
最简单的图如上所示,边只表示“两个点相连”,是“无权”、“无向”的。
另一方面,有些图的边是单向的(类似交通图中的单行道),这类图被称为有向图;另一些图图的边也可以加入权重,被称为有权图
储存方式
在数学上,图一般被表示为二元组
G
=
(
V
,
E
)
G=(V,E)
G=(V,E),
V
V
V表示节点,
E
E
E表示连接节点的边。(具体上怎么储存是很自由的)
从数据结构上来讲,图可以表示为:
class Graph:
n # 节点数量
val=[] # 节点的值
adj=[] # 储存任意两点是否相连
一般来讲,储存边有两种方法:
- 邻接矩阵:用一个
n
×
n
n\times n
n×n的矩阵
A
\bold A
A(其实就是二维数组)储存。
- 无权图: 当 u , v u,v u,v不相连时, A u , v = 0 \bold A_{u,v}=0 Au,v=0;反之, A u , v = 1 \bold A_{u,v}=1 Au,v=1
- 有权图:当 u , v u,v u,v不相连时, A u , v = inf \bold A_{u,v}=\text{inf} Au,v=inf;反之, A u , v = w \bold A_{u,v}=w Au,v=w; inf \text{inf} inf是一个大数,表示两节点距离无限远,而 w w w是边的权重
- 邻接表:用一个长度为 n n n的链表数组 A [ n ] \bold A[n] A[n]储存,只有当 u , v u,v u,v相连时,节点 v v v才插入 A [ u ] \bold A[u] A[u]中。
以邻接矩阵为例,写出图的构造方法:
(我们假设首先输入
n
,
k
n,k
n,k,分别表示节点数量和边的数量,接下来
k
k
k行,每行给出一条边连接的节点)
def __init__(self): # 当然这是一个无向无权图
self.n,k=map(int,input())
self.adj=[[0 for i in range(self.n)] for j in range(self.n)] # 将adj初始化为nxn的0矩阵
for i in range(k):
u,v=map(int,input())
self.adj[u][v]=self.adj[v][u]=1 # 注意无向图的矩阵是对称的
图的遍历
既然树是一种特殊的图,那么图也会有类似于有序遍历以及层次遍历的算法。图的遍历分为两类。
深度优先搜索DFS
DFS会不断移动到第一个它能访问的新节点,直到它到达一个不连接新节点的位置,此时它会回退,去访问下一个新节点。下图给了一个DFS访问顺序的例子。
可以感觉到,DFS和有序遍历非常类似,所以这个算法的实现也是我们熟悉的,只不过,由于图缺少天然的层次结构,我们不能根据节点的位置知道哪些节点访问过,因此,为了避免重复访问,我们需要用一个数组vis
来记录某个节点是否访问过。DFS的递归实现如下:(当然可以用栈)
vis
G
def dfs(int u): #
global vis,G
vis[u]=1 # 将u标记为访问过
print(u) # 这里可以执行任意操作
for v in range(G.n):
if vis[v]==0 and G.adj[u][v]==1: # 如果与u相连的节点v没访问过
dfs(v) # 移动到v,继续搜索
if __name__=="__main__":
G=Graph() # 构造一个图
vis=[0 for i in range(G.n)] # vis初值为0,表示所有节点都没访问过
for u in range(G.n): # 考虑这里为什么要循环
if vis[u]==0: # 如果节点u未访问过,则从u开始dfs
dfs(u)
广度优先搜索BFS
BFS会先记录当前位置能访问到的所有新节点,然后依次访问它们;在到达一个新节点时,算法会重复记录的过程,在访问了第一次记录的所有节点后,它会接着访问第二次记录的节点;直到访问了所有节点。
这和层次遍历非常类似,所以我们也可以用队列实现:
G=Graph()
queue=[]
vis=[0 for i in range(G.n)]
for x in range(G.n):
if vis[x]==0:
queue.push(x)
vis[x]=1
while len(queue) != 0:
u=queue.pop(0)
vis[u]=1
print(u) # 访问u
for v in range(G.n):
if vis[v] == 0 and G.adj[u][v] == 1:
queue.push(v)
接下来的部分会在算法部分讲
最小生成树
最短路算法
网络流
割顶
二分图
习题
基础
图
- 深入虎穴(凸的遍历)
- 路径和II(dfs)
- 网红点打卡攻略(最短路)
- 计算图(dfs)
- 二叉树中所有距离为k的节点(bfs)
- 最接近目标值的子序列和(二分法)