入门小菜鸟,希望像做笔记记录自己学的东西,也希望能帮助到同样入门的人,更希望大佬们帮忙纠错啦~侵权立删。
目录
一、图的相关概念
1、图
表示多对多的关系
一组顶点,通常用V(Vertex)表示顶点集合
一组边,通常用E(Edge)表示边的集合,边是顶点对,分为有向边和无向边
下图即为图。
2、无向图和有向图
🌳无向图:边没有方向的图。(即从A可以直接到B,B也可以直接到A)
🌳有向图:边有方向的图。(即从A可以直接到B,但B不能直接到A)
🌳入度:以顶点A为头的弧的数目称为A的入度。如上图所示:A的入度为0。
🌳出度:以顶点A为尾的弧的数目称为A的出度。如上图所示:A的出度为2。
3、邻接矩阵
存储着边的信息的矩阵,而顶点则用矩阵的下标表示,连通为1,不连通为0。
注:当边有权值时,1全部变为对应的权值。
如下图所示:邻接矩阵为:
0 | 1 | 2 | 3 | 4 | |
0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 1 | 1 | 0 |
2 | 1 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 1 |
4 | 1 | 0 | 0 | 0 | 0 |
4、邻接表和加权邻接字典
使用邻接矩阵实现图结构,能直观体现顶点之间的关系,也能很好地计算相应顶点的出度和入度,但对于无向图来说,邻接矩阵会同时保存两个顶点之间的两条边关系,会造成存储浪费。
所以,可以使用一维列表进行存储,减少一半的存储关系,使用邻接表的方式来实现图结构。
邻接表:针对每个节点设置一个邻接列表。
还是直接看例子吧。
邻接表如下:(a,b,c,d,e分别表示0,1,2,3,4)
a, b, c, d, e = list(range(8))
N = [
{},
{c, d},
{a},
{e},
{a},
]
如果边有权值的话,则多加上权值的表示,构成加权邻接字典。
二、图的遍历
1、DFS(深度优先搜索)
与树的先序遍历相同,DFS要设置相应的顶点访问标识,已经访问过的结点不再访问,实现过程最重要的是状态的回溯,一般使用递归。
简单来说就是一条道走到底(选择),不行就回去(回溯),走过的不再重复(状态标识)。
2、BFS(广度优先搜索)
与树的层序遍历相同,使用队列对顶点进行存储搜索,然后先进后出的对顶点的有效边顶点进行搜索,搜索过的就不再搜索。因此,要标识顶点的搜索状态。
三、python实现图的遍历(基于邻接表)
1、深度搜索(分析见注释)实现查找所有路径以及最短路径
首先深度搜索主体
def dfs(graph, path, end,results,w,weights): #找出从start到end的所有路径
s = path[-1]
if s == end: #判断是否走到指定位置
results.append(path) #添加路径
weights.append(w) #添加路径权重
return
for x in graph[ord(s) - 65]:
if x not in path: #走过的不再走
dfs(graph,path + [x],end,results,w + graph[ord(s) - 65][x],weights)
#这里注意不能path.append(x),因为path会跟着变
这里我们就得到所有路径和权值,接下来就对这些路径的长短进行一个排序,就可以找到最短路径。
def search(graph, path, end):
results = []
w = 0
weights = []
dfs(graph, path, end, results,w,weights)
a=sorted(enumerate(weights), key=lambda x:x[1]) #进行一个排序
print(results) #输出所有路径
print(results[a[0][0]]) #输出最短路径
具体调用
Graph = [{'B':3,'C':4},
{'A':1,'D':5},
{'A':6,'E':2},
{'A':2,'B':9,'C':4},
{'C':3,'D':5}]
r = search(Graph,['A'],'D')
输出
所有路径:[['A', 'B', 'D'], ['A', 'C', 'E', 'D']]
最短路径:['A', 'B', 'D']
2、广度搜索(分析见注释)遍历输出图的数据
广搜主体
from queue import Queue
def bfs(graph,start): #遍历图
queue = Queue()
search = [] #用来记录已经搜索过的
queue.put(start)
print(start)
search.append(start)
while not queue.empty():
s = queue.get()
for i in graph[ord(s) - 65]:
if i not in search:
queue.put(i)
print(i)
search.append(i)
具体调用
Graph = [{'B':3,'C':4},
{'A':1,'D':5},
{'A':6,'E':2},
{'A':2,'B':9,'C':4},
{'C':3,'D':5}]
bfs(Graph,'B')
输出
BADCE
四、最短路径Djkstra算法
由上面的最短路径算法补充Djkstra算法。
1、思路:
核心思想:贪心算法(即总是做出在当前看来是最好的选择)
不断地从start开始找路,分别找出start到各点距离最短的路径,不断更新。
具体流程如下图所示:
从1出发:
(1,2) | (1,3) | (1,4) | (1,5) | (1,6) | |
第一次 | 2 | 4 | ? | ? | ? |
第二次 | 2 | 4 | 5 | 8 | ? |
第三次 | 2 | 4 | 5 | 8 | 10 |
2、python实现Djkstra算法
分析请见注释
def dj(start, mgraph):
nopass = [x for x in range(len(mgraph)) if x != ord(start)-65]#初始未到达的各个点
dis = mgraph[ord(start) - 65].copy() #获取初始距离,并且不改变原来graph的值
path = []#记录路径
for i in range(len(mgraph)):
path.append([start])
#一个一个点突破
while len(nopass):
idx = nopass[0] #开始寻找第一个点
for i in list(dis.keys()):
i = ord(i) - 65
if i in nopass:
if chr(i+65) not in dis.keys():
dis.update({chr(i+65):100000000000})
if dis[chr(i+65)] < dis[chr(idx+65)]: #更新,找到前期,这是为了找出有没有比直接到这个点更近的可能,如果没有说明这个点目前的路径就是最短路径
idx = i
nopass.remove(idx)#通过该点,将他从搜索列表中去除
if path[idx] == [start]:
path[idx] = [start,chr(idx + 65)]#路径节点添加
flag = 1
for i in nopass: #找剩下没通过的点中有没有需要经过idx的
if chr(i+65) not in dis.keys():
dis.update({chr(i+65):100000000000})
if chr(i+65) in mgraph[idx].keys() and dis[chr(idx+65)] + mgraph[idx][chr(i+65)] < dis[chr(i+65)]:
flag = 0
dis[chr(i+65)] = dis[chr(idx+65)] + mgraph[idx][chr(i+65)]
if((flag == 0) & (path[idx] == [start])):
path[idx] = [start,chr(idx + 65)]
path[i] = [path[idx] ,chr(i+65)]
print(start)
print("path is ")
print(path)
print("dis is ")
print(dis)
print("\n")
Graph = [{'B':3,'C':4},
{'A':1,'D':5},
{'A':6,'E':2},
{'A':2,'B':9,'C':4},
{'C':3,'D':5}]
dj('B',Graph)
输出
B
path is
[['B', 'A'], ['B'], [['B', 'A'], 'C'], ['B', 'D'], [[['B', 'A'], 'C'], 'E']]
dis is
{'A': 1, 'D': 5, 'C': 5, 'E': 7}
欢迎大家在评论区批评指正,谢谢~