一、相关定义
图(Graph)
- 由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
- 图中的数据元素称为顶点(Vertex)。
- V是有穷非空集合,不能是空集。
- 顶点之间的逻辑关系用边来表示,边集可以是空的。
无向图(Undirected graphs)
- 无向边(Edge): 若两个顶点之间的边没有方向,则称这条边为无向边;
- 若图中任意一条边都是无向边,则称图为无向图;
- 在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。
- 包含n个顶点的无向完全图有n*(n-1)/ 2条边。
有向图(Directed graph)
- 有向边:若从一个顶点到另一个顶点有方向,则称这条边为有向边,也称为弧(Arc);
- 若图中任意一条边都是有向边,则称图为有向图;
- 在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。
- 包含n个顶点的有向完全图有n*(n-1)条边。
网(Network)
- 有些图的边或弧有与它相关的数字,叫做权(weight);
- 带权的图通常称为网。
连通图(Connected Graph)
- 在无向图中,若两个顶点之间有路径,则称两个点是连通的;
- 若任意两个顶点都是连通的,则称为连通图。
- 无向图中的极大连通子图称为连通分量;
强连通图
- 在有向图中,若每一对顶点v~i~、v~j~,从v~i~到v~j~都存在路径,则称图为强连通图;
- 极大强连通子图称为强连通分量。
生成树
- 连通图的生成树是一个极小的连通子图,它包含图中的全部顶点,却只有n-1条边;
- 若有向图只有一个顶点入度为0,其余均为1,则是一棵有向树(点的入度是以该点为头的弧的数目,即方向指向它的弧,反之为出度);
- 一个有向图的生成森林由若干棵有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧。
二、图的存储结构
邻接矩阵(Adjacency Matrix)
用两个数组来表示图,一个一维数组表示顶点,一个二维数组(邻接矩阵)存储图中边或弧的信息。
- 在无向图邻接矩阵中,横纵坐标是n个顶点,若对应的两个订点之间有一条边,记为1,其余记为0,必为对称矩阵;
- 在有向图中,每一行为以对应点为起点的边,有为1无为0,所以不是对称矩阵;
- 在网图中,若两点之间有边,记录对应的权重,若两点相等,记为0,若无边,记为∞。
矩阵特点:
- 可以轻易判定两定点之间是否有边(1有0无);
- 一个顶点的度(一个顶点的边数)为对应的行或列元素之和(在有向图中,入度为列各元素之和,出度为行各元素之和);
- 求顶点邻接点即为把该点对应的行或列扫描一遍。
邻接矩阵存储结构:
private static class Matrix{
String[] vexs; //顶点数组
Integer[][] arc; //邻接矩阵
int verNum,edgeNum; //顶点数和边数
}
创建过程代码:
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("请输入顶点数:");
matrix.verNum = in.nextInt();
System.out.println("请输入边数:");
matrix.edgeNum = in.nextInt();
matrix.vexs = new String[MAXVEX];
matrix.arc = new Integer[MAXVEX][MAXVEX];
System.out.println("请输入每个顶点:");
for (int i = 0; i < matrix.verNum; i++) {
matrix.vexs[i] = in.next();
}
for (int i = 0; i < matrix.verNum; i++) {
for (int j = 0; j < matrix.verNum; j++) {
matrix.arc[i][j] = INFINITY;
}
}
for (int k = 0; k < matrix.edgeNum; k++) {
System.out.println("请输入边上的下标i,j和权重w:");
int i = in.nextInt();
int j = in.nextInt();
int w = in.nextInt();
matrix.arc[i][j] = w;
}
for (int i = 0; i < matrix.verNum; i++) {
Integer[] a = matrix.arc[i];
for (int j = 0; j < matrix.verNum; j++) {
if(i == j)
System.out.print(0+", ");
else if(a[j] == INFINITY)
System.out.print('\u221E'+", ");
else
System.out.print(a[j] + ", ");
}
System.out.println();
}
}
输出结果:
请输入顶点数:
5
请输入边数:
6
请输入每个顶点:
v0 v1 v2 v3 v4
请输入边上的下标i,j和权重w:
1 0 9
请输入边上的下标i,j和权重w:
1 2 3
请输入边上的下标i,j和权重w:
2 0 2
请输入边上的下标i,j和权重w:
2 3 5
请输入边上的下标i,j和权重w:
3 4 1
请输入边上的下标i,j和权重w:
0 4 6
0, ∞, ∞, ∞, 6,
9, 0, 3, ∞, ∞,
2, ∞, 0, 5, ∞,
∞, ∞, ∞, 0, 1,
∞, ∞, ∞, ∞, 0,
邻接表(Adjacency List)
- 一维数组存放顶点;
- 对无向表,每个顶点作为一个单链表的头指针依次连接与它相邻的点。
- 对有向表,以顶点的弧尾存储边表;
- 对网图,为链表节点增加一个权重的数据域。
一个邻接表结构如下:
//边表结点
private static class EdgeNode{
int adjvex; //邻接点域
int weight; //网图的权重
EdgeNode next; //next结点
}
//顶点表结点
private static class VertexNode{
String data; //顶点名称
EdgeNode firstEdge; //边表头指针
}
//邻接表
private static class AdjacencyGraph{
VertexNode[] vertexNodes; //存放顶点的数组
int VertexNum,EdgeNum; //顶点数和边数
}
十字链表(Orthogonal List)
逆邻接表:
有向图调换箭头方向后的邻接表
十字链表:
结合邻接表和逆邻接表,在邻接表中加入入边,以方便查找。
结点代码如下:
//边表结点
private static class EdgeNode{
int tailvex,headvex; //邻接点域
int weight; //网图的权重
EdgeNode headlink,taillink; //出入结点
}
//顶点表结点
private static class VertexNode{
String data; //顶点名称
EdgeNode firstin; //边表头入指针
EdgeNode firstOut; //边表头出指针
}
//十字链表
private static class AdjacencyGraph{
VertexNode[] vertexNodes; //存放顶点的数组
int VertexNum,EdgeNum; //顶点数和边数
}
邻接多重表
在无向图中,使用邻接表,每一条边会重复两次,在删除时会造成不便,因此采用邻接多重表。
邻接多重表有两部分组成:
- 顶点:由顶点组成的数组;
- 边表结点:一个结点中包含四个域:边依附的两个顶点、每个顶点的另一条边。
结点代码如下:
//边表结点
private static class EdgeNode{
int ivex,jvex; //邻接点域
EdgeNode ilink,jlink; //next结点
boolean mark; //标记是否被搜索过
}
//顶点表结点
private static class VertexNode{
String data; //顶点名称
EdgeNode firstEdge; //边表头指针
VertexNode(String s){
this.data = s;
this.firstEdge = null;
}
}
//邻接表
private static class MulipleGraph{
VertexNode[] vertexNodes; //存放顶点的数组
int VertexNum,EdgeNum; //顶点数和边数
}
创建方法关键代码:
for (int k = 0; k < multilistGraph.EdgeNum; k++) {
System.out.println("请输入顶点序号i,j:");
int i = in.nextInt();
int j = in.nextInt();
EdgeNode edgeNode = new EdgeNode();
edgeNode.ivex = i;
edgeNode.jvex = j;
edgeNode.ilink = multilistGraph.vertexNodes[i].firstEdge;
edgeNode.jlink = multilistGraph.vertexNodes[j].firstEdge;
multilistGraph.vertexNodes[i].firstEdge = edgeNode;
multilistGraph.vertexNodes[j].firstEdge = edgeNode;
}
边集数组
边集数组是由两个一维数组构成,一个存储顶点的信息,另一个存储边的信息,这个边数组每个数据元素由一条边的起点下标、终点下标和权组成。
边集数组的结构:
class Edge{
int begin; //起点下标
int end; //终点下标
int weight; //权重
}
在边集数组中需要扫描整个边数组,效率不高。
图的遍历
附上链接:DFS和BFS
最小生成树算法
附上链接: 最小生成树算法
最短路径
附上链接: 最短路径算法
拓扑排序与关键路径
附上链接:拓扑排序与关键路径