4.1 图
关于图,包括:无向图(简单连接)、有向图(连接有方向性)、加权图(连接带有权值)和加权有向图(连接既有方向性又带有有权值)。
无向图
定义:图是由一组顶点和一组能够将两个顶点相连的边组成的。
- 自环:即一条连接一个顶点和其自身的边;
- 连接同一对顶点的两条边称为平行边。
定义:在图中,路径是由边顺序连接的一系列顶点。简单路径是一条没有重复顶点的路径。环是一条至少含有一条边且起点和终点相同的路径。简单环是一条(除了起点和终点必须相同之外)不含有重复顶点和边的环。路径或者环的长度为其中所包含的边数。
定义:如果从任意一个顶点都存在一条路径到达另一个任意顶点,我们称这幅图是连通图。一幅非连通的图由若干连通的部分组成,它们都是其极大连通子图。
定义:树是一幅无环连通图。互不相连的树组成的集合称为森林。连通图的生成树是它的一幅子图,它含有图中的所有顶点且是一棵树。图的生成树森林是它的所有连通子树的生成树的集合。
4.1.1 表示无向图的数据类型
要开发处理图问题的各种算法,我们首先来看一份定义了图的基本操作的API。
表4.1.1 无向图的API
public class Graph | |
Graph(int V) | 创建一个含有V个顶点但不含有边的图 |
Graph(In in) | 从标准输入流in读入一幅图 |
int V() | 顶点数 |
int E() | 边数 |
void addEdge(int v, int w) | 向图中添加一条边v-w |
Iterable<Integer> adj(int v) | 和v相邻的所有顶点 |
String toString() | 对象的字符串表示 |
表4.1.2 最常用的图处理代码
任务 | 实现 |
计算v的度数 | public static int degree(Graph G,int v) { int degree = 0; for( int w : G.adj(v) ) degree++; return degree; } |
计算所有顶点的最大度数 | public static int maxDegree(Graph G) { int max = 0; for( int v = 0; v<G.V();v++) if( degree(G,v) >max) max = degree(G,v); return max; } |
计算所有顶点的平均度数 | public static double avgDegree( Graph G ) { return 2.0*G.E( ) / G.v( ); } |
计算自环的个数 | public static int numberOfSelfLoops( Graph G) { int count = 0; for( int v = 0;v<G.V();v++) for(int w:G.adj(v)) if(v==w) count++; return count/2; //每条边都被记过两次 } |
图的邻接表的字符串表示(Graph的实例方法) | public String toString() { String s = V +" vertices, " + E + " edges\n "; for( int v = 0 ;v<V ; v++ ) { s += v +" : "; for( int w :this.adj(v) ) s += w+" "; s += "\n"; } return s; } |
Graph数据类型
public class Graph
{
private final int V; //顶点数目
private int E; //边的数目
private Bag<Integer>[] adj; //邻接表
public Graph(int V)
{
this.V = V; this.E = 0;
adj = ( Bag<Integer>[]) new Bag[V]; //创建邻接表
for(int v = 0; v < V; v++) //将所有链表初始化为空
adj[v] = new Bag<Integer>();
}
public Graph(In in)
{
this(in.readInt()); //读取V并将图初始化
int E = in.readInt(); //读取E
for(int i = 0; i < E; i++)
{
int v = in.readInt(); //读取一个顶点
int w = in.readInt(); //读取另一个顶点
addEdge(v,w); //添加一条连接它们的边
}
}
public int V() { return V;}
public int E() { return E;}
public void addEdge( int v , int w )
{
adj[v].add(w); //将w添加到v的链表中
adj[w].add(v); //将v添加到w的链表中
E++;
}
public Iterable<Integer> adj(int v)
{ return adj[v]; }
}
这份Graph的实现使用了一个由顶点索引的整型链表数组。每条边都会出现两次,即当存在一条连接v与w的边时,w会出现在v的链表中,v也会出现在w的链表中。第二个构造函数从输入流中读取一幅图,开头是V,然后是E,再然后是一列整数对,大小在0到V-1之间。