图的存储结构相对于线性表和树来说更为复杂,因为图中的顶点具有相对概念,没有固定的位置。图是由(V, E)来表示的,V是顶点的集合,E是边的集合。
图看起来就像下图这样:
图有各种形状和大小。边可以有权重(weight),即每一条边会被分配一个正数或者负数值。边是可以有方向的。在上面提到的例子中,边是没有方向的。
1.为什么要使用图?
例如,假设你有一系列任务需要完成,但是有的任务必须等待其他任务完成后才可以开始。你可以通过非循环有向图来建立模型:
每一个顶点代表一个任务。两个任务之间的边表示目的任务必须等到源任务完成后才可以开始(先决条件)。比如,在任务B和任务D都完成之前,任务C不可以开始。在任务A完成之前,任务B和D都不能开始。
现在这个问题就通过图描述清楚了,你可以使用深度优先搜索算法来执行执行拓扑排序。这样就可以将所有的任务排入最优的执行顺序,保证等待任务完成的时间最小化。(这里可能的顺序之一是:A, B, D, E, C, F, G, H, I, J, K)
2.图的存储结构
图的结构有多种形式,这里介绍两种常用的结构:邻接矩阵(数组表示法)和邻接表
(1)邻接矩阵
用两个数组分别存储数据元素信息和数据元素之间的关系信息。无向图和邻接矩阵如下:
0代表两点之间没有关联;1代表有关联
需要注意的是,当边上有权值的时候,称之为网图。邻接矩阵中的元素是两点间的权值,若两点间没有联系,元素为∞
代码实现
class Graph:
def __init__(self, gdict={}):
self.gdict = gdict
# 获取所有结点
def Node(self):
return list(self.gdict.keys())
# 计算出邻接矩阵(二维数组)
def Matrix(self):
binary_matrix = []
node = self.Node()
for v1 in node:
binary_matrix.append([])
for v2 in node:
if v2 in self.gdict[v1]:
binary_matrix[-1].append(1)
else:
binary_matrix[-1].append(0)
return binary_matrix
(2)邻接表
邻接表是图的一种链式存储结构。在邻接表中对图的每个顶点建立一个单链表。 左边的节点称为顶点节点,其结构体包含顶点元素和指向第一条边的指针;右边的为边节点。 对于有权值的网图,只需要在边节点增加一个权值的成员变量即可。
代码实现
class Graph:
def __init__(self, gdict={}):
self.gdict = gdict
# 生成N个链表
def LinkedList(self):
# 链表的数据结构
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
# 填充每一个链表
res = []
node = list(self.gdict.keys())
for v1 in node:
temp = ListNode(v1)
cur = temp
for v2 in node:
if v2 in self.gdict[v1]:
cur.next = ListNode(v2)
cur = cur.next
res.append(temp)
return res
*3.拓扑排序
针对于DAG图(有向无环图)。按照某种规则将这个图的顶点取出来,这些顶点能够表示什么或者有什么联系。 顶点的顺序是保证所有指向它的下个节点在被指节点前面! 拓扑排序序列不一定唯一! 「拓扑排序」的一个附加效果是:能够顺带检测有向图中是否存在环
步骤:
-
从DGA图中找到一个没有前驱的顶点输出。(可以遍历,也可以用优先队列维护)
-
删除以这个点为起点的边。(它的指向的边删除,为了找到下个没有前驱的顶点)
-
重复上述,直到最后一个顶点被输出。如果还有顶点未被输出,则说明有环!
代码实现
class Graph:
def __init__(self, gdict={}):
self.gdict = gdict
self.linkedlist = self.LinkedList()
# 生成图的邻接表结构
def LinkedList(self):
class ListNode: # 链表结构
def __init__(self, x):
self.val = x
self.next = None
links = []
node = list(self.gdict.keys()) # 图的原始数据是以“前驱”为键,“后继”为值的哈希表
for v1 in node:
temp = ListNode(v1)
cur = temp
for v2 in node:
if v2 in self.gdict[v1] and v1 != v2:
cur.next = ListNode(v2)
cur = cur.next
links.append(temp)
return links
# 拓扑排序(用邻接表排序)
def TopoSort(self):
indegree = {} # 存储每个结点的入度的哈希表
edge = {} # 存储每个结点的后继(也就是结点的关系)
# “拆解”每个链表,填充 入度哈希表 和 后继哈希表
for link in self.linkedlist:
node = link.val
edge[node] = []
if not indegree.get(link.val):
indegree[link.val] = 0
link = link.next
while link: # 表头之后
edge[node].append(link.val)
if indegree.get(link.val):
indegree[link.val] += 1
else:
indegree[link.val] = 1
link = link.next
# 下面就是针对邻接表的拓扑排序
res = []
while indegree:
for n in indegree:
if indegree[n] == 0: # 找到度为0的结点
res.append(n)
for e in edge[n]:
indegree[e] -= 1
del indegree[n]
break
else: # 遍历入度哈希表后没有找到入度为0的结点,则一定存在环
return "别TM搞笑了,这是个带环图!"
return res
'''
graph = {
"a": {"b"},
"b": {"c"},
"c": {"d"},
"d": {"e"},
"e": {}
}
obj = Graph(graph)
out = obj.TopoSort()
print(out)
'''
*4.关键路径