一篇文章学懂图这种数据结构

  • 为什么要有图
    • 我们学过线性表和树
    • 线性表局限于一个直接前驱和直接后继的关系
    • 树也只有一个直接前驱
    • 当我们需要表示多对多的关系时,这里我们就用到了图
  • 图的举例说明
    • 图是一种数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边。结点也可以称为顶点。
      图
  • 图的常用概念
    • 顶点(vertex)
    • 边(edge)
    • 路径
    • 无向图
      无向图
    • 有向图
    • 带权图
      有向图
  • 图的表示方式
    • 邻接矩阵(二维数组表示)

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

      • 邻接矩阵需要为每个顶点分配n个边的空间,其中很多边是不存在的,会造成空间的一定损失
      • 邻接表的实现只关心存在的边,不关心不存在的边。邻接表由数组+链表组成
        邻接表
  • 图的快速入门
    • 实现如下结构
      图
    • 具体代码在最后面
  • 图的深度优先遍历介绍
    • 介绍
      • 遍历,就是对结点的访问。一个图有那么多个结点,如何遍历这些结点,需要有特定策略,一般有两种访问策略:深度优先遍历、广度优先遍历
    • 深度优先遍历思想
      • 从初始访问结点出发,初始访问结点可能有多个邻接结点。深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点为初始结点,访问它的第一个邻接结点。
      • 我们可以看到,这种访问策略是优先往纵向挖掘深入,而不是对一个结点的所有邻接点进行横向访问
      • 显然,深度优先搜索是一个递归的过程
    • 步骤
      1. 访问初始结点v,并标记结点v为已访问
      2. 查找结点v的第一个邻接结点w
      3. 若w存在,则继续执行4,则回到第一步,将从v的下一个结点继续
      4. 若w未被访问,对w进行深度优先遍历递归(即把w当作另一个v,然后进行步骤123)
      5. 查找结点v的w邻接结点的下一个邻接结点,转到步骤3
        具体代码在最后面
  • 图的广度优先遍历
    • 广度优先遍历思想
      • 图的广度优先搜索(Broad First Search)
      • 类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来访问这些结点的邻接结点
    • 步骤
      1. 访问初始结点v并标记结点v为以访问。
      2. 结点v入队
      3. 当队列非空时,继续执行,否则算法结束
      4. 出队列,取出队头结点u
      5. 查找结点u的第一个邻接结点w
      6. 若结点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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值