1. 图概念 - 多对多关系
提出背景
1、 线性表只能局限于一个直接前驱、直接后继的关系**(一对一关系)** ;
2、 树只能局限于一个父节点的关系**(一对多关系)** ;
专有名词
1、 边:两个结点之间的连接;
2、 结点(顶点):可以具有零个或多个相邻元素;
3、 无(有)向图:顶点之间的连接没有(有)方向-即边没有方向;
4、 带权图(网):边带数值的图;
5、 图的表示;
①:数组形式 - 邻接矩阵
②:数组+链表形式 - 邻链表
无向图 - 数据结构
有向图 - 数据结构
带权图(也称网) - 数据结构
1.1 图的表式
1.1.1 矩阵 - 数组
缺点:浪费太多的空间 - 标记某个结点与图中所有结点的关系
1.1.2 邻接表 - 数组+链表
优势:只记录某个结点与该结点有联系的结点信息
2. 代码
2.1 矩阵
2.1.1 基本类创建#
/**
* @Classname UndirectedWeightGraph
* @Description
* @Date 2022/4/16 13:32
* @Created by lrc
*/
//无向带权图
public class UndirectedWeightGraph {
//存储结点
ArrayList<String> vertexs;
// 存储结点之间的权值 - 结点之间的关系 -- 矩阵
int[][] edges;
// 存储图中边的个数
int numOfEdges;
//存储是否遍历过的节点信息 false未遍历 true已经遍历过
boolean[] isVisable;
public static void main(String[] args) {
//1, 结点信息
String[] vertexs = {
"v1", "v2", "v3", "v4"};
Integer n = vertexs.length;
// 2. 创建带5个结点的无序带权图
UndirectedWeightGraph graph = new UndirectedWeightGraph(n);
// 3. 添加结点
for(String vertex : vertexs) {
graph.insertVertex(vertex);
}
// 4. 添加结点之间的权值 - 这里的权值1仅表示两结点之间有连接关系
graph.insertWeight(0,1, 1);
graph.insertWeight(0,2, 1);
graph.insertWeight(1,2, 1);
graph.insertWeight(1,3, 1);
// 5. 显示图的矩阵
//System.out.println(graph.getGraph());
graph.showGrpah();
}
//初始化 - 结点个数
UndirectedWeightGraph(int vertexsNum) {
this.vertexs = new ArrayList<>(vertexsNum);
this.edges = new int[vertexsNum][vertexsNum];
this.numOfEdges = 0;
}
//获取边的个数
public int getNumOfEdges() {
return numOfEdges;
}
//获取结点个数
public int getVertexsNum() {
return this.vertexs.size();
}
//添加新结点
public void insertVertex(String vertex) {
this.vertexs.add(vertex);
}
//获取两结点之间的边的权值 "A结点表示0" "B节点表示1"
public int getWeight(int firstVertex, int secondVertext) {
return edges[firstVertex][secondVertext];
}
//获取矩阵 - 即把数组Edges权值图打印出来
public String getGraph() {
return Arrays.deepToString(edges);
}
//显示矩阵
public void showGrpah() {
for(int[] edgeWeight : edges) {
System.out.println(Arrays.toString(edgeWeight));
}
}
//添加结点之间的关系 - 即权值 - 因为无向图的关系必须赋值数组中两个元素相同的权值
public void insertWeight(int firstVertex, int secondVertext, int weight) {
edges[firstVertex][secondVertext] = weight;
edges[secondVertext][firstVertex] = weight;
numOfEdges++;
}
}
2.1.2 遍历图的结点#
遍历
深度优先遍历(Depth First Search)
广度优先遍历(Board First Search)
2.1.2.1 深度优先遍历(DFS)#
DFS思想:访问到某个结点,就以某个结点访问该结点的第一个邻接结点 —纵向挖掘访问
找到W
未被访问
访问过
未找到W
1、 访问并输出初始结点V,并标记该V结点已被访问;
2、 查找V结点某个邻接结点W;
3、 是否找到W?;
是否W已经被访问过?
对W进行深度遍历 - 递归进入该结点(即V为W,重复1、2、3步骤)
查找V的下一个邻接结点 - 从步骤3开始
未找到
返回到上一层递归
深度优先遍历代码
写法1
//得到第一个邻接点的小标 - 找到返回W的小标,未找到返回-1 - 下一个小标的结点
public int getFirstNeigbor(int index) {
for(int j = 0; j <vertexs.size(); j++) {
if(edges[index][j] > 0) {
return j;
}
}
return -1;
}
//根据前一个邻结点的小标来获取一个邻接点- 找到返回新的W的小标,未找到返回-1
public int getNextNeigbor(int index, int w) {
for(int j = w+1; j<vertexs.size(); j++ ) {
if(edges[index][j] > 0) {
return j;
}
}
return -1;
}
//深度优先遍历
public void dfs(boolean[] isVisited, int i) {
System.out.println(vertexs.get(i));
isVisited[i] = true;
int w = getFirstNeigbor(i);
while(w != -1) {
if(!isVisited[w]) {
dfs(isVisited, w);
}
w = getNextNeigbor(i, w);
}
}
//重载深度优先算法
public void dfs() {
for(int i = 0; i<getVertexsNum(); i++) {
//如果未访问过,则从该结点开始访问
if(isVisable[i] == false) {
dfs(i);
}
}
//遍历完后修改回原来的状态
for(int i = 0; i < isVisable.length; i++) {
isVisable[i] = false;
}
}
写法2
//得到下一个邻接结点 - 寻找当前结点未被访问的邻接结点
public int getNextNeigbor2(int index) {
for(int j = 0; j<isVisable.length; j++) {
if(edges[index][j] > 0 && isVisable[j] == false) {
return j;
}
}
return -1;
}
//深度优先遍历2
public void dfs2(int i) {
System.out.println(vertexs.get(i));
isVisable[i] = true;
int w = getNextNeigbor2(i);
while(w != -1) {
dfs2(w);
w = getNextNeigbor2(i);
}
}
//重载深度优先算法
public void dfs2() {
//图可能存在没有变得结点 - 故需要遍历所有结点
for(int i = 0; i<getVertexsNum(); i++) {
//如果未访问过,则从该结点开始访问
if(isVisable[i] == false) {
dfs2(i);
}
}
//遍历完后修改回原来的状态
for(int i = 0; i < isVisable.length; i++) {
isVisable[i] = false;
}
}
测试
public static void main(String[] args) {
//1, 结点信息
String[] vertexs = {
"v1", "v2", "v3", "v4"};
Integer n = vertexs.length;
// 2. 创建带5个结点的无序带权图
UndirectedWeightGraph graph = new UndirectedWeightGraph(n);
// 3. 添加结点
for (String vertex : vertexs) {
graph.insertVertex(vertex);
}
// 4. 添加结点之间的权值 - 这里的权值1仅表示两结点之间有连接关系
graph.insertWeight(0, 1, 1);
graph.insertWeight(0, 2, 1);
graph.insertWeight(1, 2, 1);
graph.insertWeight(1, 3, 1);
// 5. 显示图的矩阵
//System.out.println(graph.getGraph());
graph.showGrpah();
graph.dfs2();
}
流程讲解
1. 首先dfs2( 0 ) 打印V1
2. 寻找与V1相邻并且未访问过的邻结点 找到V2即 1
3. 进入递归 dfs(1) 打印V2
4. 寻找与V2相邻并且未访问过的邻结点 找到V3即 2
5. 进入递归 dfs(2) 打印V3
6. 寻找与V3相邻并且未访问过的邻结点 未找到 退出dfs(2)这个递归 返回到dfs(1)这个递归
7. 寻找与V2相邻并且未访问过的邻结点 找到V4即 3
8. 进入递归 dfs(3) 打印V4
9. 寻找与V4相邻并且未访问过的邻结点 未找到 退出dfs(3)这个递归 返回到dfs(1)这个递归
10. 寻找与V2相邻并且未访问过的邻结点 未找到 退出dfs(1)这个递归 返回到dfs(0)这个递归
11. 寻找与V1相邻并且未访问过的邻结点 未找到 退出dfs(0) 结束函数
2.1.2.2 广度优先遍历(BFS)- 分层搜索#
BFS思想:从队列弹出的结点为基准,访问该结点的所有未被访问的邻结节点,并将其放入队列中。如果该弹出的结点所有邻接结点被访问完,则从队列弹出一个结点,重复前者动作
//得到下一个邻接结点
public int getNextNeigbor2(int index) {
for (int j = 0; j < isVisable.length; j++) {
if (edges[index][j] > 0 && isVisable[j] == false) {
return j;
}
}
return -1;
}
public void bfs(int i) {
// 创建队列
LinkedList<Integer> queue = new LinkedList();
//将i入队
queue.addLast(i);
//从队列中获取头结点
int u = queue.removeFirst();
// 打印并输出结点信息
System.out.println(this.getVertexByIndex(i));
isVisable[u] = true;
//以弹出结点读出所有该结点的邻接结点
while(true) {
//获取u的未被访问的邻接结点
int w = getNextNeigbor2(u);
if(w != -1) {
//打印邻接结点并将其放入队列中,并标记已经被访问
System.out.println(getVertexByIndex(w));
isVisable[w] = true;
queue.addLast(w);
//如果当前结点已经遍历过所有其邻接结点,则从队列中弹出一个结点,并访问该结点所有邻接结点
}else {
// 如果队列无元素可弹,结束循环
if(queue.size() == 0) {
break;
}
u = queue.removeFirst();
}
}
}
//重载广度优先遍历
public void bfs() {
//所有结点必须遍历 - 因为存在没有边的结点
for(int i = 0; i < getVertexsNum(); i++) {
if( isVisable[i] == false) {
bfs(i);
}
}
//恢复遍历前的状态
for(int i = 0; i < isVisable.length; i++) {
isVisable[i] = false;
}
}
2.1.2.3 案例讲解#
#深度优先遍历DFS
1 -> 2 -> 4 -> 8 -> 5 -> 3 -> 6 -> 7
#广度优先遍历BFS
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8