图——创建、表示、遍历(深度优先和广度优先)

图——创建、表示、遍历(深度优先和广度优先)

本篇博文通过学习尚硅谷韩老师《Java数据结构与算法》课程所做,在此非常感谢!

概述

须知:图是一种数据结构,其中节点可以具有零个或多个相邻元素,两个节点之间的连接称为边,节点也可以称为顶点;

图的概念

  • 顶点(vertex)
  • 边(edge)
  • 路径
  • 无向图
  • 有向图

无向图

在这里插入图片描述

有向图

在这里插入图片描述

带权图

在这里插入图片描述

图(无向)的创建和表示

图的两种表示方式
在这里插入图片描述

图的创建和邻接矩阵表示的实现

package edu.hebeu.graph;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;

/**
 * 图
 * @author 13651
 *
 */
public class Graph {
	
	/**
	 * 存放图的顶点的集合
	 */
	private ArrayList<String> vertexList;
	
	/**
	 * 存放图对应的邻接矩阵
	 */
	private int[][] adjacencyMatrix;
	
	/**
	 * 存放图的边的数目
	 */
	private int edgesNum;
	
	/**
	 * 存放每个节点被访问的情况:true.已访问;false.未访问
	 */
	private boolean[] isVisiteds;
	
	/**
	 * 创建图对象的构造器
	 * @param n 图的顶点个数
	 */
	public Graph(int n) {
		adjacencyMatrix = new int[n][n]; // 初始化邻接矩阵为n*n
		vertexList = new ArrayList<String>(n); // 初始化存放图顶点的集合
		isVisiteds = new boolean[n]; // 初始化存放每个节点访问情况的数组
	}
	
	/**
	 * 插入顶点的方法
	 * @param vertex 顶点
	 */
	public void insertVertex(String vertex) {
		vertexList.add(vertex);
	}
	
	/** 
	 * @param v1 连接边的一个顶点的下标
	 * @param v2 连接边的另一个顶点的下标
	 * @param weight 边的权重
	 */
	public void insertEdges(int v1, int v2, int weight) {
		// TODO 因为这个图是无向的,所以要如下形式添加边
		adjacencyMatrix[v1][v2] = weight;
		adjacencyMatrix[v2][v1] = weight;
		edgesNum++; // 将边的数目++
	}

	/**
	 * 获取当前节点(i索引对应的节点)的第一个邻接节点的下标值
	 * @param i 当前节点
	 * @return 当前节点(i索引对应的节点)的第一个邻接节点的下标值,存在返回其下标值,不存在返回-1
	 */
	public int getFirstAdjacencyVertexIndex(int i) {
		for (int j = 0; j < vertexList.size(); j++) {
			if (adjacencyMatrix[i][j] > 0) { // 当邻接矩阵此时的节点值大于0,即i与j节点连通
				return j;
			}
		}
		return -1;
	}
	
	/**
	 * 根据前一个邻接节点的下标来获取下一个邻接节点的下标
	 * @param v1
	 * @param v2
	 * @return 存在返回其下标值,不存在返回-1
	 */
	public int getNextAdjacencyVertexIndex(int v1, int v2) {
		for (int j = v2 + 1; j < vertexList.size(); j++) {
			if (adjacencyMatrix[v1][j] > 0) {
				return j;
			}
		}
		return -1;
	}
	
	/**
	 * 显示打印这个图对应的邻接矩阵
	 */
	public void showAdjacencyMatrix() {
		for (int[] tmp : adjacencyMatrix) {
			System.out.println(Arrays.toString(tmp));
		}
	}
	
	/**
	 * 获取图中的顶点个数
	 * @return
	 */
	public int getVertexNum() {
		return vertexList.size();
	}
	
	/**
	 * 获取图中边的个数
	 * @return
	 */
	public int getEdgesNum() {
		return edgesNum;
	}
	
	/**
	 * 返回下标为i的顶点的数据
	 * @param i 顶点对应的下标
	 * @return
	 */
	public String getVertexValue(int i) {
		return vertexList.get(i);
	}
	
	/**
	 * 获取边(连接顶点v1和v2的边)的权值
	 * @param v1 边的一个顶点v1的下标
	 * @param v2 边的另一个顶点v2的下标
	 * @return
	 */
	public int getEdgeWeight(int v1, int v2) {
		return adjacencyMatrix[v1][v2];
	}

}

