1 概述
在典型应用中,图都是通过文件或者网页定义的,使用的是字符串而非整数来表示和指代顶点。为了适应这样的应用,我们定义了拥有一下性质的格式:
- 顶点名为字符串;
- 用指定的分隔符来隔开顶点;
- 每一行都表示一组边的集合,每一条边都连接着这一行的第一个名称表示的顶点和其他名称所表示的顶点;
- 顶点总数V和边的总数E都是隐式定义的。
图1-1如下所示:
该图表示一个小型运输系统模型,其中每个顶点是美国机场的代码,连接它们的边则表示顶点之间的航线。
图1-2如下所示:
上图为互联网电影数据库,文件每一行列出了一个电影名以及出演该部电影的一系列演员。电影和演员都是顶点,而邻接表中的每一条边都将电影和它的表演者联系起来,这是一副二分图。
2 API
public class | SymbolGraph | |
---|---|---|
SymbolGraph(String filename, String delm) | 根据filename指定的文件构造图,使用delm分隔顶点 | |
public boolean | contains(String key) | key是一个顶点吗 |
int | indexOf(String key) | key的索引 |
public String | nameOf(int v) | 索引v的顶点名 |
Graph | G() | 隐藏的Graph对象 |
3 实现
package com.gaogzhen.datastructure.graph.undirected;
import edu.princeton.cs.algs4.Graph;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.ST;
/**
* 符号图
* @author: Administrator
* @createTime: 2023/03/14 19:59
*/
public class SymbolGraph {
/**
* 符号表
* 符号名-索引
*/
private ST<String, Integer> st;
/**
* 反向索引数组
* 索引->符号名
*/
private String[] keys;
/**
* 无向图
*/
private Graph graph;
/**
* 文件读入数据构建无向图
* @param filename 文件路径
* @param delimiter 顶点分隔符
*/
public SymbolGraph(String filename, String delimiter) {
// 构建符号表
st = new ST<>();
In in = new In(filename);
while (!in.isEmpty()) {
String[] s = in.readLine().split(delimiter);
for (int i = 0; i < s.length; i++) {
if (!st.contains(s[i])) {
st.put(s[i], st.size());
}
}
}
// 初始化反向索引数组keys
keys = new String[st.size()];
for (String name : st.keys()) {
keys[st.get(name)] = name;
}
// 构建无向图
graph = new Graph(st.size());
in = new In(filename);
while (in.hasNextLine()) {
String[] a = in.readLine().split(delimiter);
int v = st.get(a[0]);
for (int i = 1; i < a.length; i++) {
int w = st.get(a[i]);
graph.addEdge(v, w);
}
}
}
/**
* 是否包含某个顶点
* @param s 给定顶点
* @return {@code true} 如果顶点 {@code s}在符号表中;否则 {@code false}
*/
public boolean contains(String s) { return st.contains(s);}
/**
* 返回顶点名称对应的索引
* @param s 顶点名称
* @return 顶点名称 {@code s} 对应的索引(0~V-1)
*/
public int indexOf(String s) {
return st.get(s);
}
/**
* 返回索引{@code v}对应的顶点名称
* @param v 顶点索引(0~V-1)
* @return 索引 {@code v} 对应的顶点名称
*/
public String nameOf(int v) {
validateVertex(v);
return keys[v];
}
/**
* 返回该符号图关联的无向图
* @return 该符号图关联的无向图
*/
public Graph graph() {
return graph;
}
/**
* 校验顶点 {@code v} 是否合法
* @param v 顶点v
*/
private void validateVertex(int v) {
int c = graph.V();
if (v < 0 || v >= c) {
throw new IllegalArgumentException("顶点 " + v + " 不在 0~" + (c - 1) + " 之间");
}
}
}
符号表实现用到以下三种数据结构:
- 符号表st,键的类型为String(顶点名),值的类型为Integer(索引);
- 一个数组keys[],用做反向索引,保存每个顶点索引对应的顶点名称;
- 一个Graph对象graph,它使用索引表示定的名称。
SymbolGraph会遍历2遍数据来构建上述数据结构。因为key[]和graph初始化需要用到顶点总数V,所以第一遍先构建符号表st,通过st.size()获取顶点总数。
典型应用中,不方便指定顶点总数V和边总数E,使用SymbolGraph方便在文件中增减数据。
4 测试
测试代码:
public static void testSymbolGraph() {
SymbolGraph symbolGraph = new SymbolGraph("H:\\gaogzhen\\java\\projects\\algorithm\\asserts\\routes.txt", " ");
System.out.println(symbolGraph.contains("ORD"));
System.out.println(symbolGraph.indexOf("ORD"));
System.out.println(symbolGraph.nameOf(3));
System.out.println(symbolGraph.graph());
}
测试结果:
true
2
DEN
10 vertices, 18 edges
0: 2 7 1
1: 4 7 0
2: 7 0 6 5 4 3
3: 9 6 2
4: 1 5 7 2
5: 4 2 6
6: 9 8 3 2 5
7: 1 2 4 0
8: 9 6
9: 6 8 3
演员电影图自己测试。
5 间隔度数
图处理一个经典问题就是,找到一个社交网络中两个人间隔的度数。以电影-演员图为例,kevin Bacon是一个活跃演员,演过很多电影,找出演员Kevin Bacon数。Kevin Bacon数:Bacon本人为0,所有和Bacon演过同一部电影的人的值为1,所有(除了Kevin Bacon)和Kevin Bacon数为1的演员演过同一部电影的其他演员值为2,依次类推。
实现代码:
package com.gaogzhen.datastructure.graph.undirected;
import edu.princeton.cs.algs4.StdIn;
/**
* 无向图间隔度数
* @author: Administrator
* @createTime: 2023/03/14 21:17
*/
public class DegreesOfSeparation {
public static void degreesOfSeparation(String filename, String delimiter, String source) {
// 构建符号图
SymbolGraph symbolGraph = new SymbolGraph(filename, delimiter);
if (!symbolGraph.contains(source)) {
System.out.println(source + " 不在数据库中");
return;
}
// 符号图和起始顶点构建最短路径
BreadthFirstDirectedPaths bfs = new BreadthFirstDirectedPaths(symbolGraph.graph(), symbolGraph.indexOf(source));
while (!StdIn.isEmpty()) {
String sink = StdIn.readLine();
if (symbolGraph.contains(sink)) {
int s = symbolGraph.indexOf(sink);
if (bfs.hasPathTo(s)) {
for (Integer v : bfs.pathTo(s)) {
System.out.println("\t" + symbolGraph.nameOf(v));
}
} else {
System.out.println(sink + " 与 " + source + " 不相连");
}
} else {
System.out.println(sink + " 不在数据库中");
}
}
}
}
测试代码:
public static void testDegreesOfSeparate() {
String filename = "H:\\gaogzhen\\java\\projects\\algorithm\\asserts\\movies.txt";
String delimiter = "/";
String source = "Bacon, Kevin";
DegreesOfSeparation.degreesOfSeparation(filename, delimiter, source);
}
测试结果:
xxx
xxx 不在数据库中
Kidman, Nicole
Bacon, Kevin
JFK (1991)
Sutherland, Donald (I)
Cold Mountain (2003)
Kidman, Nicole
Aronson, Steve
Bacon, Kevin
JFK (1991)
X, Malcolm
Malcolm X (1992)
Aronson, Steve
间隔度数的理论在计算机网络的设计以及理解各个科学领域中的自然网络中都能起到重要的作用。
后记
如果小伙伴什么问题或者指教,欢迎交流。
❓QQ:806797785
⭐️源代码仓库地址:https://gitee.com/gaogzhen/algorithm
参考链接:
[1][美]Robert Sedgewich,[美]Kevin Wayne著;谢路云译.算法:第4版[M].北京:人民邮电出版社,2012.10.p352-356.