目录
图是一种相对复杂的数据结构由顶点和连接每队顶点的边构成的图形就是图。
1. 基础背景
假设你居住在旧金山,要从双子峰前往金门大桥。你想乘公交车前往,并希望换乘最少。可以做的公交车如下所示:
为了找到换乘最少的乘车路线,你将用什么样的算法?
一步就能到达金门大桥吗?下面突出了所有一步就能到达的地方。
金门大桥未突出,因此一步无法到达那里,两步能吗?
金门大桥也未突出,因此两步无法到达那里,三步呢?
金门大桥突出了!因此从双子峰出发,可以沿下面的路线三步到达金门大桥。
还有其他的路线,但是他们更远(需要四步)。这个算法发现,前往金门大桥的最短路径需要三步。这种问题被称为 最短路径问题(shortest-path problem)。你经常找出最短路径,这可能是前往朋友家的最短路径,也可能是国际象棋中把对方将死的最少步数。解决最短路径问题的算法被称为 广度优先搜索 。
要确定如何从双子峰前往金门大桥,需要两个步骤。
- 使用图来建立问题模型;
- 使用广度优先搜索解决问题。
下面介绍什么是图。
2. 图是什么?
图模拟了一组连接。例如,假设你与朋友玩牌,要模拟谁欠谁钱,可以像下面这样指出Alex欠Rama钱。
完整的欠钱图可能是类似于下面这样的。
(指出谁欠谁钱的图)
图由节点(node)和边(edge)组成。
就这么简单,图由节点和边组成。一个节点可能与众多节点直接相连,这些节点被称为邻居。 在前面的欠钱图中,Rama是Alex的邻居。Adit不是Alex的邻居,因为他们不直接相连。但Adit既是Rama的邻居,又是Tom的邻居。
图用于模拟不同的东西是如何相连的。
3. 实现图
首先,需要用代码来实现图。图由很多节点组成。
每个节点都与邻近节点相连,如何表示类似于“你→Bob”这样的关系呢?有一种结构能让你表示这种关系,它就是散列表。
散列表可以让你能够将键映射到值。 在这里,你将节点映射到其所有邻居。
表示这种关系的python实现也非常简单:
graph = {}
graph["you"] = ["alice", "bob", "claire"]
注意,“you”被映射到了一个数组,因此graph["you"]是一个数组,其中包括了“you”的所有邻居。
图不过是一系列的节点和边,因此在Python中,只需使用上述代码就可表示一个图。那像下面这样更大的图呢?
表示它的Python代码如下:
graph = {}
graph["you"] = ["alice", "bob", "claire"]
graph["bob"] = ["anuj", "peggy"]
graph["alice"] = ["peggy"]
graph["claire"] = ["thom", "jonny"]
graph["anuj"] = []
graph["peggy"] = []
graph["thom"] = []
graph["jonny"] = []
另外,键-值对的添加顺序重要吗?换言之,如果你这样编写代码:
graph["claire"] = ["thom", "jonny"]
graph["anuj"] = []
而不是这样:
graph["claire"] = ["thom", "jonny"]
graph["anuj"] = []
对结果会有什么影响吗?
实际上,对结果并不会有任何影响。散列表是无序的,因此添加键-值对的顺序无关紧要。
Anuj、 Peggy、 Thom和Jonny都没有邻居,这是因为虽然有指向他们的箭头,但没有从他们出发指向其他人的箭头。这被称为 有向图(directed graph),其中的关系是单向的。因此,Anuj是Bob的邻居,但Bob不是Anuj的邻居。无向图(undirected graph)没有箭头,直接相连的节点互为邻居。例如,下面两个图是等价的。