图
- 为什么要有图
- 我们学过线性表和树
- 线性表局限于一个直接前驱和直接后继的关系
- 树也只有一个直接前驱
- 当我们需要表示多对多的关系时,这里我们就用到了图
- 图的举例说明
- 图是一种数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边。结点也可以称为顶点。
- 图是一种数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边。结点也可以称为顶点。
- 图的常用概念
- 顶点(vertex)
- 边(edge)
- 路径
- 无向图
- 有向图
- 带权图
- 图的表示方式
-
邻接矩阵(二维数组表示)
- 邻接矩阵表示图形中顶点之间相邻关系的矩阵,对于n个顶点的图而言,矩阵的row和col表示的是1…n个点
- 邻接矩阵表示图形中顶点之间相邻关系的矩阵,对于n个顶点的图而言,矩阵的row和col表示的是1…n个点
-
邻接表(链表表示)
- 邻接矩阵需要为每个顶点分配n个边的空间,其中很多边是不存在的,会造成空间的一定损失
- 邻接表的实现只关心存在的边,不关心不存在的边。邻接表由数组+链表组成
-
- 图的快速入门
- 实现如下结构
- 具体代码在最后面
- 实现如下结构
- 图的深度优先遍历介绍
- 介绍
- 遍历,就是对结点的访问。一个图有那么多个结点,如何遍历这些结点,需要有特定策略,一般有两种访问策略:深度优先遍历、广度优先遍历
- 深度优先遍历思想
- 从初始访问结点出发,初始访问结点可能有多个邻接结点。深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点为初始结点,访问它的第一个邻接结点。
- 我们可以看到,这种访问策略是优先往纵向挖掘深入,而不是对一个结点的所有邻接点进行横向访问
- 显然,深度优先搜索是一个递归的过程
- 步骤
- 访问初始结点v,并标记结点v为已访问
- 查找结点v的第一个邻接结点w
- 若w存在,则继续执行4,则回到第一步,将从v的下一个结点继续
- 若w未被访问,对w进行深度优先遍历递归(即把w当作另一个v,然后进行步骤123)
- 查找结点v的w邻接结点的下一个邻接结点,转到步骤3
具体代码在最后面
- 介绍
- 图的广度优先遍历
- 广度优先遍历思想
- 图的广度优先搜索(Broad First Search)
- 类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来访问这些结点的邻接结点
- 步骤
- 访问初始结点v并标记结点v为以访问。
- 结点v入队
- 当队列非空时,继续执行,否则算法结束
- 出队列,取出队头结点u
- 查找结点u的第一个邻接结点w
- 若结点u的邻接点不存在,则转到步骤3,否则,循环执行以下三个步骤
6.1. 若结点w尚未被访问,则访问结点w并标记为已访问
6.2 结点w入队列
6.3 查找结点u的继w邻接点后的下一个邻接点w,转到步骤6
具体代码在最后面
- 广度优先遍历思想
- 代码
import java.util.*;
public class GraphDemo {
boolean[] isVisited;
public static void main(String[] args) {
// 创建图对象
String[] vertexs = {"A","B","C","D","E"};
int n = vertexs.length;
Graph graph = new Graph(n);
// 将各个顶点加入到顶点集中
for (String vertex : vertexs) {
graph.add(vertex);
}
// 将边加入到图中
graph.addEdge(0, 1, 1);
graph.addEdge(0, 2, 1);
graph.addEdge(1, 2, 1);
graph.addEdge(1, 3, 1);
graph.addEdge(1, 4, 1);
// 遍历
// graph.showGraph();
// 深度遍历
System.out.print("深度遍历结果是:");
graph.dfs();
System.out.println();
// 广度遍历
System.out.print("广度遍历结果是:");
graph.bfs();
}
}
class Graph{
private List<String> vertexs;
private int[][] edges;
int numOfEdges;
boolean[] isVisited;
/**
* 初始化图
* @param n 图的顶点个数
*/
public Graph(int n) {
vertexs = new ArrayList<>(n);
edges = new int[n][n];
numOfEdges = 0;
}
/**
* 将顶点加入到顶点集中
* @param vertex
*/
public void add(String vertex) {
vertexs.add(vertex);
}
/**
* 将边加入到边集合中
* @param v1
* @param v2
* @param v3
*/
public void addEdge(int v1, int v2, int v3) {
edges[v1][v2] = v3;
edges[v2][v1] = v3;
numOfEdges++;
}
/**
* 显示邻接矩阵
*/
public void showGraph() {
for (int[] edge : edges) {
System.out.println(Arrays.toString(edge));
}
}
public void dfs(int i) {
// 访问该结点
System.out.print(vertexs.get(i) + " ");
// 置为已访问
isVisited[i] = true;
// 获取第一个邻接点
int w = getFirstNeighbor(i);
while(w != -1) {
if(!isVisited[w]) {
dfs(w);
}
// 获取下一个邻接点
w = getNextNeighbor(i,w);
}
}
// 对dfs进行重载,防止不连通图没有遍历完所有的结点的情况
public void dfs() {
isVisited = new boolean[getNumberOfVertex()];
// 遍历所有的结点,进行dfs
for(int i = 0; i < getNumberOfVertex(); i++) {
if (!isVisited[i]) {
dfs(i);
}
}
}
// 从某个结点开始进行广度遍历
public void bfs(int i) {
// 队列,记录结点访问次序
List<Integer> list = new ArrayList<>();
// 访问结点,输出结点信息
System.out.print(vertexs.get(i) + " ");
// 标记为已访问
isVisited[i] = true;
// 将结点加入队列
list.add(i);
while(!list.isEmpty()) {
// 取出队头元素
int w = list.remove(0);
// 得到第一个邻接结点的下标w
int u = getFirstNeighbor(w);
while(u != -1) {// 找到
if(!isVisited[u]) {// 是否访问过
System.out.print(vertexs.get(u) + " ");
isVisited[u] = true;
list.add(u);
}
// 找与w相邻的下一个邻结点
u = getNextNeighbor(w, u);
}
}
}
// 遍历所有结点,都进行广度优先搜索
public void bfs() {
int size = getNumberOfVertex();
isVisited = new boolean[size];
for (int i = 0; i < size; i++) {
if (!isVisited[i]) {
bfs(i);
}
}
}
// 根据前一个邻接结点的下标来获取下一个邻接结点
private int getNextNeighbor(int v1, int v2) {
for(int i = v2 + 1; i < getNumberOfVertex(); i++) {
if(edges[v1][i] > 0) return i;
}
return -1;
}
/**
* 获取第一个邻接点的下标w
* @param index
* @return 如果存在就返回对应的下标,否则返回-1
*/
public int getFirstNeighbor(int index) {
for(int i = 0; i < getNumberOfVertex();i++) {
if(edges[index][i] > 0) return i;
}
return -1;
}
public int getNumberOfVertex() {
return vertexs.size();
}
}