一、圖的基本概念
1、圖的組成及應用場景
圖由頂點(vertex)和邊(edge)組成,通常表示爲 G = (V, E) ,G表示一個圖,V是頂點集,E是邊集 。頂點集V有窮且非空 ,任意兩個頂點之間都可以用邊來表示它們之間的關係,邊集E可以是空的。
下面就是圖的應用:
2、圖的分類及基本概念
有向圖:邊有明確方向的圖。
無向圖:所有邊都沒有方向的圖。
混合圖:既有有方向的邊又有無方向的邊的圖。
有向無環圖:從任意頂點出發無法經過若干條邊回到該頂點的圖。
無向完全圖:任意兩個頂點之間都存在邊的圖。n個頂點的無向完全圖有(n-1) + (n-2) + (n-3) + ... + 2 + 1條邊
。
有向完全圖:任意兩個頂點之間都存在兩條相反邊的圖。n個頂點的有向完全圖有n x (n-1)條邊
。
入度:以該點爲終點的所有邊的條數。
出度:以該點爲起點的所有邊的條數。
連通圖:無向圖任意兩個頂點之間都可相互抵達(直接或間接都算)的圖。
強連通圖:有向圖任意兩個頂點之間都可相互抵達(直接或間接都算)的圖。
連通分量:無向圖的極大連通子圖。
強連通分量:有向圖的極大強連通子圖。
二、圖的構建
1、圖的設計
2、接口代碼
public interface Graph<E, V> {
int edgesSize();
int verticesSize();
void addVertex(E e);
void addEdge(E from, E to, V weight);
void addEdge(E from, E to);
void removeVertex(E e);
void removeEdge(E from, E to);
void bfs(E e);
void dfs(E e);
}
3、注意點
① 怎麼維護邊的信息和頂點的信息?頂點信息(頂點出度邊、入度邊、外部傳的對象)可用哈希表維護---勢必要重寫hashCode和equals函數,邊信息(起點、終點、權值)用集合足以。
② 怎麼刪除一條邊或一個頂點?刪除邊的時候先判斷便是否存在,存在就從起點的出度邊和終點的入度邊中分別刪除,再從總集合中刪除即可。刪除頂點的時候,需要先遍歷該頂點的出度邊,將所有出度邊終點的入度邊集合中刪除這條邊,同時也要刪除該頂點的出度邊,相當於變遍歷邊刪除,可以用java官方的迭代器完成。
4、搭建圖的代碼
public class ListGraph<E, V> implements Graph<E, V> {
private Map<E, Vertex<E, V>> vertices;
private Set<Edge<E, V>> edges;
private static class Vertex<E, V> {
E element;
Set<Edge<E, V>> outEdges = new HashSet<>();
Set<Edge<E, V>> inEdges = new HashSet<>();
public Vertex(E element) {
this.element = element;
}
@Override
public String toString() {
return "Vertex{" +
"element=" + element + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Vertex<?, ?> vertex = (Vertex<?, ?>) o;
return Objects.equals(element, vertex.element);
}
@Override
public int hashCode() {
return Objects.hash(element);
}
}
private static class Edge<E, V> {
Vertex<E, V> from, to;
V weight;
public Edge(Vertex<E, V> from, Vertex<E, V> to) {
this.from = from;
this.to = to;
}
public EdgeInfo<E, V> castToInfo() {
EdgeInfo<E, V> info = new EdgeInfo<>();
info.setFrom(this.from.element);
info.setTo(this.to.element);
info.setWeight(this.weight);
return info;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Edge<?, ?> edge = (Edge<?, ?>) o;
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.element +
", to=" + to.element +
", weight=" + weight +
'}';
}
}
public ListGraph() {
edges = new HashSet<>();
vertices = new HashMap<>();
}
@Override
public int edgesSize() {
return edges.size();
}
@Override
public int verticesSize() {
return vertices.size();
}
@Override
public void addVertex(E e) {
if (vertices.get(e) != null)
return;
vertices.put(e, new Vertex<>(e));
}
@Override
public void addEdge(E from, E to, V weight) {
Vertex<E, V> fromVertex = vertices.get(from);
Vertex<E, V> toVertex = vertices.get(to);
if (fromVertex == null) {
fromVertex = new Vertex<>(from);
vertices.put(from, fromVertex);
}
if (toVertex == null) {
toVertex = new Vertex<>(to);
vertices.put(to, toVertex);
}
Edge<E, V> edge = new Edge<>(fromVertex, toVertex);
edge.weight = weight;
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 addEdge(E from, E to) {
addEdge(from, to, null);
}
@Override
public void removeVertex(E e) {
Vertex<E, V> vertex = vertices.get(e);
if (vertex == null)
return;
//先遍歷頂點的出邊進行相應刪除(邊遍歷邊刪除需要用迭代器)
for (Iterator iterator = vertex.outEdges.iterator(); iterator.hasNext(); ) {
Edge<E, V> edge = (Edge<E, V>) iterator.next();
edge.to.inEdges.remove(edge);
iterator.remove(); //將當前遍歷到的元素從遍歷的集合中刪除
edges.remove(edge);
}
//再遍歷頂點的入邊進行相應刪除(邊遍歷邊刪除需要用迭代器)
for (Iterator iterator = vertex.inEdges.iterator(); iterator.hasNext(); ) {
Edge<E, V> edge = (Edge<E, V>) iterator.next();
edge.from.outEdges.remove(edge);
iterator.remove(); //將當前遍歷到的元素從遍歷的集合中刪除
edges.remove(edge);
}
}
@Override
public void removeEdge(E from, E to) {
Vertex<E, V> fromVertex = vertices.get(from);
Vertex<E, V> toVertex = vertices.get(to);
if (fromVertex == null || toVertex == null)
return;
Edge<E, V> edge = new Edge<>(fromVertex, toVertex);
if (edges.remove(edge)) {
fromVertex.outEdges.remove(edge);
toVertex.inEdges.remove(edge);
}
}
}
二、深度優先搜索
1、遞歸版思路
先訪問起點,再依次對起點的出度邊進行深度優先搜索。爲了避免重複訪問,還需要增加一個函數參數,用來記錄已經訪問過的頂點,訪問過的結點就不再訪問。當然遞歸結束的條件就是到達的頂點沒有出度邊爲止。
2、遞歸版實現
@Override
public void dfs(E e) {
Vertex<E, V> vertex = vertices.get(e);
if (vertex == null)
return;
dfs(vertex, new HashSet<Vertex<E, V>>());
}
/**
* 遞歸版深搜
*
* @param vertex 起點
* @param visitedVertices 訪問過的頂點集合
*/
private void dfs(Vertex<E, V> vertex, Set<Vertex<E, V>> visitedVertices) {
System.out.println(vertex.element);
visitedVertices.add(vertex);
for (Edge<E, V> edge : vertex.outEdges) {
if (visitedVertices.contains(edge.to))
continue;
dfs(edge.to, visitedVertices);
}
}
3、非遞歸版思路
利用棧模擬遞歸。先將起點入棧並訪問,再從棧頂彈出起點,以後每次當棧頂彈出頂點的時候立即選擇一條該點的出度邊(終點未訪問過),將這條邊的起點、終點依次壓入棧中,並訪問該終點。如此反覆,直到棧爲空。這裏也需要一個集合記錄訪問過的頂點。
4、非遞歸版實現
/**
* 非遞歸版深搜
*
* @param beginVertex 起點
* @param visitedVertices 訪問過的頂點集合
*/
private void dfs2(Vertex<E, V> beginVertex, Set<Vertex<E, V>> visitedVertices) {
Stack<Vertex<E, V>> stack = new Stack<>();
stack.push(beginVertex);
System.out.println(beginVertex.element);
visitedVertices.add(beginVertex);
while (!stack.isEmpty()) {
Vertex<E, V> vertex = stack.pop();
for (Edge<E, V> edge : vertex.outEdges) {
if (visitedVertices.contains(edge.to))
continue;
stack.push(edge.from);
stack.push(edge.to);
System.out.println(edge.to.element);
visitedVertices.add(edge.to);
break;
}
}
}
二、廣度優先搜索
1、BFS思路
和二叉樹的層次遍歷類似,利用隊列實現。先將起點入隊並訪問,再從隊列中出隊一個頂點,將該頂點所有出度邊的終點(未被訪問過的頂點)依次入隊並訪問,每次從隊列中出隊都是相同操作,直到隊列爲空。
2、BFS實現
@Override
public void bfs(E e) {
Vertex<E, V> startVertex = vertices.get(e);
if (startVertex == null)
return;
Queue<Vertex<E, V>> queue = new LinkedList<>();
Set<Vertex<E, V>> visitedVertices = new HashSet<>();
queue.offer(startVertex);
System.out.println(startVertex.element);
visitedVertices.add(startVertex);
while (!queue.isEmpty()) {
Vertex<E, V> vertex = queue.poll();
for (Edge<E, V> edge : vertex.outEdges) {
if (visitedVertices.contains(edge.to))
continue;
queue.offer(edge.to);
System.out.println(edge.to.element);
visitedVertices.add(edge.to);
}
}
}