28 图、图的存储、图的深度优先遍历和广度优先遍历

1. 图基本介绍

1.1 为什么要有图

  1. 线性表局限于一个直接前驱和一个直接后继的关系。
  2. 树也只能有一个直接前驱也就是父节点。
  3. 当我们需要表示多对多的关系时, 这里我们就用到了图。

1.2 图的举例说明

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

1.3 图的常用概念

  1. 顶点

  2. 路径

  3. 无向图

  4. 有向图

  5. 带权图

2. 图的表示方式

  1. 图的表示方式有两种:二维数组表示(邻接矩阵);链表表示(邻接表)。

2.1 邻接矩阵

  1. 邻接矩阵是表示图形中顶点之间相邻关系的矩阵,对于 n 个顶点的图而言,矩阵的 row 和 col 表示的是 1…n 个点。

2.2 邻接表

  1. 邻接矩阵需要为每个顶点都分配 n 个边的空间,其实有很多边都是不存在,会造成空间的一定损失。

  2. 邻接表的实现只关心存在的边,不关心不存在的边。因此没有空间浪费,邻接表由数组+链表组成。

3. 图的邻接矩阵存储方式的代码实现

package com.guli.graph;

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

public class GraphDemo {
    public static void main(String[] args) {
        String[] vertexs = {"A", "B", "C", "D", "E"};
        // 创建图
        Graph graph = new Graph(5);
        // 给图中插入顶点
        for (String vertex : vertexs) {
            graph.insertVertex(vertex);
        }
        // 给图添加边  A-B A-C B-C B-D B-E
        graph.insertEdge("A", "B", 1);
        graph.insertEdge("A", "C", 1);
        graph.insertEdge("B", "C", 1);
        graph.insertEdge("B", "D", 1);
        graph.insertEdge("B", "E", 1);

        // 输出图的邻接矩阵
        graph.showAdjacencyMatrix();
    }

}

class Graph {
    private ArrayList<String> vertexs;
    private int[][] edges;
    private int numOfEdges;

    /**
     * 初始化图
     *
     * @param numOfVertex 顶点个数
     */
    public Graph(int numOfVertex) {
        vertexs = new ArrayList<>(numOfVertex);
        edges = new int[numOfVertex][numOfVertex];
        numOfEdges = 0;
    }

    /**
     * 插入顶点
     *
     * @param vertex 顶点
     */
    public void insertVertex(String vertex) {
        vertexs.add(vertex);
    }

    /**
     * 插入边
     *
     * @param v1     顶点1
     * @param v2     顶点2
     * @param weight 两点之间的权重
     */
    public void insertEdge(String v1, String v2, int weight) {
        int index1 = vertexs.indexOf(v1);
        int index2 = vertexs.indexOf(v2);
        edges[index1][index2] = weight;
        edges[index2][index1] = weight;
        numOfEdges++;
    }

    /**
     * 返回边的条数
     *
     * @return 边的条数
     */
    public int getNumOfEdges() {
        return numOfEdges;
    }

    /**
     * 返回顶点的个数
     *
     * @return 顶点的个数
     */
    public int getNumOfVertexs() {
        return vertexs.size();
    }

    /**
     * 输出图的邻接矩阵
     */
    public void showAdjacencyMatrix() {
        for (int[] edge : edges) {
            System.out.println(Arrays.toString(edge));
        }
    }
}

4. 图的深度优先遍历

4.1 步骤

  1. 访问初始结点 v,并标记结点 v 为已访问。
  2. 查找结点 v 的第一个邻接结点 w。
  3. 若 w 存在,则继续执行 4,如果 w 不存在,则回到第 1 步,将从 v 的下一个结点继续。
  4. 若 w 未被访问,对 w 进行深度优先遍历递归(即把 w 当做另一个 v,然后进行步骤 123)。
  5. 查找结点 v 的 w 邻接结点的下一个邻接结点,转到步骤 3。

4.2 代码实现

		/**
     * 寻找第一个与当前顶点邻接的未被访问过的节点的下标
     *
     * @param vertex 当前节点
     * @return 第一个与当前顶点邻接的未被访问过的节点的下标
     */
    private int getFirstNeighbor(String vertex) {
        // 得到当前节点的下标
        int index = vertexs.indexOf(vertex);
        // 寻找第一个与当前顶点邻接的未被访问过的节点的下标
        for (int i = 0; i < edges[0].length; i++) {
            if (edges[index][i] > 0 && !isVisited.contains(vertexs.get(i))) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 图的深度优先遍历
     *
     * @param startVertex 开始遍历的节点
     */
    public ArrayList<String> dfs(String startVertex) {
        isVisited = new ArrayList<>();
        // 存放最终序列
        ArrayList<String> seq = new ArrayList<>();
        // 记录指向要获取邻接节点的节点的下标
        int temp = 0;
        seq.add(startVertex);
        // 记录访问过的节点
        isVisited.add(startVertex);
        int num = 0;
        // 得到序列
        while (seq.size() < vertexs.size()) {
            // 寻找第一个与当前顶点邻接的未被访问过的节点的下标,并将节点存放进最终序列中
            if ((num = getFirstNeighbor(seq.get(temp))) != -1) {
                seq.add(vertexs.get(num));
                isVisited.add(vertexs.get(num));
                temp = seq.size() - 1;
            } else {
                temp--;
            }
        }
        return seq;
    }

5. 图的广度优先遍历

5.1 步骤

  1. 访问初始结点 v 并标记结点 v 为已访问。
  2. 结点 v 入队列。
  3. 当队列非空时,继续执行,否则算法结束。
  4. 出队列,取得队头结点 u。
  5. 查找结点 u 的所有未被访问过的邻接结点,并入队列。
  6. 循环执行 456。

5.2 代码实现

		/**
     * 获得所有与当前顶点邻接且未被访问过的顶点
     *
     * @param vertex 当前顶点
     * @return 所有与当前顶点邻接且未被访问过的顶点
     */
    private ArrayList<String> getAllNeighbor(String vertex) {
        ArrayList<String> list = new ArrayList<>();
        int index = vertexs.indexOf(vertex);
        for (int i = 0; i < edges.length; i++) {
            if (edges[index][i] > 0 && !isVisited.contains(vertexs.get(i))) {
                list.add(vertexs.get(i));
            }
        }
        return list;
    }

    /**
     * 图的广度优先遍历
     *
     * @param startVertex 开始遍历的节点
     * @return 最终序列
     */
    public ArrayList<String> bfs(String startVertex) {
        isVisited = new ArrayList<>();
        // 存放最终序列
        ArrayList<String> seq = new ArrayList<>();
        // 临时存放部分节点
        Queue<String> queue = new LinkedList<>();
        queue.add(startVertex);
        isVisited.add(startVertex);
        while (seq.size() < vertexs.size() && !queue.isEmpty()) {
            String v = queue.remove();
            seq.add(v);
            queue.addAll(getAllNeighbor(v));
            isVisited.addAll(getAllNeighbor(v));
        }
        return seq;
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值