圖代碼實現
圖的基礎代碼
圖的基礎接口
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年共同獲得計算機領域的最高獎:圖靈獎。
在接口中增加 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); // 刪除邊
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;
}