数据结构与算法分析——java语言描述第三版图论部分java实现,使用邻接表实现图论相关算法。

本文介绍了使用Java实现《数据结构与算法分析》书中第九章的图论部分,通过邻接表来实现图的相关算法。包括广度优先搜索用于解决无圈图的单源最短路径问题和关键路径分析,深度优先搜索在无向图中寻找割点,以及欧拉环游和Prim、Kruskal最小生成树算法。
摘要由CSDN通过智能技术生成

这篇文章是笔者,学习《数据结构与算法分析——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;
			//记录当前
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值