图(无向)的遍历

介绍
所谓图的遍历,就是对节点的访问,一般有两种访问策略:1、深度优先策略;2、广度优先策略;

深度优先

基本思想

  • 深度优先遍历,从初始访问节点出发,初始访问节点可能有多个邻接节点,深度优先遍历的策略就是首先访问第一个邻接节点,然后再以这个被访问的邻接节点做为初始节点,访问它的第一个邻接节点可以理解为:每次都在访问完当前节点后首先访问当前节点的第一个邻接节点;
  • 我们可以看到这样的访问策略是优先向纵向挖掘深入;
  • 深度优先是一个递归的过程;

算法步骤:

  • 访问初始节点V,并标记节点V为已访问;
  • 查找节点V的第一个邻接节点W;
  • 若W存在,则继续执行下一步;若W不存在,则回到第1步,将从V的下一个节点继续;
  • 若W未被访问,对W进行深度优先遍历递归(即把W当作另一个V,再依次执行上面的1、2、3步);
  • (执行到此,说明W已经被访问了)查找结点V的W邻接点的下一个邻接节点,转至第3步;

代码实现,在上面的图的类中添加如下的实例方法:

	/** 外部调用的深度优先遍历方法
	 * 对dfs深度优先遍历的重载,遍历所有的节点并进行dfs深度优先遍历
	 */
	public void dfs() {
		isVisiteds = new boolean[getVertexNum()]; // 初始化存放每个节点访问情况的数组
		// TODO 遍历所有的节点,进行深度优先遍历dfs,相当于回溯
		for (int i = 0; i < getVertexNum(); i++) {
			if (!isVisiteds[i]) { // 如果该节点没有访问
				dfs(isVisiteds, i);
			}
		}
	}
	
	/**
	 * 深度优先遍历算法
	 * 对一个节点v进行广度优先的方法
	 * @param isVisiteds
	 * @param i 当前进行深度优先遍历的节点
	 * 
	 *	算法步骤:
		  1.访问初始节点V,并标记节点V为已访问;
		  2.查找节点V的第一个邻接节点W;
		  3.若W存在,则继续执行下一步;若W不存在,则回到第1步,将从V的下一个节点继续;
		  4.若W未被访问,对W进行深度优先遍历递归(即把W当作另一个V,再依次执行上面的1、2、3步);
		  5.(执行到此,说明W已经被访问了)查找结点V的W邻接点的下一个邻接节点,转至第3步;
	 */
	private void dfs(boolean[] isVisiteds, int v) {
		// TODO 1. 首先我们访问该节点,然后将该节点设置为已访问
		System.out.print(getVertexValue(v) + " -> "); isVisiteds[v] = true;
		// TODO 2. 查找节点V的第一个邻接节点W
		int w = getFirstAdjacencyVertexIndex(v);
		// TODO 3. 若W存在,则继续执行下一步;若W不存在,则回到第1步,将从V的下一个节点继续;
		while (w != -1) { // w存在
			if (!isVisiteds[w]) { // w没有被访问
				dfs(isVisiteds, w); // 对w进行深度优先遍历递归
			}
			// TODO 5. (执行到此,说明W已经被访问了)查找结点V的W邻接点的下一个邻接节点,转至第3步;
			w = getNextAdjacencyVertexIndex(v, w);
		}
		
	}

广度优先

基本思想
图的广度优先类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的节点的顺序,以便按照这个顺序来访问这些节点的邻接节点;

算法步骤:

  • 访问初始节点V并标记节点V为已访问;
  • 节点V入队列;
  • 当队列非空时,继续执行,否则算法结束(节点V的算法结束);
  • 出队列,取得头结点U;
  • 查找节点U的第一个邻接节点W;
  • 若节点U的邻接节点W不存在,则转到3步骤;否则循环执行下面的3步;
    • 若节点W尚未被访问,则访问节点W并标记为已访问;
    • 节点W入队列;
    • 查找节点U的继W邻接节点后的一个邻接节点W,转到6步骤;

