无向图
1 图
图是由一组顶点和一组能够将两个顶点相连的边组成的。
图(Graph)结构是一种非线性的数据结构,图在实际生活中有很多例子,比如交通运输网,地铁网络,社交网络,计算机中的状态执行等等都可以抽象成图结构。图结构比树结构复杂的非线性结构。
图的构成
**1.顶点(vertex):**图中的数据元素
**2.边(edge):**图中连接这些顶点的线
所有的顶点构成一个顶点集合,所有的边构成边的集合,一个完整的图结构就是由顶点集合和边集合组成。图结构在数学上记为以下形式:
G=(V,E) 或者 G=(V(G),E(G))
两个核心要素:顶点和边,一个图可以没有边,但不能没有顶点。
图的定义和绘出的图像是无关的。
图的分类
好友关系:无向图(undirected graph) 一个图结构中,所有的边都没有方向性,那么这种图便称为无向图。
关注关系:有向图(directed graph) 一个图结构中,边是有方向性的,那么这种图就称为有向图。
有权图;无权图
这里的权可以理解成一个数值,就是说节点与节点之间这个边是否有一个数值与它对应,对于无权图来说这个边不需要具体的值。
对于有权图节点与节点之间的关系可能需要某个值来表示,比如这个数值能代表两个顶点间的距离,或者从一个顶点到另一个顶点的时间,所以这时候这个边的值就是代表着两个节点之间的关系,这种图被称为有权图;
分类:
无向 | 有向 | |
---|---|---|
无权 | 无向无权图 | 有向无权图 |
有权 | 无向有权图 | 有向有权图 |
本次课程涉及到图为无权无向图。
2 无向图
特殊的图:定义允许出现两种简单而特殊的情况,
**1.自环边(self-loop):**节点自身的边,自己指向自己。
**2.平行边(parallel-edges):**两个节点之间存在多个边相连接。
简单图:无自环、无平行边。
图术语介绍:
**① **相邻、度数、子图
- 相邻:当两个顶点通过一条边相连时,我们称这两个顶点是相邻的, 并称这条边依附于这两个顶点。
- 度数:某个顶点的度数即为依附于它的边的总数。
- **子图:**是由一幅图的所有边的一个子集(以及它们所依附的所有顶点 )组成的图。
② 路径、环
- 路径:是由边顺序连接的一系列顶点。简单路径是一条没有重复顶点的路径。
- 环:是一条至少含有一条边且起点和终点相同的路径。简单环是一条(除了起点和终点必须相同之外)不含有重复顶点和边的环。
路径或者环的长度为其中所包含的边数。大多数情况下,我们研究的都是简单环和简单路径井会省略掉简单二字。当允许重复的顶点时,我们指的都是一般的路径和环。当两个顶点之间存在一条连接双方的路径时, 我们称一 个顶点和另一个顶点是连通的。我们用类似出u-V-W-X的记法来表示u到x的一条路径, 用U-V-W-X-U表示u到V到W到x再回到u的一条环。
**③ **联通图
-
**连通图:**图中任意两个顶点之间皆存在路径。
一幅非连通的图由若干连通的部分组成,它们都是其极大连通子图。一般来说,要处理一张图就需要一个个地处理它的连通分量(子图)。
- 任何连通图的连通分量只有一个,即是其自身
- 非连通的无向图有多个连通分量。
④ 树
树 是一幅无环连通图。互不相连的树组成的集合称为森林。连通图的生成树是它的一幅子图,它含有图中的所有顶点且是一棵树。图的生成树森林是它的所有连通子图的生成树的集合。
树是一种无环图。无环图一定是树吗?
联通图的生成树包含所有的顶点的树:边数为V-1。包含所有顶点,边数位V-1,一定是联通生成树吗?
一个图一定有生成树吗?
一个图一定有生成森林。
⑤稀疏图和稠密图
图的密度是指已经连接的顶点对占所有可能被连接的顶点对的比例。
在稀益明中,被连楼的厦点对很少;而在稠密图中,只有少部分顶点对之间没有边连接。一般来说,如果图中不同的边的数量在顶点总数V的一个小的常数倍以内,那么我们就认为这幅图是稀疏的,否则则是稠密的。但实际应用中稀疏图和稠密图之间的区别是十分明显的。我们将会遇到的应用使用的几乎都是稀疏图。
假设节点度最大为3,如果有3000个节点,有3000* 3/2 = 4500条边,最多有3000 * 2999/2=4498500条边。
树一定是稀疏图。
图的表示满足:
必须为可能在应用中碰到的各种类型的图预留出足够的空间;
Graph的实例方法的实现一定要快,它们是开发处理图的各种用例的基础。
①图的基本表示-邻接矩阵
我们可以使用一个K乘K的布尔矩阵。当顶点v和顶点w之间有相连接的边时,定义v行w列的元素值为true,否则为false。这种表示方法不符合第一个条件,含有上百万个顶点的图是很常见的,V^2个布尔值所需的空间是不能满足的。adj[v]= adj[w] = true
A[i]][j]= 1表示顶点i和顶点j相邻。
对于简单图:主对角线为0。对于无向图,矩阵关于主对角线对称。
空间复杂度为O(V^2)
②图的基本表示-边的数组
我们可以使用一个Edge类,它含有两个int实例变量。这种表示方法很简洁,但不满足第二个条件一要 实现 adj() 需要检查图中的所有边。
一个二维数组,第一维的大小是图中边的总数,第二位的大小是2,即边的两个顶点。数组是int型的,存储的边的两个顶点就是顶点数组中对应这两个顶点的索引。
③图的基本表示-邻接表
我们可以使用一个以顶点为索引的列表数组,其中的每个元素都是和该顶点相邻的顶点列表,这种数据结构能够同时满足典型应用所需的以上两个条件。
邻接表可以实现快速查看两点是否相邻。空间复杂度为O(V+E)。
可以使用链表、哈希表(HashSet)-O(1)和红黑树(TreeSet)-O(logV)来进行实现。
链表查找一定是线性的,所以想要加快查询的速度就不要使用链表。改为使用红黑树和哈希表。红黑树和哈希表的差别其实不大。可根据自己的习惯进行选择。我们这里选择红黑树,红黑树是保持了顺序性的并且更省空间。txt如下。
7 9
0 1
0 3
1 2
1 6
2 3
2 5
3 4
4 5
5 6
代码:Graph
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import java.util.TreeSet;
//基于邻接表实现的图表示
public class Graph {
private int V; //顶点数
private int E; //边数
private TreeSet<Integer>[] adj; //和顶点相邻的所有顶点
public Graph(String filename){
File file = new File(filename);
try(Scanner scanner = new Scanner(file)){
//获取到顶点数
V = scanner.nextInt();
if(V < 0) throw new IllegalArgumentException("V must be non-negative");
adj = new TreeSet[V];//创建容量为V的红黑树
for(int i = 0; i < V; i ++)
adj[i] = new TreeSet<Integer>(); //每个存放顶点的红黑树
E = scanner.nextInt();
if(E < 0) throw new IllegalArgumentException("E must be non-negative");
//获取边
for(int i = 0; i < E; i ++){
int a = scanner.nextInt();
validateVertex(a);//判断顶点a是否合法
int b = scanner.nextInt();
validateVertex(b);//判断顶点b是否合法
//自环边,本节只讨论简单图
if(a == b) throw new IllegalArgumentException("Self Loop is Detected!");
//平行边,本节只讨论简单图
if(adj[a].contains(b)) throw new IllegalArgumentException("Parallel Edges are Detected!");
adj[a].add(b); //b是a的邻边,b加到adj[a]
adj[b].add(a); //a是b的邻边,a加到adj[b]
}
}
catch(IOException e){
e.printStackTrace();
}
}
//判断该顶点是否合法。后面复用,这里用public
public void validateVertex(int v){
if(v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + "is invalid");
}
//图的顶点数
public int V(){
return V;
}
//图的边数
public int E(){
return E;
}
//两点间是否有边
public boolean hasEdge(int v, int w){
validateVertex(v);//判断点是否合法
validateVertex(w);
return adj[v].contains(w);
}
//获取顶点的所有相邻顶点
public Iterable<Integer> adj(int v){
validateVertex(v);
return adj[v];
}
//获取顶点的度数(有个相邻定点)
public int degree(int v){
validateVertex(v);
return adj[v].size();
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append(String.format("V = %d, E = %d\n", V, E));
for(int v = 0; v < V; v ++){
sb.append(String.format("%d : ", v));
for(int w : adj[v])
sb.append(String.format("%d ", w));
sb.append('\n');