【戀上數據結構】圖代碼實現、BFS、DFS、拓撲排序

圖的基礎代碼

圖的基礎接口

package com.mj.graph;

import java.util.List;

public interface Graph<V, E> {
	int edgesSize(); 		// 邊的數量
	int verticesSize();		// 頂點數量
	
	void addVertex(V v); 		// 添加頂點
	void addEdge(V from, V to); // 添加邊
	void addEdge(V from, V to, E weight);// 添加邊
	
	void removeVertex(V v); 		// 刪除頂點
	void removeEdge(V from, V to);	 // 刪除邊
	
	interface vertexVisitor<V>{
		boolean visit(V v);
	}
	
 }

頂點Vertex

/**
* 頂點
 */
private static class Vertex<V, E> {
	V value;
	Set<Edge<V, E>> inEdges = new HashSet<>(); // 進來的邊
	Set<Edge<V, E>> outEdges = new HashSet<>(); // 出去的邊
	public Vertex(V value){
		this.value = value;
	}
	@Override
	public boolean equals(Object obj) {
		return Objects.equals(value, ((Vertex<V, E>)obj).value);
	}
	@Override
	public int hashCode() {
		return value == null ? 0 : value.hashCode();
	}
	@Override
	public String toString() {
		return value == null ? "null" : value.toString();
	}
	
}

邊Edge

/*
 * 邊
 */
private static class Edge<V, E> {
	Vertex<V, E> from; // 出發點
	Vertex<V, E> to; // 到達點
	E weight;	// 權值
	
	public Edge(Vertex<V, E> from, Vertex<V, E> to) {
		this.from = from;
		this.to = to;
	}
	@Override
	public boolean equals(Object obj) {
		Edge<V, E> edge = (Edge<V, E>) obj;
		return Objects.equals(from, edge.from) && Objects.equals(to, edge.to);
	}
	@Override
	public int hashCode() {
		return from.hashCode() * 31 + to.hashCode();
	}
	@Override
	public String toString() {
		return "Edge [from=" + from + ", to=" + to + ", weight=" + weight + "]";
	}
	
}

一些稍微複雜的操作單獨列出來,簡單地操作直接從完整源碼中查看即可。

添加邊addEdge

/**
 * 添加無權值的邊
 */
@Override
public void addEdge(V from, V to) {
	addEdge(from, to, null);
}

/*
 * 添加有權值的邊
 */
@Override
public void addEdge(V from, V to, E weight) {
	// 根據傳入的參數from找到出發點,如果不存在則創建
	Vertex<V, E> fromVertex = vertices.get(from);
	if(fromVertex == null){ 
		fromVertex = new Vertex<>(from);
		vertices.put(from, fromVertex);
	}
	// 根據傳入的參數to找到終點,如果不存在則創建
	Vertex<V, E> toVertex = vertices.get(to);
	if(toVertex == null){
		toVertex = new Vertex<>(to);
		vertices.put(to, toVertex);
	}
	
	// 根據出發點與終點,創建邊
	Edge<V, E> edge = new Edge<>(fromVertex, toVertex);
	edge.weight = weight; // 有權值則加上權值,無權值則爲null
	
	// 不管原來是否存在,都先刪除此邊,再添加進去
	if(fromVertex.outEdges.remove(edge)){ 
		toVertex.inEdges.remove(edge);
		edges.remove(edge);
	}
	fromVertex.outEdges.add(edge);
	toVertex.inEdges.add(edge);
	edges.add(edge);
}

刪除邊removeEdge

/*
 * 刪除邊
 */
@Override
public void removeEdge(V from, V to) {
	// 根據傳入的from獲得起點,不存在則不需要刪除
	Vertex<V, E> fromVertex = vertices.get(from);
	if(fromVertex == null) return;
	// 根據傳入的to找到終點,不存在則不需要刪除
	Vertex<V, E> toVertex = vertices.get(to);
	if(toVertex == null) return;
	
	// 根據起點和終點獲得邊,然後刪除
	Edge<V, E> edge = new Edge<>(fromVertex, toVertex);
	if(fromVertex.outEdges.remove(edge)){
		toVertex.inEdges.remove(edge);
		edges.remove(edge);
	}
}

