note-3

  图是研究离散对象的最常用和最基础的概念,这里我们暂不关注其应用,只看相关算法。
note-3_第1张图片
  最简单的图如上所示,边只表示“两个点相连”,是“无权”、“无向”的。
  另一方面,有些图的边是单向的(类似交通图中的单行道),这类图被称为有向图;另一些图图的边也可以加入权重,被称为有权图
note-3_第2张图片note-3_第3张图片

储存方式

  在数学上,图一般被表示为二元组 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 uv不相连时, 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 uv不相连时, 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 uv相连时,节点 v v v才插入 A [ u ] \bold A[u] A[u]中。

以邻接矩阵为例,写出图的构造方法:
(我们假设首先输入 n , k n,k nk,分别表示节点数量和边的数量,接下来 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访问顺序的例子。
note-3_第4张图片
  可以感觉到,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会先记录当前位置能访问到的所有新节点,然后依次访问它们;在到达一个新节点时,算法会重复记录的过程,在访问了第一次记录的所有节点后,它会接着访问第二次记录的节点;直到访问了所有节点。
note-3_第5张图片
这和层次遍历非常类似,所以我们也可以用队列实现:

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)

接下来的部分会在算法部分讲

最小生成树

最短路算法

网络流

割顶

二分图

习题

基础

  • 程序运行时间 (处理输入,计算)
  • 查验身份证(特判字符,处理输入)
  • 说反话(字符串处理,c++可以学下cin怎么用)
  • 字符统计 (字符串处理,映射)

  • 深入虎穴(凸的遍历)
  • 路径和II(dfs)
  • 网红点打卡攻略(最短路)
  • 计算图(dfs)
  • 二叉树中所有距离为k的节点(bfs)
  • 最接近目标值的子序列和(二分法)

你可能感兴趣的:(算法,笔记)