上篇文章介绍了图的邻接矩阵的实现,本文即介绍图的另一种实现方法-邻接表
邻接表的实现原理
使用邻接矩阵实现图,对于n个顶点的图,即使是有向图也需要O(n^2)。实际上,如此大的空间足以容纳所有潜在的弧。然而实际应用所处理的图,所含的
边通常远远少于O(n^2)。比如在平面图之类的稀疏图(sparse graph)中,边数渐进地不超过O(n),仅与顶点总数大致相当。
由此可见,邻接矩阵的空间效率之所以低,是因为其中大量单元所对应的弧,并未在图中出现。类似的问题也出现的树的顺序存储结构,对应的也可以通过链式存储的方法来改变。
按照这一思路,的确可以导出图结构的另一种表示与实现形式。
以图a所示的无向图为例,只需将如图b所示的邻接矩阵,逐行地转换为图c所示的一组列表,即可分别记录各顶点的关联边(或等价地,邻接顶点)。这些表便称之为邻接表(adjacency list)。
图到邻接表的转化过程
以上图为例,对于图a的四个顶点A,B,C,D四个顶点的属性上添加一个对于弧的指针,指向以该顶点为起点的一条弧(被称为该顶点为起点的所有弧的首弧)。而对于弧的属性也增加一个对顶点的指针和对弧的指针,其中对于顶点的指针指向该弧的终点(弧头),而对弧的指针指向已该弧的起点(弧尾)的另一条弧。
这样,根据一个顶点的弧的指针,通过这个弧对于其他的弧的指针即可定义出该顶点作为起点(弧尾)的所有弧。也可以根据一个顶点的弧的指针,通过这个弧对于弧的终点的指针(弧头)便可定义出由该顶点起的一条路。
节点的实现
public class Node<T> {
private T data; // 存储的数据
private Edge head; // 该节点作为弧的起点(弧尾)的首弧
private int outDegree; // 出度
private int inDegree; // 入度
//以下属性在遍历中使用
private int status = 0;
//状态 0 undiscovered 1 discovered 2 visited
//此处应该使用枚举,笔者偷懒了
private int parent = -1;
private int dTime = -1; //开始遍历的时间
private int fTime = -1; //结束遍历的时间
//省掉getter和setter...
public void addInDegree() {
this.inDegree++;
}
protected void addOutDegree() {
this.outDegree++;
}
protected void delInDegree() {
this.inDegree--;
}
protected void delOutDegree() {
this.outDegree--;
}
//设置一条也该节点为起点的弧
protected void setEdge(Edge edge) {
// 从该节点的弧链接头指针出发
Edge nextNull = ge