刪除點removeVertex

/*
 * 刪除點
 */
@Override
public void removeVertex(V v) {
	// 根據傳入的值找到點並刪除,不存在則不做操作
	Vertex<V, E> vertex = vertices.remove(v);
	if(vertex == null) return;
	
	// 迭代器遍歷集合vertex.outEdges
	for (Iterator<Edge<V, E>> iterator = vertex.outEdges.iterator(); iterator.hasNext();) {
		Edge<V, E> edge = iterator.next(); // 遍歷到的該點出去的邊
		edge.to.inEdges.remove(edge);// 獲取終點進入的邊,並從中刪除遍歷到的邊
		iterator.remove(); // 將當前遍歷到的元素edge從集合vertex.outEdges中刪掉
		edges.remove(edge);
	}
	
	// 迭代器遍歷集合vertex.inEdges
	for (Iterator<Edge<V, E>> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {
		Edge<V, E> edge = iterator.next(); // 遍歷到的進入該點的邊
		edge.from.outEdges.remove(edge); // 獲取起點出去的邊,並從中刪除遍歷到的邊
		iterator.remove(); // 將當前遍歷到的元素edge從集合vertex.inEdges中刪掉
		edges.remove(edge);
	}
	
}

完整源碼

/**
 * 鄰接表實現圖
 */
@SuppressWarnings("unchecked")
public class ListGraph<V, E> implements Graph<V, E> {
	// 傳入的V與頂點類Vertex的映射
	private Map<V, Vertex<V, E>> vertices = new HashMap<>();
	// 邊的Set集合
	private Set<Edge<V, E>> edges = new HashSet<>();
	
	/**
	 * 頂點
	 */
	private static class Vertex<V, E> {
		V value;
		Set<Edge<V, E>> inEdges = new HashSet<>(); // 進來的邊
		Set<Edge<V, E>> outEdges = new HashSet<>(); // 出去的邊
		public Vertex(V value){
			this.value = value;
		}
		@Override
		public boolean equals(Object obj) {
			return Objects.equals(value, ((Vertex<V, E>)obj).value);
		}
		@Override
		public int hashCode() {
			return value == null ? 0 : value.hashCode();
		}
		@Override
		public String toString() {
			return value == null ? "null" : value.toString();
		}
		
	}
	/**
	 * 邊
	 */
	private static class Edge<V, E> {
		Vertex<V, E> from; // 出發點
		Vertex<V, E> to; // 到達點
		E weight;	// 權值
		
		public Edge(Vertex<V, E> from, Vertex<V, E> to) {
			this.from = from;
			this.to = to;
		}
		@Override
		public boolean equals(Object obj) {
			Edge<V, E> edge = (Edge<V, E>) obj;
			return Objects.equals(from, edge.from) && Objects.equals(to, edge.to);
		}
		@Override
		public int hashCode() {
			return from.hashCode() * 31 + to.hashCode();
		}
		@Override
		public String toString() {
			return "Edge [from=" + from + ", to=" + to + ", weight=" + weight + "]";
		}
		
	}
	
	public void print(){
		System.out.println("[頂點]-------------------");
		vertices.forEach((V v, Vertex<V, E> vertex) -> {
			System.out.println(v);
			System.out.println("out-----------");
			System.out.println(vertex.outEdges);
			System.out.println("int-----------");
			System.out.println(vertex.inEdges);
		});
		System.out.println("[邊]-------------------");
		edges.forEach((Edge<V, E> edge) -> {
			System.out.println(edge);
		});
	}
	
	@Override
	public int edgesSize() {
		return edges.size();
	}

	@Override
	public int verticesSize() {
		return vertices.size();
	}

	@Override
	public void addVertex(V v) {
		if(vertices.containsKey(v)) return;
		vertices.put(v, new Vertex<>(v));
	}

	@Override
	public void addEdge(V from, V to) {
		addEdge(from, to, null);
	}

	@Override
	public void addEdge(V from, V to, E weight) {
		// 根據傳入的參數from找到起點,如果不存在則創建
		Vertex<V, E> fromVertex = vertices.get(from);
		if(fromVertex == null){ 
			fromVertex = new Vertex<>(from);
			vertices.put(from, fromVertex);
		}
		// 根據傳入的參數to找到終點,如果不存在則創建
		Vertex<V, E> toVertex = vertices.get(to);
		if(toVertex == null){
			toVertex = new Vertex<>(to);
			vertices.put(to, toVertex);
		}
		
		// 根據出發點與終點,創建邊
		Edge<V, E> edge = new Edge<>(fromVertex, toVertex);
		edge.weight = weight; // 有權值則加上權值,無權值則爲null
		
		// 不管原來是否存在,都先刪除此邊,再添加進去
		if(fromVertex.outEdges.remove(edge)){ 
			toVertex.inEdges.remove(edge);
			edges.remove(edge);
		}
		fromVertex.outEdges.add(edge);
		toVertex.inEdges.add(edge);
		edges.add(edge);
	}
	
	@Override
	public void removeVertex(V v) {
		// 根據傳入的值找到點並刪除,不存在則不做操作
		Vertex<V, E> vertex = vertices.remove(v);
		if(vertex == null) return;
		
		// 迭代器遍歷集合vertex.outEdges
		for (Iterator<Edge<V, E>> iterator = vertex.outEdges.iterator(); iterator.hasNext();) {
			Edge<V, E> edge = iterator.next(); // 遍歷到的該點出去的邊
			edge.to.inEdges.remove(edge);// 獲取終點進入的邊,並從中刪除遍歷到的邊
			iterator.remove(); // 將當前遍歷到的元素edge從集合vertex.outEdges中刪掉
			edges.remove(edge);
		}
		
		// 迭代器遍歷集合vertex.inEdges
		for (Iterator<Edge<V, E>> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {
			Edge<V, E> edge = iterator.next(); // 遍歷到的進入該點的邊
			edge.from.outEdges.remove(edge); // 獲取起點出去的邊,並從中刪除遍歷到的邊
			iterator.remove(); // 將當前遍歷到的元素edge從集合vertex.inEdges中刪掉
			edges.remove(edge);
		}
		
	}

	@Override
	public void removeEdge(V from, V to) {
		// 根據傳入的from獲得起點,不存在則不需要刪除
		Vertex<V, E> fromVertex = vertices.get(from);
		if(fromVertex == null) return;
		// 根據傳入的to找到終點,不存在則不需要刪除
		Vertex<V, E> toVertex = vertices.get(to);
		if(toVertex == null) return;
		
		// 根據起點和終點獲得邊,然後刪除
		Edge<V, E> edge = new Edge<>(fromVertex, toVertex);
		if(fromVertex.outEdges.remove(edge)){
			toVertex.inEdges.remove(edge);
			edges.remove(edge);
		}
	}
}

圖的遍歷

圖的遍歷

  • 從圖中某一頂點出發訪問圖中其餘頂點,且每一個頂點僅被訪問一次

圖有2種常見的遍歷方式(有向圖、無向圖都適用)

  • 廣度優先搜索(Breadth First Search,BFS),又稱爲寬度優先搜索橫向優先搜索
  • 深度優先搜索(Depth First Search,DFS)
    發明“深度優先搜索”算法的2位科學家在1986年共同獲得計算機領域的最高獎:圖靈獎。

在接口中增加 bfsdfs 方法。

package com.mj.graph;

import java.util.List;

public interface Graph<V, E> {
	int edgesSize(); // 邊的數量
	int verticesSize();	// 頂點數量
	
	void addVertex(V v); // 添加頂點
	void addEdge(V from, V to); // 添加邊
	void addEdge(V from, V to, E weight);// 添加邊
	
	void removeVertex(V v); // 刪除頂點
	void removeEdge(V from, V to); // 刪除邊
	
	void bfs(V begin, vertexVisitor<V> visitor); // 廣度優先搜索
	void dfs(V begin, vertexVisitor<V> visitor); // 深度優先搜索
	
	List<V> topologicalSort(); // 拓撲排序
	
	interface vertexVisitor<V>{
		boolean visit(V v);
	}
	
 }

廣度優先搜索(Breadth First Search)思路與實現

之前所學的二叉樹層序遍歷就是一種廣度優先搜索

注:BFS結果不唯一
在這裏插入圖片描述
在這裏插入圖片描述
思路
在這裏插入圖片描述
從某個點開始,將它可以到達的點放入隊列,如果已經訪問過則跳過,然後從隊列中取出點重複該過程。

  • 第一層:假設從點A開始,它可以到達B、F,則將B、F入隊
    此時隊列中元素 [B、F]
  • 第二層:隊頭B出隊,B可以到達C、I、G,將C、I、G入隊
    此時隊列中元素 [F、C、I、G]
  • 第三層:隊頭F出隊,F可以到達G、E,但G已訪問過,將E入隊
    此時隊列中元素 [C、I、G、E]
  • 第四層:隊頭C出隊,C可以到達I、D,但I已訪問過,將D入隊
  • 此時隊列中元素 [I、G、E、D]
  • 第五層:隊頭I出隊,I可以到達D,但D已訪問過,不執行操作。
    此時隊列中元素 [G、E、D]
  • 第六層:隊頭G出隊,G可以到達D、H,但D已訪問過,將H入隊
    此時隊列中元素 [E、D、H]
  • 第七層:隊頭E出隊,E可以到達D、H、F,都訪問過,不執行操作。
    此時隊列中元素 [D、H]
  • 第八層:隊頭D出隊,D可以到達C、H、E,都訪問過,不執行操作。
    此時隊列中元素 [H]
  • 第九層:隊頭H出隊,H可以到達D、G、E,都訪問過,不執行操作。
    此時隊列中元素 []
  • 隊列爲空,廣度優先搜索結束。

實現

/**
 * 廣度優先搜索BFS
 */
public void bfs(V begin, vertexVisitor<V> visitor) {
	if(visitor == null) return;
	// 根據傳入的值begin找到頂點
	Vertex<V, E> beginVertex = vertices.get(begin);
	if(beginVertex == null) return; // 該頂點不存在,不做操作
	
	// 存放已經訪問過的節點
	Set<Vertex<V, E>> visitedVertices = new HashSet<>();		
	Queue<Vertex<V, E>> queue = new LinkedList<>();
	queue.offer(beginVertex); // 元素入隊
	visitedVertices.add(beginVertex);
	
	// 思路參考二叉樹層次遍歷,隊列存放每一層的頂點,用集合記錄已經訪問過的點
	while(!queue.isEmpty()){
		Vertex<V, E> vertex = queue.poll(); // 隊列中取出一個頂點
		if(visitor.visit(vertex.value)) return;
		// 遍歷[隊列中取出的頂點]的出去的邊,將[這些邊的終點]入隊,並且標記爲已經訪問過
		for(Edge<V, E> edge : vertex.outEdges){
			// 如果集合中已經記錄該頂點,說明已經訪問過,跳過進行下一輪
			if(visitedVertices.contains(edge.to)) continue;
			queue.offer(edge.to);
			visitedVertices.add(edge.to);
		}
	}
	
}

深度優先搜索(Depth First Search)

之前所學的二叉樹前序遍歷就是一種深度優先搜索

注:DFS結果不唯一。
在這裏插入圖片描述
在這裏插入圖片描述

遞歸實現

/**
 * 遞歸實現深度優先搜索DFS
 */
public void dfs(V begin) {
	Vertex<V, E> beginVertex = vertices.get(begin); // 根據傳入的值獲取頂點
	if (beginVertex == null) return; // 頂點不存在則不執行操作
	dfs2(beginVertex, new HashSet<>()); // 傳入的集合,用來記錄訪問過的頂點
}
private void dfs(Vertex<V, E> vertex, Set<Vertex<V, E>> vistedVertices){
	System.out.println(vertex.value);
	vistedVertices.add(vertex);
	
	for(Edge<V, E> edge : vertex.outEdges){
		if(vistedVertices.contains(edge.to)) continue;
		dfs2(edge.to, vistedVertices);
	}
}

非遞歸思路與實現

在這裏插入圖片描述

/**
 * 非遞歸實現深度優先搜索DFS
 */
public void dfs(V begin, vertexVisitor<V> visitor){
	if(visitor == null) return;
	
	Vertex<V, E> beginVertex = vertices.get(begin);
	if(begin == null) return;
	
	Set<Vertex<V, E>> visitedVertices = new HashSet<>();
	Stack<Vertex<V, E>> stack = new Stack<>();
	
	stack.push(beginVertex); // 先訪問起點
	visitedVertices.add(beginVertex);
	if(visitor.visit(begin)) return;
	
	while(!stack.isEmpty()){
		Vertex<V, E> vertex = stack.pop();
		
		for(Edge<V, E> edge : vertex.outEdges){
			if(visitedVertices.contains(edge.to)) continue;
			
			stack.push(edge.from);
			stack.push(edge.to);
			visitedVertices.add(edge.to);
			if(visitor.visit(edge.to.value)) return;
			
			break;
		}
	}
}

AOV網(Activity On Vertex Network)

一項大的工程常被分爲多個小的子工

  • 子工程之間可能存在一定的先後順序,即某些子工程必須在其他的一些子工程完成後才能開始。

在現代化管理中,人們常用有向圖描述和分析一項工程的計劃和實施過程子工程被稱爲活動(Activity)

  • 頂點表示活動有向邊表示活動之間的先後關係,這樣的圖簡稱爲 AOV 網

標準的AOV網必須是一個有向無環圖(Directed Acyclic Graph,簡稱 DAG)
在這裏插入圖片描述

拓撲排序(Topological Sort)

前驅活動:有向邊起點的活動稱爲終點的前驅活動

  • 只有當一個活動的前驅全部都完成後,這個活動才能進行

後繼活動:有向邊終點的活動稱爲起點的後繼活動

在這裏插入圖片描述

  • A 是 B 的前驅活動,B 是 A 的後繼活動
  • B 是 C 的前驅活動,C 是 B 的後繼活動

拓撲排序 - 思路

可以使用卡恩算法(Kahn於1962年提出)完成拓撲排序。

假設 L 是存放拓撲排序結果的列表:

  • ① 把所有入度爲 0 的頂點放入 L 中,然後把這些頂點從圖中去掉
    ② 重複操作 ①,直到找不到入度爲 0 的頂點
  • 如果此時 L 中的元素個數和頂點總數相同,說明拓撲排序完成
  • 如果此時 L 中的元素個數少於頂點總數,說明原圖中存在環,無法進行拓撲排序

在這裏插入圖片描述

實現

/**
 * 拓撲排序
 */
@Override
public List<V> topologicalSort() {
	List<V> list = new ArrayList<>();
	Queue<Vertex<V, E>> queue = new LinkedList<>();
	Map<Vertex<V, E>, Integer> ins = new HashMap<>();
	
	// 初始化(將度爲0的節點放入隊列)
	vertices.forEach((V v, Vertex<V, E> vertex) -> {
		int indegree = vertex.inEdges.size(); // 入度
		if(indegree == 0) { // 入度爲0,放入隊列
			queue.offer(vertex);
		} else { // 入度不爲0,用map記錄它的入度
			ins.put(vertex, indegree);
		}
	});	
	
	while(!queue.isEmpty()){ // 從隊列中取節點
		Vertex<V, E> vertex = queue.poll();
		list.add(vertex.value); // 放入返回結果中
		
		for (Edge<V, E> edge : vertex.outEdges){
			// 隊列中取出節點所通向節點的入度
			int toIndegree = ins.get(edge.to) - 1;
			if(toIndegree == 0) { // 入度爲0,放入隊列
				queue.offer(edge.to);
			} else { // 入度不爲0,用map記錄它的入度
				ins.put(edge.to, toIndegree);
			}
		}
	}
	
	return list;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章