这篇文章是笔者,学习《数据结构与算法分析——java语言描述第三版》一书的第九章图论部分,根据书中的提示加上自己的理解,编写的源代码。
注意点:
1.使用HashMap + LinkedList的方式来实现邻接表。
2.实现了广度优先搜索,及其应用(无圈图中的单源最短路径问题,关键路径分析),深度优先搜索及其应用(无向图中的割点寻找)
3.后续还会陆续更新第九章的其他问题。import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
/**
*
* 这次数据结构上的改进使用在ArrayVetex邻接表表示法中的改进想法:
* 1. 不再使用数组结构来组织多个点的邻接表了。
* 2. 改为使用HashMap + LinKedList来组织所有的顶点数据以及顶点之间的邻接顺序。
*
* 选用这两种的组合时基于以下一个重要的理由:
* 1. 每个顶点的邻接点个数数目在实际情况中一定不会多,也就是linkedList的size会是一个小常数。
* 所以我们可以任何linkedList使用get,contain等遍历链表的线性方法,在这里可以看成是常数操作。
*
* @version v1.0 无权图版本
* 后期我再想想怎么更好的组织,融入有权版本,估计还是得新建一个内部私有类(类似ArrayVerex.Edge类)来实现。
*
* @version v2.0
* 哎,还是老老实实的使用标准库中的hashmap吧,自己的虽然不错,但是我自己独立开发的hashmap功能实在是不健全,尤其是没有迭代器
* 太伤了。
*
*
* @author 25040
*
*/
public class HashMapAdjacencyList<T> {
public static void main(String[] args) {
HashMapAdjacencyList<String> graph = new HashMapAdjacencyList<>(7);
/*无权无圈图测试数据
graph.builtAdjacency("v1", new String[] {"v2","v4"}, new int[] {1,1});
graph.builtAdjacency("v2", new String[] {"v4","v5"}, new int[] {1,1});
graph.builtAdjacency("v3", new String[] {"v1","v6"}, new int[] {1,1});
graph.builtAdjacency("v4", new String[] {"v3","v5","v6","v7"}, new int[] {1,1,1,1});
graph.builtAdjacency("v5", new String[] {"v7"}, new int[] {1});
graph.builtAdjacency("v6", new String[] {}, new int[] {});
graph.builtAdjacency("v7", new String[] {"v6"}, new int[] {1});
*/
/*有权无圈图的测试数据
graph.builtAdjacency("v1", new String[] {"v2","v4"}, new int[] {2,1});
graph.builtAdjacency("v2", new String[] {"v4","v5"}, new int[] {3,10});
graph.builtAdjacency("v3", new String[] {"v1","v6"}, new int[] {4,5});
graph.builtAdjacency("v4", new String[] {"v3","v5","v6","v7"}, new int[] {2,2,8,4});
graph.builtAdjacency("v5", new String[] {"v7"}, new int[] {6});
graph.builtAdjacency("v6", new String[] {}, new int[] {});
graph.builtAdjacency("v7", new String[] {"v6"}, new int[] {1});
*/
/*割点测试数据
graph.builtAdjacency("A", new String[] {"B","D"},new int[] {1,1});
graph.builtAdjacency("B", new String[] {"A","C"},new int[] {1,1});
graph.builtAdjacency("C", new String[] {"B","D","G"},new int[] {1,1,1});
graph.builtAdjacency("D", new String[] {"A","C","E","F"},new int[] {1,1,1,1});
graph.builtAdjacency("E", new String[] {"D","F"},new int[] {1,1});
graph.builtAdjacency("F", new String[] {"D","E"},new int[] {1,1});
graph.builtAdjacency("G", new String[] {"C"},new int[] {1});
*/
graph.builtAdjacency("start", new String[] {"A","B"},new int[] {3, 2});
graph.builtAdjacency("A", new String[] {"C","D"},new int[] {3, 2});
graph.builtAdjacency("B", new String[] {"D","E"},new int[] {2, 1});
graph.builtAdjacency("C", new String[] {"F"},new int[] {3});
graph.builtAdjacency("D", new String[] {"F","G"},new int[] {3, 2});
graph.builtAdjacency("E", new String[] {"G","K"},new int[] {2, 4});
graph.builtAdjacency("F", new String[] {"H"},new int[] {1});
graph.builtAdjacency("G", new String[] {"H"},new int[] {1});
graph.builtAdjacency("H", new String[] {"end"},new int[] {1});
graph.builtAdjacency("K", new String[] {"H"},new int[] {1});
graph.builtAdjacency("end", new String[] {},new int[] {});
//System.out.println(graph.toString());
//System.out.println(graph.breathFirstSearch("v3", "v7"));
//System.out.println(graph.getShortestPathWithWeightedGraph("v3", "v5"));
//System.out.println(graph.inDegree("v4"));
//graph.depthFirstSearchAndShowPath("v1");
//graph.findArt("A");
graph.criticalPathAnalysis("start","end");
}
//属性存储一个HashMap
private HashMap<T, OneVetexAdjacencyList<T>> map;
private int currentSize;
/**
* 构造
* @param 图的当前
*/
public HashMapAdjacencyList(int vetexNum) {
this.map = new HashMap<>(vetexNum);
this.currentSize = 0;
}
/**
* 清空邻接表
*/
public void makeEmpty() {
this.currentSize = 0;
map.clear();
}
/**
* 建立两个顶点的连接。
* 注意时有向的,前一个参数代表的顶点->后一个参数代表的顶点
*
* @param vetex
* @param otherVetex
*/
public void bulitAdjacency(T selfVetex, T otherVetex) {
//调用map的put方法,如果self顶点不存在,则新建邻接表加入到map中,返回true,否则如果self顶点存在,不作操作,返回false
if(this.map.put(selfVetex, new OneVetexAdjacencyList<>(selfVetex)) != null) {
++this.currentSize;
}
OneVetexAdjacencyList<T> adjacencyList = this.map.get(selfVetex);
adjacencyList.insertAdjcancyVetex(otherVetex);
}
/**
* 建立当前self顶点与其他多个顶点的邻接关系
*
* @param selfVetex
* @param otherVetexs
*/
public void builtAdjacency(T selfVetex, T[] otherVetexs, int[] allEdgeWeight) {
if(this.map.put(selfVetex, new OneVetexAdjacencyList<>(selfVetex)) != null) {
++this.currentSize;
}
OneVetexAdjacencyList<T> adjacencyList = this.map.get(selfVetex);
adjacencyList.insertAdjacencyVetex(otherVetexs, allEdgeWeight);
}
/**
* 格式化输出
*/
public String toString() {
return this.map.toString();
}
/**
* 返回当前图中有多个少顶点,即有多少个邻接表
* @return
*/
public int size() {
return this.currentSize;
}
/**
* 无权无圈图的广度优先搜索求最短路径问题。
* 使用LinkedList和HashMap来完成。其中linkedList完成的更像是队列的工作。
*
* 那么问题来了,为什么这样做,返回的是最短路径。
* 因为,其实理由和书上一样的。当我们添加currDist + 1的那些邻接点,更远的邻接点时,我们让它从队尾入队。
* 这样保证了一定是先处理完成了当前currDist的所有节点后,在处理更远的邻接点。而更早处理的点,更早占据map的位置,
* 我们自然可以认为它相对于后来者具有更短的路径。也就是书中已经known了。
*
* 返回路径的边对象的链表。储存了最短路径存储了经过了那些节点,其权值的含义在这里变成了各节点到起点的间距。
* 如果没有路径能从起点到终点,返回null
*
* @param startVetex
* @return
*/
public List<T> breathFirstSearch(T startVetex, T endVetex) {
HashMap<T,T> pathMap = new HashMap<>();
LinkedList<T> q = new LinkedList<>();
//让起点入队。
q.addLast(startVetex);
while(! q.isEmpty()) {
//获取当前的节点的邻接点的列表,并且从队列中删除它
T nowNode = q.removeFirst();
OneVetexAdjacencyList<T> adjList = this.map.get(nowNode);
LinkedList<Edge<T>> edgeList = adjList.getAdjacencyVetexList();
//遍历所有的邻接节点
for (Edge<T> edge : edgeList) {
T adjVetex = edge.getNextVetex();
//判断当前节点是否已经known了,即已经被遍历过了
if(pathMap.get(adjVetex) == null) {
//known为false的节点,存储了节点路径。
pathMap.put(adjVetex, nowNode);
q.addLast(adjVetex);
}
}
}
pathMap.put(startVetex, null);
return getPathFromMap(pathMap, startVetex, endVetex);
}
/**
* 从生成的map路径表中,返回正向的路径。
* @param pathMap
* @param startVetex
* @param endVetex
* @return
*/
private LinkedList<T> getPathFromMap(HashMap<T, T> pathMap, T startVetex, T endVetex) {
LinkedList<T> result = null;
if(!pathMap.isEmpty()) {
result = new LinkedList<>();
//从尾部开始
for(T vetex = endVetex;vetex != null;vetex = pathMap.get(vetex))
result.addFirst(vetex);
}
return result;
}
/**
* 使用广度优先搜索解决有权无圈图中的单源最短路径问题。莫名其妙相除的版本。
* 其实是我错误地理解书上的意思,误打误撞出来的。过程根本不贪,但结果是贪婪的。
*
* 图中不能包含负值边。
*
* 思路还是根广度优先搜索的思路一样,按照邻接点的层次层层往更远的节点扩展,直到覆盖所有节点。
* 该种算法是我根据书中的算法自己想的,其根书上有点不太一样,我目的是从起点出发遍历更新所有的边。
* 跟贪婪算法不太一样的地方在于,该算法不要求每轮都选择最短距离的未知的节点。而是跟广度优先搜索一样。遍历所有的边。
* known用来判断该点是否进入队列,即其所有的邻接点是否都被处理的一遍。
* 同时只要 邻接点的路径信息满足 pathMap.get(nowNode).dv + weight < adjVetexInfo.dv 就表示该邻接点可以由更短的路径过来。
* 因此更新其pv和dv值,不论该邻接点是否known。
* 因此分析的时间界限为O(E),跟顶点数无关
* 测试通过。
*
* @param startVetex 输入节点
* @param endVetex 输出节点
* @return
*/
public List<Edge<T>> getShortestPathWithWeightedGraph(T startVetex, T endVetex){
/**
* 方法内部类
* @author 25040
*
*/
class pathInfo{
//标记节点是否被标记过了
boolean known;
//记录当前