- 由于线性表和树结构都局限于一个直接前驱和一个直接后继的关系, 无法表示多对多的关系. 此时通过图来实现
概述
- 图是一种数据结构, 其中节点可以有零个或多个相邻节点. 节点与节点之间的的连接称为边. 节点也可以称为顶点
- 图的常用概念:
- 顶点(Vertex)
- 边(Edge)
- 路径: 比如从 D->C的路径有2个
- (1) D->B->C
- (2) D->A->B-C
- 无向图: 顶点之间的连接没有方向, 比如 A-B, 既可以是 A->B也可以 B->A
- 有向图: 顶点之间的连接有方向, 比如 A-B, 只能是 A->B不能是 B->A
- 带权图: 边带权值的图, 又称为网
2种表示方式
- 邻接矩阵(Adacency Matrix)是表示图形中顶点之间相邻关系的矩阵, 一般使用二维数组来实现. 此方式会给没有的边分配空间
- 邻接表的实现是只关心存在的边, 不表示没有的边, 因此也不会给没有的边分配空间, 一般使用数组+链表结构来实现
两种访问策略
- 深度优先搜索(Depth First Search)
- (1) 深度优先遍历, 从初始访问节点出发, 初始访问节点可能有多个邻接节点, 深度优先遍历的策略就是首先访问第一个邻接节点, 然后再以这个被访问的邻接节点作为初始节点, 访问它的第一个邻接节点, 可以这样理解: 每次都在访问完当前节点后首先访问当前节点的第一个邻接节点
- (2) 我们可以看到, 这样的访问策略是优先往
纵向
挖掘深入, 而不是对一个节点的所有邻接节点进行横向访问 - (3) 显然, 深度优先搜索是一个递归的过程
- 广度优先搜索(Broad First Search)
- 类似于一个分层搜索的过程, 广度优先遍历需要使用一个队列以保持访问过的节点的顺序, 以便按这个顺序来访问这些节点的邻接节点
- 广度优先遍历算法步骤:
- 访问初始节点 v并标记节点 v为已访问
- 节点 v入队列
- 当队列非空时, 继续执行, 否则算法结束
- 出队列, 取得队头节点 u
- 查找节点 u的第一个邻接节点 w
- 若节点 u的邻接节点 w不存在, 则转到步骤3; 否则循环执行以下三个步骤:
- (1) 若节点 w尚未被访问, 则访问节点 w并标记为已访问
- (2) 节点 w入队列
- (3) 查找节点 u的继 w邻接节点后的下一个邻接节点 w, 转到步骤6
public class GraphApp {
/** 存储节点(顶点)列表*/
private List<String> vertexList;
/** 存储图对应的邻结矩阵*/
private int[][] adjacencyMatrix;
/** 记录节点是否已被访问*/
private boolean[] isVisited;
public static void main(String[] args) {
// /** 定义节点(顶点)*/
// String[] vertexs = {"A", "B", "C", "D", "E"};
// /** 创建图实例*/
// GraphApp graph = new GraphApp(vertexs.length);
// /** 更新顶点与顶点间的边的关系*/
// graph.insertEdge(0, 1, 1); // A->B 和 B->A
// graph.insertEdge(0, 2, 1);
// graph.insertEdge(1, 2, 1);
// graph.insertEdge(1, 3, 1);
// graph.insertEdge(1, 4, 1);
/** 定义顶点*/
String[] vertexs = {"1", "2", "3", "4", "5", "6", "7", "8"};
/** 创建图实例*/
GraphApp graph = new GraphApp(vertexs.length);
/** 更新顶点与顶点间的边的关系*/
graph.insertEdge(0, 1, 1); // 0->1 和 1->0
graph.insertEdge(0, 2, 1); // 0->2 和 2->0
graph.insertEdge(1, 3, 1); // 1->3 和 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);
/** 循环的添加顶点*/
for(String vertex: vertexs) {
graph.insertVertex(vertex);
}
/** 打印图对应的邻接矩阵*/
graph.showGraph();
System.out.println("深度遍历:");
graph.depthFirstSearch();
System.out.println();
System.out.println("广度优先:");
graph.broadFirstSearch();
}
public GraphApp(int n) {
adjacencyMatrix = new int[n][n];
vertexList = new ArrayList<>(n);
}
/**
* @param v1 表示图横向下标 row
* @param v2 表示图纵向下标 col
* @param weight 表示有效标记*/
public void insertEdge(int v1, int v2, int weight) {
adjacencyMatrix[v1][v2] = weight;
adjacencyMatrix[v2][v1] = weight;
}
/** 打印图对应的邻接矩阵*/
public void showGraph() {
for(int[] link : adjacencyMatrix) {
System.out.println(Arrays.toString(link));
}
}
/** 添加节点(顶点)*/
public void insertVertex(String vertex) {
vertexList.add(vertex);
}
/** 获取第一个邻接节点(顶点)的下标 w*/
public int getFirstNeighbor(int index) {
for(int w = 0; w < vertexList.size(); w++) {
if(adjacencyMatrix[index][w] > 0) {
return w;
}
}
return -1;
}
/** 根据前一个邻接节点的下标来获取下一个邻接节点*/
public int getNextNeighbor(int v1, int v2) {
for(int j = v2 + 1; j < vertexList.size(); j++) {
if(adjacencyMatrix[v1][j] > 0) {
return j;
}
}
return -1;
}
/** 深度优先遍历算法
* - i的起始值是 0
* - 纵向遍历*/
private void depthFirstSearch(boolean[] isVisited, int i) {
/** 打印当前节点(顶点)*/
System.out.print(vertexList.get(i) + "->");
/** 同时将当前节点标记为已经访问*/
isVisited[i] = true;
/** 查找当前节点的第一个邻接节点 w*/
int w = getFirstNeighbor(i);
while(w != -1) { /** 说明有*/
if(!isVisited[w]) { /** 如果 w节点未被访问过, 递归深度优先遍历*/
depthFirstSearch(isVisited, w);
}
/** 如果 w节点已经被访问过*/
w = getNextNeighbor(i, w);
}
}
/** 遍历所有的节点, 进行深度优先遍历(搜索)*/
public void depthFirstSearch() {
isVisited = new boolean[vertexList.size()];
/** 遍历所有的节点, 回溯*/
for(int i = 0; i < vertexList.size(); i++) {
if(!isVisited[i]) {
depthFirstSearch(isVisited, i);
}
}
}
/** 广度优先遍历算法
* - i的起始值是 0
* - 横向遍历*/
private void broadFirstSearch(boolean[] isVisited, int i) {
/** 队列的头节点对应下标*/
int u ;
/** 邻接节点下标 w*/
int w ;
/** 队列中, 记录节点的访问顺序*/
LinkedList<Integer> queue = new LinkedList<>();
/** 打印当前节点(顶点)*/
System.out.print(vertexList.get(i) + "=>");
/** 同时将当前节点标记为已经访问*/
isVisited[i] = true;
/** 将当前访问节点加入队列*/
queue.addLast(i);
while(!queue.isEmpty()) {
/** 取出队列的头节点下标*/
u = queue.removeFirst();
/** 得到第一个邻接节点的下标 w*/
w = getFirstNeighbor(u);
while(w != -1) {
if(!isVisited[w]) { /** 如果 w节点未被访问过*/
System.out.print(vertexList.get(w) + "=>");
/** 标记已经访问*/
isVisited[w] = true;
/** 第一个邻接节点入队*/
queue.addLast(w);
}
/** 以 u为前驱点(u 这个行的), 找 w后面的下一个邻节点*/
w = getNextNeighbor(u, w);
}
}
}
/** 遍历所有的节点, 进行广度优先遍历(搜索)*/
public void broadFirstSearch() {
isVisited = new boolean[vertexList.size()];
/** 遍历所有的节点, 回溯*/
for(int i = 0; i < vertexList.size(); i++) {
if(!isVisited[i]) {
broadFirstSearch(isVisited, i);
}
}
}
}
输出:
[0, 1, 1, 0, 0, 0, 0, 0]
[1, 0, 0, 1, 1, 0, 0, 0]
[1, 0, 0, 0, 0, 1, 1, 0]
[0, 1, 0, 0, 0, 0, 0, 1]
[0, 1, 0, 0, 0, 0, 0, 1]
[0, 0, 1, 0, 0, 0, 1, 0]
[0, 0, 1, 0, 0, 1, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0]
深度遍历:
1->2->4->8->5->3->6->7->
广度优先:
1=>2=>3=>4=>5=>6=>7=>8=>
如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!