本节课是《进击的Java新人》的第二十一周第一课。
我从第十五周到第二十周先空出来了,是准备讲多线程和并发编程的。这一部分我会加快速度,我先把后面的课程往外发一下。
图算法,在实际的编程中还是会经常遇到的,最主要的就是两种图的遍历算法。举个例子,Java程序员必须掌握的垃圾回收(Garbage Collection,GC)里面,就非常依赖图算法。所以,在讨论GC之前,我们必须先把图的知识掌握了。
如果有一组数据,它们之间的任意两个元素之间都可以存在联系。我们把存在联系的元素以线段相连接,称这些元素为顶点,这些线段为边。这样得到的数据结构就是图。图的结构广泛地存在于电讯,物理,逻辑学等学科中。在大学的离散数学课程中介绍了很多图论的结论和定理。与离散数学中的图论不同的是,在计算机领域,更关注的是如何在计算机上实现图的操作。
像所有的数据结构一样,在讨论具体的操作之前,必须先定义图和它的表达形式。
图中的概念
在图中的数据元素通常称为结点,V是所有顶点的集合,E是所有边的集合。如果两个顶点v, w,只能由v向w,而不能由w向v,那么我们就把这种情况叫做一个从 v 到 w 的有向边。v 也被称做初始点,w也被称为终点。这种图就被称做有向图。
如果v和w是没有顺序的,从v到达w和从w到达v是完全相同的,这种图就被称为无向图。
假如,图中的结点个数为n,那么在一个无向图,假设每对结点之间只能有一条边,那么这个无向图中的边数最多只有
。边数最多的无向图也叫完全图。
同样的,对于有向图,边数最多有n(n-1)个,边数最多的有向图叫有向完全图。
如果图中的边数很少,这种图也被叫稀疏图。稀疏图是一种大约的概念,并没有一定的数值,说边数低于这个数值的时候就是稀疏图,在不同的场景下,稀疏图的定义会不一样。反之,如果边数比较多,就称为稠密图。
有时图的边具有与它相关的数,这种与图的边相关的数就叫做权。例如,从A地到B地的车票是80块钱,那这个80就是这条路上的耗费,我们如果把这样的交通网做成一个图,就变成了一个带权有向图。这种图也被称为网络。
一个顶点如果有多个边与其相联,那么这些相联的边数就称为点的度。如果是有向图,那么初始点就有出度,而终点则有入度。
邻接矩阵
图的结构比较复杂,任意两个顶点之间都可能存在联系,因此无法以数据元素在存储区中的物理位置来表示元素之间的关系,换言之,图没有顺序映像的存储结构,但我们可以使用二维数组来表达元素与元素之间的关系。
对于图G,假如其中有n个结点,我们可以定义一个二维数组A[n][n],如果顶点
和顶点
之间存在边
,那么就将A[i][j]设为1,否则设为0。就称二维数组A是图G的邻接矩阵。可见,如果图G是无向图,那么,如果A[i][j]为1,可以推知A[j][i]也为1。如果G是有向图,则不存在这个规律。
如果G的边上有权重,如图所示,图G是一个带权有向图,顶点
和顶点
之间存在边
,且
的权重为
,就令A[i][j]为
。如果两个顶点
和
之间,不存在边,那么就记A[k][l]为无穷大。在实际的实现中可以使用整型的最大值代替。
邻接表
另外一种比较直接的思路就是使用多重链表。它是一种最简单的链式映象结构,即以一个由一个数据域和多个指针域组成的结点表示图中一个顶点,其中数据域存储该顶点的信息,指针域存储指向其邻接点的指针。这样做的一个缺点是,由于图中各个结点的度数各不相同,最大度数和最小度数可能相差很多,因此,若按度数最大的顶点设计结点结构,则会浪费很多存储单元。而如果按照每个顶点自己的度数设计不同的结点结构,所带来的编程的复杂度得不偿失。
所以,我们就用一种改进的方案:使用链表代表一个结点,这种方案被称为邻接表。在邻接表中,为图中的每一个顶点都建立一个链表,第i个单链表中的结点表示依附于顶 点vi的边。链表中的每个结点都由3个域组成,其中邻接点域表示与vi 相邻的点,例如与vi相 邻的vj,这个结点就是vj;链(nextArc)代表顶点i与顶点j之间的边eij;数据域(info)存储和边 相关的信息,例如权重等。
为了表示每个结点的具体信息,还可以再引入表头结点,来代表具体的图中的顶点。表头结点中,包括了链域和数据域。链域指向链表中的第一个结点,而数据域则保存了结点的相关属性。
class AdjacentListNode {
public int nodeIndex;
public int info;
public AdjacentListNode nextArc;
}
class ListHead {
int data;
AdjacentListNode firstArc;
}
好了。今天的课程就到这里,都是概念,比较简单。作业:
1. 在纸上写出文章中的图的邻接表。
上一节课: