1. Basic introduction to diagrams
1.1 Why is there a picture?
- The linear table is limited to the relationship between a direct predecessor and a direct successor;
- The tree can only have one direct predecessor, which is the parent node;
- When we need to express a many-to-many relationship, we need to use a graph;
1.2 Illustration of the figure
A graph is a data structure in which a node can have zero or more adjacent elements. The connection between two nodes is called an edge. Nodes can also be called vertices. As shown in the figure:
1.3 Common concepts of graphs
- vertex
- side
- path
- Undirected graph
- Directed graph
- Weighted graph
2 Representation of graphs
There are two ways to represent the graph: two-dimensional array representation (leading matrix), linked list representation (leading junction table).
2.1 Leading matrix
The lead matrix is a matrix that represents the relationship between the vertices in the graph. For a graph with n vertices, the matrix is 1...n points represented by row and col.
2.2 Leading table
- The leading matrix needs to allocate space for n edges to each vertex. In fact, there are many edges that do not exist, which will cause a certain loss of space;
- The realization of the link table only cares about the existing edges, not the non-existent edges. Therefore, there is no waste of space, and the leading table is composed of an array + a linked list.
3. How to traverse the graph
The so-called traversal of the graph is the visit to the node. There are generally two traversal methods:
- Depth-first traversal
- Breadth first traversal
3.1 The basic idea of depth-first traversal
- Depth-first traversal, starting from the initial access node, the initial access node may have multiple adjacent nodes. The strategy of depth-first traversal is to first visit the first adjacent node, and then use this visited adjacent node as the initial A node, to visit its first adjacent node, can be understood as follows: After each visit to the current node, the first adjacent node of the current node is first visited;
- We can see that such an access strategy is to prioritize digging in the vertical direction instead of horizontally accessing all adjacent nodes of a node;
- Depth-first search is a recursive process;
3.2 Depth-first traversal algorithm steps
- Visit the initial node v, and mark the node v as visited;
- Find the first adjacent node w of node v;
- If w exists, continue execution, if w does not exist, go back to step 1, and continue from the next node of v;
- If w has not been visited, perform depth-first traversal recursion on w (that is, treat w as another v, and then proceed to steps 1, 2, 3);
- Find the next adjacent node of the w adjacent node of node v, and go to step 3;
3.3 The basic idea of breadth-first traversal
- Similar to a hierarchical search process, breadth-first traversal requires the use of a queue to maintain the order of the visited nodes, so that the adjacent nodes of these nodes can be visited in this order;
3.4 Breadth-first traversal algorithm steps
- Visit the initial node v and mark the node v as visited;
- Node v enters the queue;
- When the queue is not empty, continue to execute, otherwise the algorithm ends;
- Get out of the queue and get the head node u;
- Find the first adjacent node w of node u;
- If the adjacent node w of node u does not exist, go to step 3; otherwise, loop the following three steps:
- If node w has not been visited yet, visit node w and mark it as visited;
- The node w enters the queue;
- Find the next adjacent node w of the node u following the adjacent node of w, and go to step 6;
Complete code
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++;
}
}