赞
踩
Tips: 采用java语言,关注博主,底部附有完整代码
工具:IDEA
本篇介绍的是数据结构: 图
更多数据结构请转至偶的主页
图是一种比较复杂的非线形数据结构
非线形数据结构有2种:
如果想参考树,点击连接即可
虽说树和图都是非线形结构,但是他们也有说区别:
一般常见的二叉树只有2个结点,并且他们是有父子关系的.“等级制度”比较森严
但是图则不同,图都是平级的,没有父子之分,并且1个图结点之间可以有多个图结点相连接
常见的图有很多种,例如地铁路线:
这个图比较复杂,先来看一张简单的图:
图由2种东西组成:
顶点[vertex] (我喜欢叫图结点)
边 [edge]
图还分方向,是否环绕,一般有:
无向无环图 | 无向有环图 | 有向有环图 | 有向无环图 |
---|---|---|---|
向: 指的就是方向
环: 指的就是是否环绕
图的显示方式有2种
以无向无环图为例:
邻接矩阵:
用1表示他们相连接
用0表示他们未连接
因为他是无向无环图,所以CD如果相连,那么DC就一定相连接
等价换算到矩阵中,(2,3)如果是1,那么(3,2)一定是1
邻接链表:
通过链表的形式展示
以A顶点为例
用链表展示为A -> B -> C -> D -> E
表示:
连接
创建图之前首先需要创建一个图顶点(结点):
public static class GraphNode {
// 顶点
String value;
public GraphNode(String value) {
this.value = value;
}
@Override
public String toString() {
return "value='" + value;
}
}
创建图,添加顶点添加边:
代码比较简单,就直接看了,后面广度优先遍历深度优先遍历在细聊!
采用邻接矩阵的方式打印图:
public class Graph { // 用来存放顶点 private GraphNode[] graphNodes; // 矩阵 private int[][] matrix; // 存放顶点个数 private int maxSize; // 记录当前存放位置 private int currentIndex; public Graph(int size) { maxSize = size; graphNodes = new GraphNode[maxSize]; matrix = new int[maxSize][maxSize]; } // 顶点是否存在 public int containsVertex(String vertex) { for (int i = 0; i < graphNodes.length; i++) { if (graphNodes[i].value.equals(vertex)) { return i; } } return -1; } // 是否存放满 public boolean isFull() { return graphNodes.length >= currentIndex; } // 添加顶点 public void addVertex(GraphNode node) { if (isFull()) { graphNodes[currentIndex++] = node; } else { throw new RuntimeException("存满了,只能存放" + maxSize + "个顶点"); } } // 添加边 public void addEdge(String ver1, String ver2) { // 顶点1下标 int ver1Index = containsVertex(ver1); // 顶尖2下标 int ver2Index = containsVertex(ver2); // 如果顶点1 和顶点2 都存在 if (ver1Index != -1 && ver2Index != -1) { // 无向无环图 直接两端连接即可 matrix[ver1Index][ver2Index] = 1; matrix[ver2Index][ver1Index] = 1; } else { System.out.println("边添加失败"); } } // 顶点是否存在 public int containsVertex(String vertex) { for (int i = 0; i < graphNodes.length; i++) { if (graphNodes[i].value.equals(vertex)) { return i; } } return -1; } // 打印数据 public void show() { System.out.print("\t"); // 打印顶点 for (GraphNode graphNode : graphNodes) { System.out.printf("%s\t", graphNode.value); } System.out.println(); // 打印边 for (int i = 0; i < matrix.length; i++) { for (int j = 0; j < matrix[i].length; j++) { if (j == 0) { System.out.printf("%s\t", graphNodes[i].value); } System.out.printf("%d\t", matrix[i][j]); } System.out.println(); } } }
Graph.java 中 api设计:
属性名 | 作用 |
---|---|
GraphNode[] graphNodes; | 用来存放顶点 |
int[][][] [] matrix; | 矩阵 |
int maxSize; | 存放最大存储顶点个数 |
int currentIndex; | 存放当前添加顶点位置 |
方法名 | 作用 | 参数介绍 |
---|---|---|
boolean isFull() | 是否存放满 | - |
void addVertex(GraphNode) | 添加顶点 | @param 1:顶点信息 |
void addEdge(String ver1, String ver2) | 添加边 | @param1: 从哪个顶点开始 @param2: 到那个顶点结束 |
int containsVertex(String vertex) | 顶点是否存在 | 输入顶点 如果顶点存在,返回对应数组下标,否则返回-1 |
void show() | 打印当前数据 | - |
调用:
Graph graph = new Graph(5); // 添加结点 graph.addVertex(new Graph.GraphNode("A")); graph.addVertex(new Graph.GraphNode("B")); graph.addVertex(new Graph.GraphNode("C")); graph.addVertex(new Graph.GraphNode("D")); graph.addVertex(new Graph.GraphNode("E")); // 添加边 graph.addEdge("A", "B"); graph.addEdge("A", "C"); graph.addEdge("A", "D"); graph.addEdge("A", "E"); graph.addEdge("E", "D"); graph.addEdge("D", "C"); graph.show();
结果:
A B C D E
A 0 1 1 1 1
B 1 0 0 0 0
C 1 0 0 1 0
D 1 0 1 0 1
E 1 0 0 1 0
辅助图:
深度优先遍历(Depth-First-Search) 简称DSF,是图遍历顶点的一种方式
DSF学习重点:
如果是初学者,想搞懂DSF,首先要忘记当前和图有任何关系,
只需要记住对应的矩阵图即可,
现在只是操作的一个二维数组,这个二维数组只有0和1
假设这个二维数组中(2,3)元素为1
那么(3,2)元素也一定为1 !!
先来介绍2个最关键的方法:
public int getFirstNeighbor(int row) {
for (int column = 0; column < graphNodes.length; column++) {
// 判断是否存在,如果存在则返回对应的下标
if (matrix[row][column] == 1) {
return column;
}
}
return -1;
}
以辅助图1.0为例
public int getNextNeighbor(int row, int column) {
for (int i = column + 1; i < graphNodes.length; i++) {
// 遍历某一行,返回当前行下标位置
if (matrix[row][i] == 1) {
return i;
}
}
return -1;
}
row = 1,column = 1, 输出-1,因为(1,1)位置以后全是0
row = 2,column = 1, 输出 3, (2,1) 后第1个1的位置为3
先来看看完整DSF遍历流程:
深度遍历只要记住 是通过行找列,然后在看代码就会简单很多
首先需要在图结点中添加一个变量,标识当前结点是否访问过:
public static class GraphNode { // 顶点 String value; // 是否访问过 [默认未访问过] boolean isVisited; public GraphNode(String value) { this.value = value; } @Override public String toString() { return "value='" + value; } }
深度优先代码:
/* * @author: android 超级兵 * @create: 2022/7/19 09:52 * TODO dsf 深度优先遍历 */ private void showDSF(int i) { // 首先我们访问该结点 System.out.println("深度遍历:" + graphNodes[i].value); // 将当前结点设置为已经访问 graphNodes[i].isVisited = true; // 以当前结点进行深度遍历 // 查找结点i的第一个邻接点是否存在 int firstNeighbor = getFirstNeighbor(i); while (firstNeighbor != -1) { // 并且当前结点没有被访问过 if (!graphNodes[firstNeighbor].isVisited) { showDSF(firstNeighbor); } // 如果 firstNeighbor 结点被访问过,去查找下一个邻接点 firstNeighbor = getNextNeighbor(i, firstNeighbor); } } // 深度优先 public void showDSF() { // 从0开始回溯 showDSF(0); }
广度优先(Breadth first search)简称BSF
广度优先完整流程图:
完整代码:
// 广度优先 public void showBSF() { showBSF(0); } /* * @author: android 超级兵 * @create: 2022/7/20 13:44 * TODO 广度优先遍历(BSF broad first search) */ private void showBSF(int i) { // 表示队列头结点对应的下标 int u; // 邻接点 int w; // 队列 记录结点访问顺序 Queue<Integer> queue = new LinkedList<>(); System.out.println("广度遍历1:" + graphNodes[i].value); // 设置已访问过 graphNodes[i].isVisited = true; queue.add(i); // 队列 != null while (!queue.isEmpty()) { u = queue.poll(); // 得到第一个邻接点的下标 w = getFirstNeighbor(u); while (w != -1) { // 找到了结点 // 没有访问过 if (!graphNodes[w].isVisited) { System.out.println("广度遍历2:" + graphNodes[w].value); graphNodes[w].isVisited = true; // 入队列 queue.add(w); } // 已经访问过了,找w的后一个邻接点 w = getNextNeighbor(u, w); } } }
假如现在图结构是这样子
如果是深度优先遍历则顺序为: A->B -> D -> H -> E -> C -> F -> G
如果是广度优先遍历则顺序为: A->B -> C -> D -> E -> F -> G -> H
以这种图结构来说
通过名字也可以看出,深度是先抄底,然后再依次遍历
广度就是一层一层遍历.
本篇到此结束!
心得: 感觉本篇和树的文章来比写的不太好,可能是因为图用的比较少, 并且DSF和BSF 难度大了一点比较急躁.
在后面的学习过程中还是得静下来… -.-!
您的点赞就是对我最大的支持!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。