Java圖論的兩個(gè)基本概念之有向圖與無向圖詳解
圖(Graph)是計(jì)算機(jī)科學(xué)中一種重要的非線性數(shù)據(jù)結(jié)構(gòu),廣泛應(yīng)用于社交網(wǎng)絡(luò)、地圖導(dǎo)航、任務(wù)調(diào)度等領(lǐng)域。本文將使用Java語言深入講解圖論中的兩個(gè)基本概念:有向圖和無向圖。
什么是圖?
圖由兩個(gè)基本元素組成:
頂點(diǎn)(Vertex/Node):圖中的節(jié)點(diǎn),代表對(duì)象
邊(Edge):連接兩個(gè)頂點(diǎn),代表對(duì)象之間的關(guān)系
數(shù)學(xué)表示:G = (V, E),其中V是頂點(diǎn)集合,E是邊集合。
無向圖(Undirected Graph)
概念與特點(diǎn)
無向圖中的邊沒有方向,表示對(duì)稱的雙向關(guān)系。如果頂點(diǎn)A和頂點(diǎn)B之間存在一條邊,那么可以從A到達(dá)B,也可以從B到達(dá)A。
實(shí)際應(yīng)用
社交網(wǎng)絡(luò):微信好友關(guān)系(互為好友)
交通網(wǎng)絡(luò):城市間的雙向道路
協(xié)作關(guān)系:項(xiàng)目團(tuán)隊(duì)成員之間的合作關(guān)系
網(wǎng)絡(luò)拓?fù)?/strong>:局域網(wǎng)中設(shè)備的連接關(guān)系
Java實(shí)現(xiàn):鄰接表方式
import java.util.*;
?
/**
* 無向圖的實(shí)現(xiàn)(使用鄰接表)
*/
public class UndirectedGraph {
// 使用HashMap存儲(chǔ)圖,key為頂點(diǎn),value為鄰接頂點(diǎn)列表
private Map<String, List<String>> adjList;
public UndirectedGraph() {
this.adjList = new HashMap<>();
}
/**
* 添加頂點(diǎn)
*/
public void addVertex(String vertex) {
adjList.putIfAbsent(vertex, new ArrayList<>());
}
/**
* 添加邊(無向邊,需要雙向添加)
*/
public void addEdge(String v1, String v2) {
// 確保兩個(gè)頂點(diǎn)都存在
addVertex(v1);
addVertex(v2);
// 添加雙向邊
adjList.get(v1).add(v2);
adjList.get(v2).add(v1);
}
/**
* 移除邊
*/
public void removeEdge(String v1, String v2) {
List<String> v1Neighbors = adjList.get(v1);
List<String> v2Neighbors = adjList.get(v2);
if (v1Neighbors != null) {
v1Neighbors.remove(v2);
}
if (v2Neighbors != null) {
v2Neighbors.remove(v1);
}
}
/**
* 移除頂點(diǎn)
*/
public void removeVertex(String vertex) {
// 移除所有與該頂點(diǎn)相連的邊
List<String> neighbors = adjList.get(vertex);
if (neighbors != null) {
for (String neighbor : new ArrayList<>(neighbors)) {
removeEdge(vertex, neighbor);
}
}
// 移除頂點(diǎn)
adjList.remove(vertex);
}
/**
* 獲取頂點(diǎn)的度(連接的邊的數(shù)量)
*/
public int getDegree(String vertex) {
List<String> neighbors = adjList.get(vertex);
return neighbors != null ? neighbors.size() : 0;
}
/**
* 獲取鄰接頂點(diǎn)
*/
public List<String> getNeighbors(String vertex) {
return adjList.getOrDefault(vertex, new ArrayList<>());
}
/**
* 深度優(yōu)先搜索(DFS)
*/
public void dfs(String start) {
Set<String> visited = new HashSet<>();
dfsHelper(start, visited);
}
private void dfsHelper(String vertex, Set<String> visited) {
visited.add(vertex);
System.out.print(vertex + " ");
for (String neighbor : getNeighbors(vertex)) {
if (!visited.contains(neighbor)) {
dfsHelper(neighbor, visited);
}
}
}
/**
* 廣度優(yōu)先搜索(BFS)
*/
public void bfs(String start) {
Set<String> visited = new HashSet<>();
Queue<String> queue = new LinkedList<>();
visited.add(start);
queue.offer(start);
while (!queue.isEmpty()) {
String vertex = queue.poll();
System.out.print(vertex + " ");
for (String neighbor : getNeighbors(vertex)) {
if (!visited.contains(neighbor)) {
visited.add(neighbor);
queue.offer(neighbor);
}
}
}
}
/**
* 打印圖結(jié)構(gòu)
*/
public void display() {
System.out.println("無向圖結(jié)構(gòu):");
for (Map.Entry<String, List<String>> entry : adjList.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
}無向圖使用示例
public class UndirectedGraphDemo {
public static void main(String[] args) {
UndirectedGraph graph = new UndirectedGraph();
// 構(gòu)建社交網(wǎng)絡(luò)
graph.addEdge("Alice", "Bob");
graph.addEdge("Alice", "Charlie");
graph.addEdge("Bob", "David");
graph.addEdge("Charlie", "David");
graph.addEdge("David", "Eve");
// 顯示圖結(jié)構(gòu)
graph.display();
System.out.println("\n各頂點(diǎn)的度:");
System.out.println("Alice的度: " + graph.getDegree("Alice"));
System.out.println("David的度: " + graph.getDegree("David"));
System.out.println("\n深度優(yōu)先搜索(從Alice開始):");
graph.dfs("Alice");
System.out.println("\n\n廣度優(yōu)先搜索(從Alice開始):");
graph.bfs("Alice");
}
}有向圖(Directed Graph)
概念與特點(diǎn)
有向圖中的邊具有方向性,從一個(gè)頂點(diǎn)指向另一個(gè)頂點(diǎn)。邊<A, B>表示從A到B的單向關(guān)系,不代表可以從B到A。
實(shí)際應(yīng)用
微博關(guān)注:用戶A關(guān)注用戶B,但B不一定關(guān)注A
網(wǎng)頁鏈接:網(wǎng)頁間的超鏈接關(guān)系
任務(wù)依賴:項(xiàng)目中任務(wù)的先后順序
交通系統(tǒng):單行道網(wǎng)絡(luò)
課程prerequisite:課程的先修關(guān)系
Java實(shí)現(xiàn):鄰接表方式
import java.util.*;
?
/**
* 有向圖的實(shí)現(xiàn)(使用鄰接表)
*/
public class DirectedGraph {
private Map<String, List<String>> adjList;
public DirectedGraph() {
this.adjList = new HashMap<>();
}
/**
* 添加頂點(diǎn)
*/
public void addVertex(String vertex) {
adjList.putIfAbsent(vertex, new ArrayList<>());
}
/**
* 添加有向邊(從v1指向v2)
*/
public void addEdge(String from, String to) {
addVertex(from);
addVertex(to);
// 只添加單向邊
adjList.get(from).add(to);
}
/**
* 移除邊
*/
public void removeEdge(String from, String to) {
List<String> neighbors = adjList.get(from);
if (neighbors != null) {
neighbors.remove(to);
}
}
/**
* 移除頂點(diǎn)
*/
public void removeVertex(String vertex) {
// 移除從該頂點(diǎn)出發(fā)的所有邊
adjList.remove(vertex);
// 移除指向該頂點(diǎn)的所有邊
for (List<String> neighbors : adjList.values()) {
neighbors.remove(vertex);
}
}
/**
* 獲取出度(從該頂點(diǎn)出發(fā)的邊數(shù))
*/
public int getOutDegree(String vertex) {
List<String> neighbors = adjList.get(vertex);
return neighbors != null ? neighbors.size() : 0;
}
/**
* 獲取入度(指向該頂點(diǎn)的邊數(shù))
*/
public int getInDegree(String vertex) {
int count = 0;
for (List<String> neighbors : adjList.values()) {
if (neighbors.contains(vertex)) {
count++;
}
}
return count;
}
/**
* 獲取鄰接頂點(diǎn)(出邊指向的頂點(diǎn))
*/
public List<String> getNeighbors(String vertex) {
return adjList.getOrDefault(vertex, new ArrayList<>());
}
/**
* 拓?fù)渑判颍↘ahn算法)
* 適用于有向無環(huán)圖(DAG)
*/
public List<String> topologicalSort() {
List<String> result = new ArrayList<>();
Map<String, Integer> inDegree = new HashMap<>();
Queue<String> queue = new LinkedList<>();
// 計(jì)算所有頂點(diǎn)的入度
for (String vertex : adjList.keySet()) {
inDegree.put(vertex, getInDegree(vertex));
}
// 將入度為0的頂點(diǎn)加入隊(duì)列
for (Map.Entry<String, Integer> entry : inDegree.entrySet()) {
if (entry.getValue() == 0) {
queue.offer(entry.getKey());
}
}
// BFS處理
while (!queue.isEmpty()) {
String vertex = queue.poll();
result.add(vertex);
// 減少鄰接頂點(diǎn)的入度
for (String neighbor : getNeighbors(vertex)) {
inDegree.put(neighbor, inDegree.get(neighbor) - 1);
if (inDegree.get(neighbor) == 0) {
queue.offer(neighbor);
}
}
}
// 如果結(jié)果包含所有頂點(diǎn),說明沒有環(huán)
if (result.size() != adjList.size()) {
System.out.println("圖中存在環(huán),無法進(jìn)行拓?fù)渑判颍?);
return new ArrayList<>();
}
return result;
}
/**
* 檢測是否存在環(huán)(使用DFS)
*/
public boolean hasCycle() {
Set<String> visited = new HashSet<>();
Set<String> recStack = new HashSet<>();
for (String vertex : adjList.keySet()) {
if (hasCycleHelper(vertex, visited, recStack)) {
return true;
}
}
return false;
}
private boolean hasCycleHelper(String vertex, Set<String> visited,
Set<String> recStack) {
if (recStack.contains(vertex)) {
return true; // 發(fā)現(xiàn)環(huán)
}
if (visited.contains(vertex)) {
return false;
}
visited.add(vertex);
recStack.add(vertex);
for (String neighbor : getNeighbors(vertex)) {
if (hasCycleHelper(neighbor, visited, recStack)) {
return true;
}
}
recStack.remove(vertex);
return false;
}
/**
* 深度優(yōu)先搜索
*/
public void dfs(String start) {
Set<String> visited = new HashSet<>();
dfsHelper(start, visited);
}
private void dfsHelper(String vertex, Set<String> visited) {
visited.add(vertex);
System.out.print(vertex + " ");
for (String neighbor : getNeighbors(vertex)) {
if (!visited.contains(neighbor)) {
dfsHelper(neighbor, visited);
}
}
}
/**
* 打印圖結(jié)構(gòu)
*/
public void display() {
System.out.println("有向圖結(jié)構(gòu):");
for (Map.Entry<String, List<String>> entry : adjList.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
}有向圖使用示例
public class DirectedGraphDemo {
public static void main(String[] args) {
DirectedGraph graph = new DirectedGraph();
// 構(gòu)建課程依賴關(guān)系圖
graph.addEdge("數(shù)據(jù)結(jié)構(gòu)", "算法");
graph.addEdge("離散數(shù)學(xué)", "數(shù)據(jù)結(jié)構(gòu)");
graph.addEdge("離散數(shù)學(xué)", "算法");
graph.addEdge("程序設(shè)計(jì)", "數(shù)據(jù)結(jié)構(gòu)");
graph.addEdge("算法", "人工智能");
graph.addEdge("數(shù)據(jù)結(jié)構(gòu)", "數(shù)據(jù)庫");
// 顯示圖結(jié)構(gòu)
graph.display();
System.out.println("\n各頂點(diǎn)的入度和出度:");
System.out.println("數(shù)據(jù)結(jié)構(gòu) - 入度: " + graph.getInDegree("數(shù)據(jù)結(jié)構(gòu)") +
", 出度: " + graph.getOutDegree("數(shù)據(jù)結(jié)構(gòu)"));
System.out.println("算法 - 入度: " + graph.getInDegree("算法") +
", 出度: " + graph.getOutDegree("算法"));
System.out.println("\n檢測環(huán):");
System.out.println("是否存在環(huán): " + graph.hasCycle());
System.out.println("\n拓?fù)渑判颍ㄕn程學(xué)習(xí)順序):");
List<String> order = graph.topologicalSort();
System.out.println(order);
System.out.println("\n深度優(yōu)先搜索(從離散數(shù)學(xué)開始):");
graph.dfs("離散數(shù)學(xué)");
}
}鄰接矩陣實(shí)現(xiàn)
除了鄰接表,圖還可以用鄰接矩陣表示,特別適合稠密圖。
/**
* 使用鄰接矩陣實(shí)現(xiàn)的圖(支持有向圖和無向圖)
*/
public class GraphMatrix {
private int[][] matrix;
private Map<String, Integer> vertexIndex;
private Map<Integer, String> indexVertex;
private int vertexCount;
private boolean isDirected;
public GraphMatrix(int maxVertices, boolean isDirected) {
this.matrix = new int[maxVertices][maxVertices];
this.vertexIndex = new HashMap<>();
this.indexVertex = new HashMap<>();
this.vertexCount = 0;
this.isDirected = isDirected;
}
/**
* 添加頂點(diǎn)
*/
public void addVertex(String vertex) {
if (!vertexIndex.containsKey(vertex)) {
vertexIndex.put(vertex, vertexCount);
indexVertex.put(vertexCount, vertex);
vertexCount++;
}
}
/**
* 添加邊
*/
public void addEdge(String v1, String v2) {
addVertex(v1);
addVertex(v2);
int index1 = vertexIndex.get(v1);
int index2 = vertexIndex.get(v2);
matrix[index1][index2] = 1;
// 如果是無向圖,需要添加反向邊
if (!isDirected) {
matrix[index2][index1] = 1;
}
}
/**
* 檢查是否存在邊
*/
public boolean hasEdge(String v1, String v2) {
Integer index1 = vertexIndex.get(v1);
Integer index2 = vertexIndex.get(v2);
if (index1 == null || index2 == null) {
return false;
}
return matrix[index1][index2] == 1;
}
/**
* 打印鄰接矩陣
*/
public void display() {
System.out.println("鄰接矩陣:");
System.out.print(" ");
for (int i = 0; i < vertexCount; i++) {
System.out.printf("%-8s", indexVertex.get(i));
}
System.out.println();
for (int i = 0; i < vertexCount; i++) {
System.out.printf("%-4s", indexVertex.get(i));
for (int j = 0; j < vertexCount; j++) {
System.out.printf("%-8d", matrix[i][j]);
}
System.out.println();
}
}
}有向圖 vs 無向圖對(duì)比
| 特性 | 無向圖 | 有向圖 |
|---|---|---|
| 邊的表示 | (v1, v2) 無序?qū)?/td> | <v1, v2> 有序?qū)?/td> |
| 方向性 | 雙向,對(duì)稱關(guān)系 | 單向,非對(duì)稱關(guān)系 |
| 度的概念 | 度(Degree) | 入度和出度 |
| 最大邊數(shù) | n(n-1)/2 | n(n-1) |
| 存儲(chǔ)開銷 | 鄰接矩陣對(duì)稱 | 鄰接矩陣不對(duì)稱 |
| 特有算法 | 最小生成樹 | 拓?fù)渑判?、?qiáng)連通分量 |
圖的表示方法選擇
鄰接表 vs 鄰接矩陣
鄰接表
空間復(fù)雜度:O(V + E)
適合稀疏圖(邊少)
遍歷某個(gè)頂點(diǎn)的鄰接點(diǎn)快
判斷兩點(diǎn)是否相鄰較慢
鄰接矩陣
空間復(fù)雜度:O(V²)
適合稠密圖(邊多)
判斷兩點(diǎn)是否相鄰快O(1)
浪費(fèi)空間存儲(chǔ)不存在的邊
常用圖算法
遍歷算法
深度優(yōu)先搜索(DFS):遞歸或棧實(shí)現(xiàn)
廣度優(yōu)先搜索(BFS):隊(duì)列實(shí)現(xiàn)
最短路徑算法
Dijkstra算法:單源最短路徑(非負(fù)權(quán)重)
Bellman-Ford算法:單源最短路徑(可處理負(fù)權(quán)重)
Floyd-Warshall算法:所有頂點(diǎn)對(duì)之間的最短路徑
有向圖特有算法
拓?fù)渑判?/strong>:有向無環(huán)圖的線性排序
強(qiáng)連通分量:Kosaraju算法、Tarjan算法
無向圖特有算法
最小生成樹:Kruskal算法、Prim算法
連通性檢測:并查集
性能優(yōu)化建議
使用泛型:讓圖結(jié)構(gòu)更靈活,支持不同類型的頂點(diǎn)
權(quán)重邊:可以擴(kuò)展邊的數(shù)據(jù)結(jié)構(gòu),存儲(chǔ)權(quán)重信息
線程安全:多線程環(huán)境下使用ConcurrentHashMap
內(nèi)存優(yōu)化:大規(guī)模圖可以考慮使用位圖或壓縮存儲(chǔ)
總結(jié)
有向圖和無向圖是圖論的基礎(chǔ),掌握它們的Java實(shí)現(xiàn)對(duì)于解決復(fù)雜的網(wǎng)絡(luò)問題至關(guān)重要。選擇合適的圖類型和數(shù)據(jù)結(jié)構(gòu),能夠讓算法更高效:
對(duì)稱關(guān)系用無向圖(社交、網(wǎng)絡(luò)拓?fù)洌?/p>
非對(duì)稱關(guān)系用有向圖(依賴、關(guān)注、鏈接)
稀疏圖用鄰接表
稠密圖用鄰接矩陣
希望本文能幫助你深入理解圖的概念和Java實(shí)現(xiàn),為學(xué)習(xí)更高級(jí)的圖算法打下堅(jiān)實(shí)基礎(chǔ)!
到此這篇關(guān)于Java圖論的兩個(gè)基本概念之有向圖與無向圖的文章就介紹到這了,更多相關(guān)Java圖論有向圖與無向圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot-Starter造輪子之自動(dòng)鎖組件lock-starter實(shí)現(xiàn)
這篇文章主要為大家介紹了Springboot-Starter造輪子之自動(dòng)鎖組件lock-starter實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
Spring-MVC異步請(qǐng)求之Servlet異步處理
這篇文章主要介紹了Spring-MVC異步請(qǐng)求之Servlet異步處理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-01-01
詳解Maven 搭建spring boot多模塊項(xiàng)目(附源碼)
這篇文章主要介紹了詳解Maven 搭建spring boot多模塊項(xiàng)目(附源碼),具有一定的參考價(jià)值,有興趣的可以了解一下2017-09-09
Spring?Boot整合log4j2日志配置的詳細(xì)教程
這篇文章主要介紹了SpringBoot項(xiàng)目中整合Log4j2日志框架的步驟和配置,包括常用日志框架的比較、配置參數(shù)介紹、Log4j2配置詳解以及使用步驟,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-02-02
關(guān)于SpringBoot獲取IOC容器中注入的Bean(推薦)
本文通過實(shí)例代碼給大家詳解了springboot獲取ioc容器中注入的bean問題,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2018-05-05

