学习图相关的算法(Java 实现)(1)
学习图相关的算法(Java 实现)(2)——Prim算法求最小生成树
目录
前言
最近做题的时候发现图相关的算法都忘光了根本就没有学会过,于是知耻而后勇,决定重新系统学习一下,争取在一个月一季度完成吧
学习资料:
1.《算法(第四版)》
2.Hyperskill中图相关的部分
相关知识点
1.形式定义
图(Graph) 由两部分组成: G = (V, E)
其中 V 表示所有 节点(node) 的 集合(Set)
E 表示所有 边(Edge) 的 集合(Set), 其中的每一条边有 2个 连接的节点
举例1:
G = (V, E)
V = {0, 1, 2, 3, 4}
E = {{1, 2}, {1, 3}, {2, 4}}
举例2:
G = (V, E)
V = {A, B, C}
E = ∅
2.简单图与复杂图
在上图中
边{0, 0}连接同一个结点,类似这种边称为 自环边(self-loop)
连接结点0和结点1有三条边{0, 1},这些边称为 平行边(parallel edges)
存在自环边和/或平行边的图称为复杂图(multigraph)
其它的图称为 简单图(simple graph)
3.边和节点的关系
如果2个节点通过1条边直接连接,那么称它们为 相邻(adjacent) 节点。如上图中节点1和节点2是相邻的,而节点1和节点4不相邻。
1条边连接的2个节点 关联(incident) 这条边,如上图中节点1和节点3关联边{1, 3},节点1还关联边{1, 2}
1个节点关联的边的数量叫做这个节点的 度(Degree)。如上图中节点1的度为2,节点3的度为1,节点0的度为0
4.有向图
如果对于图中的边不是双向的,而是有起点和终点,这样的图叫做 有向图(directed graphs) 否则叫做 无向图(undirected graphs)
如下面的有向图:
在有向图中,每个节点的度为两部分 入度(indegree) 和 出度(outdegree) 之和,入度是指指向该节点的边的数量,出度是指从该节点出发的边的数量
在上图中,节点1的出度是2,入度是0;节点2的入度是1,出度是1;节点3的入度是2,出度是0;节点4的入度是1,出度是1。
5.路径和环
路径(Path) 是图中有序且连续的一组点和边,在图中如果一个路径中没有重复的节点,即一个节点在该路径中最多出现一次,这个路径就是简单的,称它为简单路径(simple path)
上图中,蓝色的边和与其关联的节点组成一条路径1->2->4->3->5,你能数清上图中从节点1到节点5有多少条简单路径吗?
如果一条路径的出发节点和结束节点为同一个节点,这个路径就是一个环(cycle),如上图中1->2->3->4->1就是一个环。
6.图的连通性
在无向图中,如果节点x和节点y之间存在至少一条路径,那么节点x和节点y就是 联通(connected) 的,如果在一个节点的集合中,任意两个点都是联通的,那么这个集合就是一个 联通分量(connected component)
在上面的图中,显然有2个联通分量,一个是{0},一个是{1, 2, 3, 4},如果一个图只有一个联通分量,那么这个图就是联通图(connected graph) ,否则这个图就不联通。
7.子图
将一个图移除任意数量的边和/或节点所得到的图叫做这个图的子图(subgraph),如下面的例子
上图中图2是图1的子图,图3是图2的子图,图3还是图1的子图。
8.图的存储方式
图有两种主要的存储方式:邻接矩阵(adjacency matrix) 和邻接表(adjacency lists)
还有一些其它的存储方式,比如用集合存储图中所有的边。。。
(1).邻接矩阵
直接用一个矩阵来表示两个节点是否相邻
在上面的矩阵中,矩阵值为1代表两节点相邻,
如果是边比较密集的图,用邻接矩阵来存储会更好些。
(2).邻接表
用邻接表来存储每个节点的相邻节点
如果想要查找图的两个节点是否相邻,那么邻接表并不是一个好的选择。
邻接矩阵和邻接表的复杂度
----- | 空间复杂度 | 添加边时间复杂度 | 检查两个节点是否相邻时间复杂度 | 遍历一个节点相邻的所有节点时间复杂度 |
---|---|---|---|---|
邻接矩阵 | O(V2) | O(1) | O(1) | O(V) |
邻接表 | O(E+V) | O(1) | degree(v) | degree(v) |
MyGraph类(无向图,邻接表实现)
import java.util.*;
/**
* 无向图类
*
*/
public class MyGraph {
private final int V; //顶点数目
private int E; //边的数目
private ArrayList<Integer>[] adjacency; //邻接表存储边
/**
* 构造一个有v个节点的图
* @param v 图中节点的数量
*/
public MyGraph(int v) {
V = v;
adjacency = new ArrayList[V];
for(int i = 0; i < V; ++i) {
adjacency[i] = new ArrayList<>();
}
}
/**
* 获取图中节点的数量
* @return 图中节点的数量
*/
public int V() {
return V;
}
/**
* 获取图中边的数量
* @return 图中边的数量
*/
public int E() {
return E;
}
/**
* 向图中添加一条边
* @param v 与边相连的节点1的序号
* @param w 与边相连的节点2的序号
*/
public void addEdge(int v, int w) {
adjacency[v].add(w);
adjacency[w].add(v);
E++;
}
/**
* 获取指定节点的邻接表
* @param v 节点的序号
* @return 一个节点相邻的节点的邻接表
*/
public ArrayList<Integer> adjacency(int v) {
return adjacency[v];
}
}
那么如果节点的值不是int类型的呢?
可以将MyGraph类封装成泛型类
那么需要有一个泛型的数组存储每个节点的值,同时还需要一个哈希表存储值到节点索引
import java.util.*;
/**
* 泛型图类
* @param <Object> 图中节点的类型
*/
public class ObjectGraph<Object> {
private MyGraph graph; //无向图
private Object[] objects; //存储节点值
private HashMap<Object, Integer> directory; //节点值到节点索引的哈希表
/**
* 构造方法
* @param objects 图中每个节点的值
*/
public ObjectGraph(Object[] objects) {
this.graph = new MyGraph(objects.length);
this.objects = objects.clone();
directory = new HashMap<>();
for(int i = 0; i < objects.length; ++i) {
directory.put(objects[i], i);
}
}
/**
* 获取图中节点的数量
* @return 图中节点的数量
*/
public int V() {
return graph.V();
}
/**
* 获取图中边的数量
* @return 图中边的数量
*/
public int E() {
return graph.E();
}
/**
* 向图中添加一条边
* @param v 与边相连的节点1的值
* @param w 与边相连的节点2的值
*/
public void addEdge(Object v, Object w) {
graph.addEdge(directory.get(v), directory.get(w));
}
// public ArrayList<Integer> adjacency(int v) {
// return graph.adjacency(v);
// }
/**
* 获取指定节点的邻接表
* @param v 节点的序号
* @return 一个节点相邻的节点的邻接表
*/
public ArrayList<Object> adjacency(int v) {
ArrayList<Object> objectAdjacency = new ArrayList<>();
Iterable<Integer> adjacency = graph.adjacency(v);
for(int index: adjacency) {
objectAdjacency.add(objects[index]);
}
return objectAdjacency;
}
}
更多内容就下次再更啦~~