1. 関連概念
- グラフ G(V,E): 頂点セット V とエッジ セット E で構成される、「多対多」の関係を表すことができるデータ構造。
- バーテックス:
- エッジ: 点 (v, w), v, w∈V のペア。エッジはアークとも呼ばれます。
- 隣接性: 点 v, w 隣接、エッジ (v, w) ∈ E の場合のみ。
- パス: 頂点シーケンス v 1、v 2、...、v nで構成され、パスの長さは n-1 です。
- 有向グラフ: エッジは単方向であり、点のペアは順序付けられています。
- 無向グラフ: エッジは双方向グラフで、点のペアは順序付けされていません。
- 加重グラフ: 異なる辺の重みが異なるグラフ。
- 接続: 1 つの頂点から他の頂点へのパスがある場合、無向グラフは接続されていると呼ばれます。
- 強い接続性: 有向グラフの頂点から他の頂点へのパスがあります。
- 弱い接続: 有向グラフ自体は強く接続されていませんが、エッジが方向を考慮していない無向グラフは接続されています。
- 完全なグラフ: 頂点のすべてのペアの間にエッジがあります。
2. グラフの表現
- 隣接行列: 2 次元配列を使用して実装されます. 配列の各要素は, グラフの 2 つの頂点間にエッジがあるかどうかを示します. またはエッジの重みを示します. 密なグラフに適しています. 疎なグラフの場合,この表現メソッドはスペースを無駄に使いすぎます。
package com.northsmile.graph;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Author:NorthSmile
* Create:2023/4/18 9:17
* 使用邻接矩阵表示图(无向图)
*/
public class Graph {
// 存储边及其权值
int[][] edges;
// 存储顶点
ArrayList<String> vertexes;
// 记录边的数目
int edgeNums;
public Graph(int n){
edges=new int[n][n];
vertexes=new ArrayList<>(n);
edgeNums=0;
}
/**
* 插入顶点
* @param vertex
*/
public void insertVertex(String vertex){
vertexes.add(vertex);
}
/**
* 插入边
* @param v1 边的一个顶点对应下标
* @param v2 边的另一个顶点对应下标
* @param w 边对应权值,默认为1
*/
public void insertEdge(int v1,int v2,int w){
edges[v1][v2]=w;
edges[v2][v1]=w;
edgeNums++;
}
/**
* 获取顶点数量
* @return
*/
public int getVertexesNum(){
return vertexes.size();
}
/**
* 获取边的数目
* @return
*/
public int getEdgesNum(){
return edgeNums;
}
/**
* 获取指定下标对应的顶点的值
* @param v
* @return
*/
public String getValByIndex(int v){
return vertexes.get(v);
}
/**
* 获取指定边的权值
* @param v1
* @param v2
* @return
*/
public int getWeightOfEdge(int v1,int v2){
return edges[v1][v2];
}
/**
* 显示表示图的邻接矩阵
*/
public void showGraph(){
for (int[] edge:edges){
System.out.println(Arrays.toString(edge));
}
}
}
package com.northsmile.graph;
/**
* Author:NorthSmile
* Create:2023/4/18 9:18
*/
public class GraphDemo {
public static void main(String[] args) {
int n=5;
String[] vertexes={
"A","B","C","D","E"};
Graph graph = new Graph(n);
// 插入顶点
for (String vertex:vertexes){
graph.insertVertex(vertex);
}
// 插入边
graph.insertEdge(0,1,1);
graph.insertEdge(0,2,1);
graph.insertEdge(1,2,1);
graph.insertEdge(1,3,1);
graph.insertEdge(1,4,1);
graph.showGraph();
}
}
- 隣接リスト:配列 + リンク リストによって実装され、スパース グラフ表現に適しています。グラフの標準的な表現方法です。頂点ごとに、すべての隣接ポイントをテーブルに格納します。
package com.northsmile.graph;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Author:NorthSmile
* Create:2023/4/18 11:12
* 使用邻接表表示图(有向图)
*/
public class GraphByAdjacenceList {
// 邻接表
HashMap<String,Vertex> list;
int vertexNums;
int edgeNums;
public GraphByAdjacenceList(int n){
list=new HashMap<>();
vertexNums=n;
edgeNums=0;
}
/**
* 插入顶点
* @param vertex
*/
public void insertVertex(String vertex){
list.put(vertex,new Vertex(vertexNums));
}
/**
* 插入边
* @param v 顶点
* @param w 邻接点
*/
public void insertEdge(String v,String w){
Vertex vertex = list.get(v);
vertex.table.add(w);
edgeNums++;
}
/**
* 获取顶点数量
* @return
*/
public int getVertexesNum(){
return list.size();
}
/**
* 获取边的数目
* @return
*/
public int getEdgesNum(){
return edgeNums;
}
/**
* 显示表示图的邻接表
*/
public void showGraph(){
for (String vertex:list.keySet()){
System.out.println(vertex+":"+list.get(vertex).table);
}
}
/**
* 顶点类
*/
class Vertex{
ArrayList<String> table;
public Vertex(int n){
table=new ArrayList<>(n/2);
}
}
}
package com.northsmile.graph;
/**
* Author:NorthSmile
* Create:2023/4/18 9:18
*/
public class GraphDemo {
public static void main(String[] args) {
// 2.邻接表
int n=5;
String[] vertexes={
"A","B","C","D","E"};
GraphByAdjacenceList graph = new GraphByAdjacenceList(n);
// 插入顶点
for (String vertex:vertexes){
graph.insertVertex(vertex);
}
// 插入边
graph.insertEdge("A","B");
graph.insertEdge("A","C");
graph.insertEdge("B","C");
graph.insertEdge("B","D");
graph.insertEdge("B","E");
graph.showGraph();
}
}
3. グラフトラバーサル
3.1 深さ優先トラバーサル (DFS)
- ツリーの事前順序走査と同様に、最初に現在の頂点にアクセスし、次にその隣接点を新しい開始点として使用して、深さ優先走査を実行します。
- ツリー トラバーサルとは異なり、グラフが最初に深さ方向にトラバースされるとき、現在の頂点の隣接するポイントが既に訪問されている可能性があるため、再度訪問する必要はありません。
- 頂点の一意のトラバーサルを実現するには、コードの実装中にタグ配列を提供する必要があります。これは、対応する頂点が訪問されたかどうかを示します。
- コードの実装は再帰的に進行します。
/**
* 深度优先遍历:类似树的先序遍历
* @param v 以当前点开始遍历
* @param visited 标记节点是否访问过
*/
public void dfs(int v,boolean[] visited){
System.out.print(vertexes.get(v)+"->");
// 遍历当前顶点的邻接点
for (int i=0;i<vertexes.size();i++){
// 说明v、i之间存在边
if (edges[v][i]!=0&&!visited[i]){
visited[i]=true;
dfs(i,visited);
}
}
}
public void dfs(){
// 标记顶点是否已经访问过
boolean[] visited=new boolean[vertexes.size()];
// 以每个顶点开始进行DFS,实现图的完整遍历
for (int i=0;i<vertexes.size();i++){
if (!visited[i]){
visited[i]=true;
dfs(i,visited);
}
}
}
3.2 幅優先トラバーサル (BFS)
- ツリー レベルのトラバーサルと同様に、最初に現在の頂点にアクセスし、次に隣接するポイントを順番にアクセスしてから、隣接するポイントを新しい開始点として幅優先のトラバーサルを続行します。
- ツリー トラバーサルとは異なり、グラフが最初に深さ方向にトラバースされるとき、現在の頂点の隣接するポイントが既に訪問されている可能性があるため、再度訪問する必要はありません。
- 頂点の一意のトラバーサルを実現するには、コードの実装中にタグ配列を提供する必要があります。これは、対応する頂点が訪問されたかどうかを示します。
- コードはキューによって実装されます。
/**
* 广度优先遍历:类似树的层次遍历
* @param v 以当前点开始遍历
* @param visited 标记节点是否访问过
*/
public void bfs(int v,boolean[] visited){
ArrayDeque<Integer> queue=new ArrayDeque<>();
queue.offer(v);
visited[v]=true;
while (!queue.isEmpty()){
Integer cur = queue.poll();
System.out.print(vertexes.get(cur)+"->");
// 遍历当前顶点的邻接点
for (int i=0;i<vertexes.size();i++){
// 说明v、i之间存在边
if (edges[cur][i]!=0&&!visited[i]){
visited[i]=true;
queue.offer(i);
}
}
}
}
public void bfs(){
// 标记顶点是否已经访问过
boolean[] visited=new boolean[vertexes.size()];
// 以每个顶点开始进行BFS,实现图的完整遍历
for (int i=0;i<vertexes.size();i++){
if (!visited[i]){
bfs(i,visited);
}
}
}
出典:Shang Silicon Valley