代码实现,在上面的图的类中添加如下的实例方法:

	/* 外部调用的广度优先遍历方法
	 * 对bfs广度优先遍历的重载,遍历所有的节点并进行bfs广度优先遍历
	 */
	public void bfs() {
		isVisiteds = new boolean[getVertexNum()]; // 初始化存放每个节点访问情况的数组
		// TODO 遍历所有的节点,进行广度优先遍历bfs,相当于回溯
		for (int i = 0; i < vertexList.size(); i++) {
			if (!isVisiteds[i]) { // 如果该节点没有被访问
				bfs(isVisiteds, i);
			}
		}
	}
	
	/** 广度优先遍历算法
	 * 对一个节点v进行广度优先的方法
	 * @param isVisiteds
	 * @param v 当前进行广度优先遍历的节点
	 * 
	 * 算法步骤:
		1.访问初始节点V并标记节点V为已访问;
		2.节点V入队列;
		3.当队列非空时,继续执行,否则算法结束(节点V的算法结束);
		4.出队列,取得头结点U;
		5.查找节点U的第一个邻接节点W;
		6.若节点U的邻接节点W不存在,则转到3步骤;否则循环执行下面的3步;
			6.1.若节点W尚未被访问,则访问节点W并标记为已访问;
			6.2.节点W入队列;
			6.3.查找节点U的继W邻接节点后的一个邻接节点W,转到6步骤;
	 */
	private void bfs(boolean[] isVisiteds, int v) {
		int u; // 存放队列头结点对应的下标
		int w; // 邻接节点w
		LinkedList<Integer> queue = new LinkedList<>(); // 队列(使用LinkedList模拟),记录节点访问的顺序
		
		// TODO 1. 访问初始节点V并标记节点V为已访问;
		System.out.print(getVertexValue(v) + " -> "); isVisiteds[v] = true;
		// TODO 2. 节点V入队列
		queue.addLast(v);
		// TODO 3. 当节点非空时,继续执行,否则算法结束(节点V的算法结束);
		while (!queue.isEmpty()) {
			// TODO 4. 出队列,取得头结点U;
			u = queue.removeFirst();
			// TODO 5. 查找节点U的第一个邻接节点W;
			w = getFirstAdjacencyVertexIndex(u);
			// TODO 6. 若节点U的邻接节点W不存在,则转到3步骤;否则循环执行下面的3步;
			while (w != -1) { // W存在(找到了)
				// TODO 6.1. 若节点W尚未被访问,则访问节点W并标记为已访问;
				if (!isVisiteds[w]) {
					System.out.print(getVertexValue(w) + " -> "); isVisiteds[w] = true;
					// TODO 6.2. 节点W入队列;
					queue.addLast(w);
				}
				// TODO 以U为前驱节点,找W后面的下一个邻接节点
				w = getNextAdjacencyVertexIndex(u, w); // 这条语句体现出我们的广度优先
			}
		}
	}

测试

我们已如下的图来创建测试代码
在这里插入图片描述
测试代码:

package edu.hebeu;

import edu.hebeu.graph.Graph;

public class Test {
	public static void main(String[] args) {
		 createGraph();
	}
	
	/**
	 * 创建图并表示为邻接矩阵的测试方法
	 */
	public static void createGraph() {
		// TODO 图对象创建
		Graph graph = new Graph(8); // 创建顶点为8个的无向图对象
		
		// TODO 添加点
		String vertexs[] = { "A", "B", "C", "D", "E", "F", "G", "H" }; // 8个顶点
		for (String vertex : vertexs) {
			graph.insertVertex(vertex); // 将顶点依次添加到图
		}
		
		// TODO 添加边 A-B、A-C、B-C、B-D、B-E
		graph.insertEdges(0, 1, 1); // A-B
		graph.insertEdges(0, 2, 1); // A-C
		graph.insertEdges(1, 3, 1); // B-D
		graph.insertEdges(1, 4, 1); // B-E
		graph.insertEdges(2, 5, 1); // C-F
		graph.insertEdges(2, 6, 1); // C-G
		graph.insertEdges(5, 6, 1); // F-G
		graph.insertEdges(3, 7, 1); // D-H
		graph.insertEdges(4, 7, 1); // E-H
		
		// TODO 显示图对应的邻接矩阵
		System.out.println("图的邻接矩阵");
		graph.showAdjacencyMatrix();
		
		// TODO 测试深度优先遍历
		System.out.print("深度优先遍历(DFS):"); graph.dfs();
		
		// TODO 测试广度优先遍历
		System.out.print("\n广度优先遍历(BFS):"); graph.bfs();
	}
}

运行结果
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值