1.図の基本的な紹介
1.1なぜ写真があるのですか?
- 線形テーブルは、直接の先行と直接の後続の間の関係に限定されます。
- ツリーには、親ノードである直接の先行ノードを1つだけ含めることができます。
- 多対多の関係を表現する必要がある場合は、グラフを使用する必要があります。
1.2図の図
グラフは、ノードが0個以上の隣接する要素を持つことができるデータ構造です。2つのノード間の接続はエッジと呼ばれます。ノードは頂点と呼ばれることもあります。図に示すように:
1.3グラフの一般的な概念
- バーテックス
- 側
- 道
- 無向グラフ
- 有向グラフ
- 加重グラフ
2グラフの表現
グラフを表現するには、2次元配列表現(先行マトリックス)、リンクリスト表現(先行ジャンクションテーブル)の2つの方法があります。
2.1主要なマトリックス
リード行列は、グラフ内の頂点間の関係を表す行列です.n個の頂点を持つグラフの場合、行列は行と列で表される1 ... n個の点です。
2.2リーディングテーブル
- 先行行列は、各頂点にn個のエッジにスペースを割り当てる必要があります。実際、存在しないエッジが多数あるため、スペースがある程度失われます。
- リンクテーブルの実現は、存在しないエッジではなく、既存のエッジのみを考慮します。したがって、スペースの無駄はなく、先頭のテーブルは配列とリンクリストで構成されます。
3.グラフをトラバースする方法
グラフのいわゆるトラバーサルは、ノードへの訪問です。一般に、2つのトラバーサル方法があります。
- 深さ優先探索
- 幅優先探索
3.1深さ優先探索の基本的な考え方
- 深さ優先トラバーサルは、初期アクセスノードから開始して、複数の隣接ノードを持つ場合があります。深さ優先トラバーサルの戦略は、最初に最初の隣接ノードにアクセスし、次にこのアクセスした隣接ノードを初期Aノードとして使用することです。 、最初の隣接ノードにアクセスするには、次のように理解できます。現在のノードにアクセスするたびに、現在のノードの最初の隣接ノードに最初にアクセスします。
- このようなアクセス戦略は、ノードの隣接するすべてのノードに水平方向にアクセスするのではなく、垂直方向の掘削を優先することであることがわかります。
- 深さ優先探索は再帰的なプロセスです。
3.2深さ優先走査アルゴリズムのステップ
- 最初のノードvにアクセスし、ノードvをアクセス済みとしてマークします。
- ノードvの最初の隣接ノードwを見つけます。
- wが存在する場合は実行を続行し、wが存在しない場合は手順1に戻り、vの次のノードから続行します。
- wにアクセスしていない場合は、wに対して深さ優先走査再帰を実行します(つまり、wを別のvとして扱い、手順1、2、3に進みます)。
- ノードvのw隣接ノードの次の隣接ノードを見つけて、ステップ3に進みます。
3.3幅優先探索の基本的な考え方
- 階層型検索プロセスと同様に、幅優先走査では、訪問したノードの順序を維持するためにキューを使用する必要があります。これにより、これらのノードの隣接ノードをこの順序で訪問できます。
3.4幅優先探索アルゴリズムの手順
- 最初のノードvにアクセスし、ノードvをアクセス済みとしてマークします。
- ノードvがキューに入ります。
- キューが空でない場合は、実行を続行します。空でない場合、アルゴリズムは終了します。
- キューから出て、ヘッドノードuを取得します。
- ノードuの最初の隣接ノードwを見つけます。
- ノードuの隣接ノードwが存在しない場合は、手順3に進みます。存在しない場合は、次の3つの手順をループします。
- ノードwにまだアクセスしていない場合は、ノードwにアクセスして、訪問済みとしてマークします。
- ノードwがキューに入ります。
- wの隣接ノードに続くノードuの次の隣接ノードwを見つけ、ステップ6に進みます。
完全なコード
package com.lele.graph;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
/**
* author: hwl
* date: 2020/12/5 18:52
* version: 1.0.0
* modified by:
* description: 图
*/
public class Graph {
private ArrayList<String> vertexList; // 存储顶点集合
private int[][] edges; // 存储图对应的领接矩阵
private int numOfEdges; // 表示边的数目
// 定义给数组boolean[],记录某个节点是否被访问
private boolean[] isVisited;
public static void main(String[] args) {
// 测试图是否创建ok
int n = 8;// 结点的个数
// String Vertexs[] = {"A","B","C","D","E"};
String Vertexs[] = {
"1","2","3","4","5","6","7","8"};
// 创建图对象
Graph graph = new Graph(n);
// 循环的添加顶点
for (String vertex : Vertexs) {
graph.insertVertex(vertex);
}
// 添加边
// A-B A-C B-C B-D B-E
// 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.insertEdge(0,1,1);
graph.insertEdge(0,2,1);
graph.insertEdge(1,3,1);
graph.insertEdge(1,4,1);
graph.insertEdge(3,7,1);
graph.insertEdge(4,7,1);
graph.insertEdge(2,5,1);
graph.insertEdge(2,6,1);
graph.insertEdge(5,6,1);
// 显示领接矩阵
graph.showGraph();
// 测试dfs
System.out.println("深度遍历");
graph.dfs();
System.out.println();
// 测试广度优先
System.out.println("广度优先!");
graph.bfs();
}
// 构造器
public Graph(int n) {
// 初始化矩阵和vertexList
edges = new int[n][n];
vertexList = new ArrayList<String>(n);
numOfEdges = 0;
}
/**
* 得到第一个邻接结点的下标 w
* @param index
* @return 如果存在对应的下标,否则返回-1
*/
public int getFirstNeighbor(int index) {
for (int j = 0; j < vertexList.size(); j++) {
if (edges[index][j] > 0) {
return j;
}
}
return -1;
}
/**
* 根据前一个邻接结点的下标来获取下一个邻接结点
* @param v1
* @param v2
* @return
*/
public int getNextNeighbor(int v1, int v2) {
for (int j = v2 + 1; j < vertexList.size(); j++) {
if (edges[v1][j] > 0) {
return j;
}
}
return -1;
}
/**
* 深度优先遍历算法
* i 第一次就是0
*/
public void dfs(boolean[] isVisited, int i) {
// 首先我们访问该结点,输出
System.out.print(getValueByIndex(i) + "->");
// 将该结点设置为已经访问
isVisited[i] = true;
// 查找结点i的第一个邻接结点w
int w = getFirstNeighbor(i);
while(w != -1) {
//说明有
if (!isVisited[w]) {
dfs(isVisited, w);
}
// 如果w结点已经被访问过
w = getNextNeighbor(i, w);
}
}
/**
* 对dfs进行一个重载,遍历我们所有的结点,并进行dfs
*/
public void dfs() {
isVisited = new boolean[vertexList.size()];
// 遍历所有的结点,进行dfs[回溯]
for (int i = 0; i < getNumOfVertex(); i++) {
if (!isVisited[i]) {
dfs(isVisited, i);
}
}
}
/**
* 对一个结点进行广度优先遍历的方法
* @param isVisited
* @param i
*/
private void bfs(boolean[] isVisited, int i) {
int u; // 表示队列的头结点对应的下标
int w; // 邻接结点
// 队列,记录结点访问的顺序
LinkedList queue = new LinkedList();
// 访问结点,输出结点信息
System.out.print(getValueByIndex(i) + "=>");
// 标记为已访问
isVisited[i] = true;
// 将结点加入队列
queue.addLast(i);
while(!queue.isEmpty()) {
// 取出队列的头结点下标
u = (Integer)queue.removeFirst();
// 获得第一个邻接结点的下标 w
w = getFirstNeighbor(u);
while(w != -1) {
// 找到
// 是否访问过
if (!isVisited[w]) {
System.out.print(getValueByIndex(w) + "=>");
// 标记已经访问
isVisited[w] = true;
// 入队
queue.addLast(w);
}
// 以u 为前驱点,找w后面的下一个邻接结点
w = getNextNeighbor(u, w);// 体现出 广度优先
}
}
}
/**
* 遍历所有的结点,都进行广度优先搜索
*/
public void bfs() {
isVisited = new boolean[vertexList.size()];
for (int i = 0; i < getNumOfVertex(); i++) {
if (!isVisited[i]) {
bfs(isVisited, i);
}
}
}
/** 图中 常用的方法 **/
/**
* 获取结点的个数
* @return
*/
public int getNumOfVertex() {
return vertexList.size();
}
/**
* 显示图对应的矩阵
*/
public void showGraph() {
for (int[] link : edges) {
System.out.println(Arrays.toString(link));
}
}
/**
* 获得边的数目
* @return
*/
public int getNumOfEdges() {
return numOfEdges;
}
/**
* 返回结点i(下标)对应的数据 0->"A" 1-> "B" 2->"C"
* @param i
* @return
*/
public String getValueByIndex(int i) {
return vertexList.get(i);
}
/**
* 返回v1和v2的权值
* @param v1
* @param v2
* @return
*/
public int getWeight(int v1, int v2) {
return edges[v1][v2];
}
/**
* 插入结点
* @param vertex
*/
public void insertVertex(String vertex) {
vertexList.add(vertex);
}
/**
* 添加边
* @param v1 表示点的下标 即第几个顶点 "A"-"B", "A"->0 "B"->1
* @param v2 第二个顶点的下标
* @param weight 表示
*/
public void insertEdge(int v1, int v2, int weight) {
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges++;
}
}