图的基本介绍
- 为什么要有图
- 图的举例说明
- 图的常用概念
图的表示方式
- 邻接矩阵
- 邻接表
图的入门 代码实现
- 要求:用代码实现下面你的图结构
- 用邻接矩阵实现
class Graph(object):
def __init__(self, n): # 传入顶点个数
self.vertex_list = [] # 存储顶点的集合
self.edges = [[0 for i in range(n)] for j in range(n)] # 存储图对应的邻接矩阵
self.num_of_edges = 0 # 表示边的数目
# 返回顶点的个数
def get_num_of_vertex(self):
return len(self.vertex_list)
# 得到边的数目
def get_num_of_edge(self):
return self.num_of_edges
# 返回结点i(下标)对应的数据:0->"A" 1->"B" 2->"C"
def get_val_by_index(self, index):
return self.vertex_list[index]
# 返回v1和v2的权值
def get_weight(self, v1, v2):
return self.edges[v1][v2]
# 显示图对应的矩阵
def show_graph(self):
for col in self.edges:
print(col)
# 插入顶点
def insert_vertex(self, vertex):
self.vertex_list.append(vertex)
# 添加边
def insert_edge(self, v1, v2, weight):
'''
:param v1: 表示顶点的下标,即是第几个顶点 “A”和“B”,“A”->0 "B"->1
:param v2: 表示第二个顶点对应的下标
:param weight: 表示权值(定义1 表示能直接连接 0 表示不能直接连接)
:return:
'''
# 因为是无向的
self.edges[v1][v2] = weight
self.edges[v2][v1] = weight
self.num_of_edges += 1
if __name__ == '__main__':
n = 5 # 顶点个数
vertex_val = ["A", "B", "C", "D", "E"]
# 创建图对象
g = Graph(n)
# 循环的添加顶点
for val in vertex_val:
g.insert_vertex(val)
# 添加边
# A-B;A-C;B-C;B-D;B-E (无向的,五条边即可)
g.insert_edge(0, 1, 1) # A-B
g.insert_edge(0, 2, 1)
g.insert_edge(1, 2, 1)
g.insert_edge(1, 3, 1)
g.insert_edge(1, 4, 1)
# 显示邻接矩阵
g.show_graph()
'''输出结果
[0, 1, 1, 0, 0]
[1, 0, 1, 1, 1]
[1, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
'''
图的深度优先遍历(DFS)
- 图的深度优先遍历介绍
- Python实现
class Graph(object):
def __init__(self, n): # 传入顶点个数
self.vertex_list = [] # 存储顶点的集合
self.edges = [[0 for i in range(n)] for j in range(n)] # 存储图对应的邻接矩阵
self.num_of_edges = 0 # 表示边的数目
# 定义数组boolean_array,记录某个结点是否被访问过
self.is_visited = [0] * n
# 得到第一个邻接顶点的下标 w
def get_first_neighbor(self, index):
'''
:param index: 传入第几个顶点
:return: 如果存在就返回对应的下标,否则返回-1
'''
for j in range(len(self.vertex_list)):
if self.edges[index][j] > 0: # 说明下一个邻接结点存在
return j
return -1
# 根据前一个邻接结点的下标来获取下一个邻接结点
def get_next_neighbor(self, v1, v2):
for j in range(v2 + 1, len(self.vertex_list)):
if self.edges[v1][j] > 0:
return j
return -1
# 深度优先遍历算法
# i 第一次就是0
def dfs(self, is_visited, i):
# 首先访问该结点,输出
print(self.get_val_by_index(i) + "->", end=" ")
# 将整个结点设置为已经访问过
self.is_visited[i] = True
# 查找结点i的第一个结点是否存在w
w = self.get_first_neighbor(i)
while w != -1: # 说明找到了下一个邻接结点
if not self.is_visited[w]: # 如果w没有被访问过
self.dfs(is_visited, w)
# 如果w结点被访问过
w = self.get_next_neighbor(i, w)
# 对dfs 遍历所有的结点,并进行dfs
def dfs_override(self):
# 遍历所有的结点,进行dfs【回溯】
for i in range(self.get_num_of_vertex()):
if not self.is_visited[i]: # 如果当前结点没走过,提高效率
self.dfs(self.is_visited, i)
# 返回顶点的个数
def get_num_of_vertex(self):
return len(self.vertex_list)
# 得到边的数目
def get_num_of_edge(self):
return self.num_of_edges
# 返回结点i(下标)对应的数据:0->"A" 1->"B" 2->"C"
def get_val_by_index(self, index):
return self.vertex_list[index]
# 返回v1和v2的权值
def get_weight(self, v1, v2):
return self.edges[v1][v2]
# 显示图对应的矩阵
def show_graph(self):
for col in self.edges:
print(col)
# 插入顶点
def insert_vertex(self, vertex):
self.vertex_list.append(vertex)
# 添加边
def insert_edge(self, v1, v2, weight):
'''
:param v1: 表示顶点的下标,即是第几个顶点 “A”和“B”,“A”->0 "B"->1
:param v2: 表示第二个顶点对应的下标
:param weight: 表示权值(定义1 表示能直接连接 0 表示不能直接连接)
:return:
'''
# 因为是无向的
self.edges[v1][v2] = weight
self.edges[v2][v1] = weight
self.num_of_edges += 1
if __name__ == '__main__':
n = 5 # 顶点个数
vertex_val = ["A", "B", "C", "D", "E"]
# 创建图对象
g = Graph(n)
# 循环的添加顶点
for val in vertex_val:
g.insert_vertex(val)
# 添加边
# A-B;A-C;B-C;B-D;B-E (无向的,五条边即可)
g.insert_edge(0, 1, 1) # A-B
g.insert_edge(0, 2, 1)
g.insert_edge(1, 2, 1)
g.insert_edge(1, 3, 1)
g.insert_edge(1, 4, 1)
# 显示邻接矩阵
g.show_graph()
print("深度优先遍历:")
g.dfs_override()
'''输出结果
[0, 1, 1, 0, 0]
[1, 0, 1, 1, 1]
[1, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
深度优先遍历:
A-> B-> C-> D-> E->
'''
图的广度优先遍历(BFS)
- 广度优先遍历的思想
- 广度优先遍历算法步骤
- Python实现
class Graph(object):
def __init__(self, n): # 传入顶点个数
self.vertex_list = [] # 存储顶点的集合
self.edges = [[0 for i in range(n)] for j in range(n)] # 存储图对应的邻接矩阵
self.num_of_edges = 0 # 表示边的数目
# 定义数组boolean_array,记录某个结点是否被访问过
self.is_visited = [0] * n
# 得到第一个邻接顶点的下标 w
def get_first_neighbor(self, index):
'''
:param index: 传入第几个顶点
:return: 如果存在就返回对应的下标,否则返回-1
'''
for j in range(len(self.vertex_list)):
if self.edges[index][j] > 0: # 说明下一个邻接结点存在
return j
return -1
# 根据前一个邻接结点的下标来获取下一个邻接结点
def get_next_neighbor(self, v1, v2):
for j in range(v2 + 1, len(self.vertex_list)):
if self.edges[v1][j] > 0:
return j
return -1
# 对一个结点进行广度优先遍历
def bfs(self, is_visited, i):
# u 表示队列的头结点对应的下标
# w 表示邻接结点w
# 新建一个队列,记录结点访问的顺序
queue = []
# 首先访问该结点,输出
print(self.get_val_by_index(i) + "->", end=" ")
# 将整个结点设置为已经访问过
self.is_visited[i] = True
# 将结点加入队列
queue.append(i)
while queue:
# 取出队列的头结点下标
u = queue.pop(0)
# 得到第一个邻结点的下标w
w = self.get_first_neighbor(u)
while w != -1:
# 再判断是否访问过
if not is_visited[w]:
# 输出该当前结点
print(self.get_val_by_index(w) + "->", end=" ")
# 将该结点设为为已经访问
self.is_visited[w] = True
# 将结点加入队列
queue.append(w)
# 如果结点被访问过
# 以u为前驱出发点,找w后面的下一个邻结点
w = self.get_next_neighbor(u, w) # 体现出广度优先
# 遍历所有的结点,都进行广度优先遍历
def bfs_override(self):
for i in range(self.get_num_of_vertex()):
if not self.is_visited[i]:
self.bfs(self.is_visited, i)
# 返回顶点的个数
def get_num_of_vertex(self):
return len(self.vertex_list)
# 得到边的数目
def get_num_of_edge(self):
return self.num_of_edges
# 返回结点i(下标)对应的数据:0->"A" 1->"B" 2->"C"
def get_val_by_index(self, index):
return self.vertex_list[index]
# 返回v1和v2的权值
def get_weight(self, v1, v2):
return self.edges[v1][v2]
# 显示图对应的矩阵
def show_graph(self):
for col in self.edges:
print(col)
# 插入顶点
def insert_vertex(self, vertex):
self.vertex_list.append(vertex)
# 添加边
def insert_edge(self, v1, v2, weight):
'''
:param v1: 表示顶点的下标,即是第几个顶点 “A”和“B”,“A”->0 "B"->1
:param v2: 表示第二个顶点对应的下标
:param weight: 表示权值(定义1 表示能直接连接 0 表示不能直接连接)
:return:
'''
# 因为是无向的
self.edges[v1][v2] = weight
self.edges[v2][v1] = weight
self.num_of_edges += 1
if __name__ == '__main__':
n = 5 # 顶点个数
vertex_val = ["A", "B", "C", "D", "E"]
# 创建图对象
g = Graph(n)
# 循环的添加顶点
for val in vertex_val:
g.insert_vertex(val)
# 添加边
# A-B;A-C;B-C;B-D;B-E (无向的,五条边即可)
g.insert_edge(0, 1, 1) # A-B
g.insert_edge(0, 2, 1)
g.insert_edge(1, 2, 1)
g.insert_edge(1, 3, 1)
g.insert_edge(1, 4, 1)
# 显示邻接矩阵
g.show_graph()
print("广度优先遍历:")
g.bfs_override()
'''输出结果
[0, 1, 1, 0, 0]
[1, 0, 1, 1, 1]
[1, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
广度优先遍历:
A-> B-> C-> D-> E->
'''
深度优先遍历和广度优先遍历的区别
- 上面代码实现的例子,因为图非常简单,所有看不太出两种遍历方式的区别,于是看一个新的例子
class Graph(object):
def __init__(self, n): # 传入顶点个数
self.vertex_list = [] # 存储顶点的集合
self.edges = [[0 for i in range(n)] for j in range(n)] # 存储图对应的邻接矩阵
self.num_of_edges = 0 # 表示边的数目
# 定义数组boolean_array,记录某个结点是否被访问过
self.is_visited = [0] * n
# 得到第一个邻接顶点的下标 w
def get_first_neighbor(self, index):
'''
:param index: 传入第几个顶点
:return: 如果存在就返回对应的下标,否则返回-1
'''
for j in range(len(self.vertex_list)):
if self.edges[index][j] > 0: # 说明下一个邻接结点存在
return j
return -1
# 根据前一个邻接结点的下标来获取下一个邻接结点
def get_next_neighbor(self, v1, v2):
for j in range(v2 + 1, len(self.vertex_list)):
if self.edges[v1][j] > 0:
return j
return -1
# 深度优先遍历算法
# i 第一次就是0
def dfs(self, is_visited, i):
# 首先访问该结点,输出
print(self.get_val_by_index(i) + "->", end=" ")
# 将整个结点设置为已经访问过
self.is_visited[i] = True
# 查找结点i的第一个结点是否存在w
w = self.get_first_neighbor(i)
while w != -1: # 说明找到了下一个邻接结点
if not self.is_visited[w]: # 如果w没有被访问过
self.dfs(is_visited, w)
# 如果w结点被访问过
w = self.get_next_neighbor(i, w)
# 对dfs 遍历所有的结点,并进行多少次 dfs
def dfs_override(self):
# 遍历所有的结点,进行dfs【回溯】
for i in range(self.get_num_of_vertex()):
if not self.is_visited[i]: # 如果当前结点没走过,提高效率
self.dfs(self.is_visited, i)
# 对一个结点进行广度优先遍历
def bfs(self, is_visited, i):
# u 表示队列的头结点对应的下标
# w 表示邻接结点w
# 新建一个队列,记录结点访问的顺序
queue = []
# 首先访问该结点,输出
print(self.get_val_by_index(i) + "->", end=" ")
# 将整个结点设置为已经访问过
self.is_visited[i] = True
# 将结点加入队列
queue.append(i)
while queue:
# 取出队列的头结点下标
u = queue.pop(0)
# 得到第一个邻结点的下标w
w = self.get_first_neighbor(u)
while w != -1:
# 再判断是否访问过
if not is_visited[w]:
# 输出该当前结点
print(self.get_val_by_index(w) + "->", end=" ")
# 将该结点设为为已经访问
self.is_visited[w] = True
# 将结点加入队列
queue.append(w)
# 如果结点被访问过
# 以u为前驱出发点,找w后面的下一个邻结点
w = self.get_next_neighbor(u, w) # 体现出广度优先
# 遍历所有的结点,都进行广度优先搜索
def bfs_override(self):
self.is_visited=[0] * n # 为了测试广度优先遍历,先把数组置空
for i in range(self.get_num_of_vertex()):
if not self.is_visited[i]:
self.bfs(self.is_visited, i)
# 返回顶点的个数
def get_num_of_vertex(self):
return len(self.vertex_list)
# 得到边的数目
def get_num_of_edge(self):
return self.num_of_edges
# 返回结点i(下标)对应的数据:0->"A" 1->"B" 2->"C"
def get_val_by_index(self, index):
return self.vertex_list[index]
# 返回v1和v2的权值
def get_weight(self, v1, v2):
return self.edges[v1][v2]
# 显示图对应的矩阵
def show_graph(self):
for col in self.edges:
print(col)
# 插入顶点
def insert_vertex(self, vertex):
self.vertex_list.append(vertex)
# 添加边
def insert_edge(self, v1, v2, weight):
'''
:param v1: 表示顶点的下标,即是第几个顶点 “A”和“B”,“A”->0 "B"->1
:param v2: 表示第二个顶点对应的下标
:param weight: 表示权值(定义1 表示能直接连接 0 表示不能直接连接)
:return:
'''
# 因为是无向的
self.edges[v1][v2] = weight
self.edges[v2][v1] = weight
self.num_of_edges += 1
if __name__ == '__main__':
# n = 5 # 顶点个数
n = 8 # 测试更复杂的顶点个数
# vertex_val = ["A", "B", "C", "D", "E"]
vertex_val = ['1', '2', '3', '4', '5', '6', '7', '8']
# 创建图对象
g = Graph(n)
# 循环的添加顶点
for val in vertex_val:
g.insert_vertex(val)
# 添加边
# A-B;A-C;B-C;B-D;B-E (无向的,五条边即可)
# g.insert_edge(0, 1, 1) # A-B
# g.insert_edge(0, 2, 1)
# g.insert_edge(1, 2, 1)
# g.insert_edge(1, 3, 1)
# g.insert_edge(1, 4, 1)
# -----更新更复杂的边----
g.insert_edge(0, 1, 1)
g.insert_edge(0, 2, 1)
g.insert_edge(1, 3, 1)
g.insert_edge(1, 4, 1)
g.insert_edge(3, 7, 1)
g.insert_edge(4, 7, 1)
g.insert_edge(2, 5, 1)
g.insert_edge(2, 6, 1)
g.insert_edge(5, 6, 1)
# 显示邻接矩阵
g.show_graph()
print("深度优先遍历:")
g.dfs_override()
print()
print("广度优先遍历:")
g.bfs_override()
# 结果分别为:
'''输出结果
[0, 1, 1, 0, 0, 0, 0, 0]
[1, 0, 0, 1, 1, 0, 0, 0]
[1, 0, 0, 0, 0, 1, 1, 0]
[0, 1, 0, 0, 0, 0, 0, 1]
[0, 1, 0, 0, 0, 0, 0, 1]
[0, 0, 1, 0, 0, 0, 1, 0]
[0, 0, 1, 0, 0, 1, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0]
深度优先遍历:
1-> 2-> 4-> 8-> 5-> 3-> 6-> 7->
广度优先遍历:
1-> 2-> 3-> 4-> 5-> 6-> 7-> 8->
'''
- 对比两者之间的路径
(1)深度优先遍历
(2)广度优先遍历