1,图的基本概念
1.1,图的基本介绍
- 线性表局限于一个直接前驱和一个直接后继的关系
- 树也只能有一个直接前驱也就是父节点
- 当需要多对多的关系的时候,就应该用到图
1.2,图的常用概念
- 顶点(Vertex)
- 边(Edge)
- 路径
- 无向图
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/526d14cf287a4cdd4da2d2154cd16c0f.png)
- 有向图
- 带权图
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b6f959097762c4f2e03437dbac6fcf9f.png)
1.3,图的表达方式
- 图的表示方式有两张:二维数组表示(邻接矩阵),链表表示(邻接表)
- 邻接矩阵:是表示图形中顶点之间相邻关系的矩阵,对于N个顶点的图而言,矩阵的
row
和column
分别表示n个点
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/436e6c58c975c6130e4e1ba6b7c81deb.png)
- 邻接表
- 邻接矩阵需要为每个顶点分配n个边的空间,但是其实很多边在图中不存在,会造成额外的空间浪费
- 邻接表的实现只关心存在的边,不关心不存在的边,不存在空间浪费,邻接表由数组和链表组成
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/4e9af8584f7a0e9435177b8e4be6b986.png)
2,图的深度优先遍历
2.1,基本思想
- 深度优先遍历,从初始访问节点出发,首先访问它的第一个邻接节点;然后以该邻接节点作为初始节点,继续访问它的第一个邻接节点;以此类推
- 如果第一个邻接节点不存在或者已经递归遍历完成,则获取第二个邻接节点作为初始节点
- 由此可以看到,深度优先遍历是先向纵深挖掘遍历,纵深遍历完成后,再进行横向遍历
- 显示,深度优先遍历时一个递归的过程
2.2,算法步骤
- 访问初始节点
index
,并将该节点标记为已访问 - 查找初始节点
index
的第一个邻接节点nextIndex
- 若
nextIndex
存在,则继续执行第四步;若nextIndex
不存在,则退回第二步,继续查找index
的下一个邻接节点,继续进行第三步判断 - 若
nextIndex
未被访问,则对nextIndex
进行深度优先遍历,即从第一步递归 - 若
nextIndex
已经被访问,则回到第二步,继续查找index
的下一个临界点,继续判断
2.3,代码实现
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/7d731cd00fe7ea9848701898bc572034.png)
- 深度优先遍历结果为:1 -> 2 -> 4 -> 8 -> 5 -> 3 -> 6 -> 7
package com.self.datastructure.chart;
import org.apache.commons.collections.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class QuickStartChart {
public static void main(String[] args) {
MyChart myChart = createChart(8);
System.out.println("深度优先遍历: ");
myChart.dfs();
}
private static MyChart createChart(int vertexNum) {
MyChart myChart = new MyChart(vertexNum);
for (int i = 1; i <= vertexNum; i++) {
myChart.addVertex(String.valueOf(i));
}
myChart.addEdge(myChart.indexOf("1"), myChart.indexOf("2"), 1);
myChart.addEdge(myChart.indexOf("1"), myChart.indexOf("3"), 1);
myChart.addEdge(myChart.indexOf("2"), myChart.indexOf("4"), 1);
myChart.addEdge(myChart.indexOf("2"), myChart.indexOf("5"), 1);
myChart.addEdge(myChart.indexOf("3"), myChart.indexOf("6"), 1);
myChart.addEdge(myChart.indexOf("3"), myChart.indexOf("7"), 1);
myChart.addEdge(myChart.indexOf("4"), myChart.indexOf("8"), 1);
myChart.addEdge(myChart.indexOf("5"), myChart.indexOf("8"), 1);
return myChart;
}
static class MyChart {
private int vertexNum;
private List<String> lstVertex;
private int[][] vertexPathArray;
private int edgeNum;
private boolean[] isVisited;
MyChart(int vertexNum) {
this.vertexNum = vertexNum;
lstVertex = new ArrayList<>(vertexNum);
vertexPathArray = new int[vertexNum][vertexNum];
isVisited = new boolean[vertexNum];
}
void addVertex(String vertex) {
if (vertexNum == lstVertex.size()) {
throw new ArrayIndexOutOfBoundsException("数组已满");
}
lstVertex.add(vertex);
}
void addAllVertex(String ... vertexArr) {
if (vertexNum < lstVertex.size() + vertexArr.length) {
throw new ArrayIndexOutOfBoundsException("数组已满");
}
lstVertex.addAll(Arrays.asList(vertexArr));
}
int indexOf(String vertex) {
return lstVertex.indexOf(vertex);
}
void addEdge(int xIndex, int yIndex, int weight) {
if (xIndex >= vertexNum || yIndex >= vertexNum) {
throw new IndexOutOfBoundsException("索引越界");
}
vertexPathArray[xIndex][yIndex] = weight;
vertexPathArray[yIndex][xIndex] = weight;
edgeNum++;
}
int getEdgeNum() {
return edgeNum;
}
void showChart() {
for (int[] array : vertexPathArray) {
System.out.println(Arrays.toString(array));
}
}
void dfs() {
for (int i = 0; i < lstVertex.size(); i++) {
if (!isVisited[i]) {
dfs(i);
}
}
}
private void dfs(int index) {
System.out.print(lstVertex.get(index) + " -> ");
isVisited[index] = true;
int nextIndex = getFirstNeighbor(index);
while (nextIndex != -1) {
if (!isVisited[nextIndex]) {
dfs(nextIndex);
}
nextIndex = getNextNeighbor(index, nextIndex);
}
}
private int getNextNeighbor(int index, int nextIndex) {
for (int i = nextIndex + 1; i < lstVertex.size(); i++) {
if (vertexPathArray[index][i] > 0) {
return i;
}
}
return -1;
}
private int getFirstNeighbor(int index) {
for (int i = 0; i < lstVertex.size(); i++) {
if (vertexPathArray[index][i] > 0) {
return i;
}
}
return -1;
}
}
}
3,图的广度优先遍历
3.1,基本思想
- 广度优先遍历,类似于分层搜索的过程;广度优先遍历需要使用一个队列来保持访问过的节点的顺序,以便按照这个顺序来访问这些节点的邻接节点
3.2,算法步骤
- 访问初始节点
index
并标记为已访问 - 节点
index
入队列,相对于深度遍历,广度遍历需要维护一个局部队列 - 当队列不为空时,继续遍历执行,否则算法结束
- 对于刚开始遍历,起步会入
index
到队列,所以对于第一次必为真 - 后续操作中,每遍历到一个未访问的节点,都会将该节点入队列,在每一次队列遍历时,移除第一个节点进行广度遍历,等队列最终为空,说明以该节点为初始节点的遍历完成
- 移除队列的第一个元素,作为初始节点,进行遍历,第一次默认取出
index
节点 - 横向查找节点
index
的第一个邻接节点nextIndex
- 若节点
index
的邻接节点nextIndex
不存在,则转到步骤三 - 如果节点存在,则需要进行下面判断
- 若节点
nextIndex
没有被访问,则访问节点并标记为已访问 - 将节点
nextIndex
入队列; - 将
nextIndex
标记添加队列完成后,将nextIndex
节点作为index
节点,转到步骤五,继续处理 - 一次遍历中,会持续往队列中添加元素,这也是第三步队列判空的意义;如果初始节点
index
存在三个横向的邻接节点,则第一次队列遍历完成后,队列中会移除index
节点,并添加三个邻接节点,等第二次遍历时,队列中会有三个节点,并继续对这三个节点进行遍历 - 尽量写明白点,看代码吧,目测看不懂…
3.3,代码实现
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/41d2987df2d46f8ff8b81361f617098d.png)
- 广度优先运行结果:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
package com.self.datastructure.chart;
import org.apache.commons.collections.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class QuickStartChart {
public static void main(String[] args) {
MyChart myChart = createChart(8);
System.out.println("深度优先遍历: ");
myChart.dfs();
}
private static MyChart createChart(int vertexNum) {
MyChart myChart = new MyChart(vertexNum);
for (int i = 1; i <= vertexNum; i++) {
myChart.addVertex(String.valueOf(i));
}
myChart.addEdge(myChart.indexOf("1"), myChart.indexOf("2"), 1);
myChart.addEdge(myChart.indexOf("1"), myChart.indexOf("3"), 1);
myChart.addEdge(myChart.indexOf("2"), myChart.indexOf("4"), 1);
myChart.addEdge(myChart.indexOf("2"), myChart.indexOf("5"), 1);
myChart.addEdge(myChart.indexOf("3"), myChart.indexOf("6"), 1);
myChart.addEdge(myChart.indexOf("3"), myChart.indexOf("7"), 1);
myChart.addEdge(myChart.indexOf("4"), myChart.indexOf("8"), 1);
myChart.addEdge(myChart.indexOf("5"), myChart.indexOf("8"), 1);
return myChart;
}
static class MyChart {
private int vertexNum;
private List<String> lstVertex;
private int[][] vertexPathArray;
private int edgeNum;
private boolean[] isVisited;
MyChart(int vertexNum) {
this.vertexNum = vertexNum;
lstVertex = new ArrayList<>(vertexNum);
vertexPathArray = new int[vertexNum][vertexNum];
isVisited = new boolean[vertexNum];
}
void addVertex(String vertex) {
if (vertexNum == lstVertex.size()) {
throw new ArrayIndexOutOfBoundsException("数组已满");
}
lstVertex.add(vertex);
}
void addAllVertex(String ... vertexArr) {
if (vertexNum < lstVertex.size() + vertexArr.length) {
throw new ArrayIndexOutOfBoundsException("数组已满");
}
lstVertex.addAll(Arrays.asList(vertexArr));
}
int indexOf(String vertex) {
return lstVertex.indexOf(vertex);
}
void addEdge(int xIndex, int yIndex, int weight) {
if (xIndex >= vertexNum || yIndex >= vertexNum) {
throw new IndexOutOfBoundsException("索引越界");
}
vertexPathArray[xIndex][yIndex] = weight;
vertexPathArray[yIndex][xIndex] = weight;
edgeNum++;
}
int getEdgeNum() {
return edgeNum;
}
void showChart() {
for (int[] array : vertexPathArray) {
System.out.println(Arrays.toString(array));
}
}
private int getNextNeighbor(int index, int nextIndex) {
for (int i = nextIndex + 1; i < lstVertex.size(); i++) {
if (vertexPathArray[index][i] > 0) {
return i;
}
}
return -1;
}
private int getFirstNeighbor(int index) {
for (int i = 0; i < lstVertex.size(); i++) {
if (vertexPathArray[index][i] > 0) {
return i;
}
}
return -1;
}
public void bfs() {
for (int i = 0; i < lstVertex.size(); i++) {
if (!isVisited[i]) {
bfs(i);
}
}
}
private void bfs(int index) {
LinkedList<Integer> lstSearch = new LinkedList<>();
lstSearch.add(index);
System.out.print(lstVertex.get(index) + " -> ");
isVisited[index] = true;
for (;CollectionUtils.isNotEmpty(lstSearch);) {
Integer currIndex = lstSearch.removeFirst();
int nextIndex = getFirstNeighbor(currIndex);
for (;-1 != nextIndex;) {
if (!isVisited[nextIndex]) {
lstSearch.add(nextIndex);
isVisited[nextIndex] = true;
System.out.print(lstVertex.get(nextIndex) + " -> ");
}
nextIndex = getNextNeighbor(currIndex, nextIndex);
}
}
}
}
}