数据结构-图

92 篇文章 0 订阅
16 篇文章 0 订阅

  • 由于线性表和树结构都局限于一个直接前驱和一个直接后继的关系, 无法表示多对多的关系. 此时通过图来实现

概述

  • 图是一种数据结构, 其中节点可以有零个或多个相邻节点. 节点与节点之间的的连接称为边. 节点也可以称为顶点
  • 图的常用概念:
  1. 顶点(Vertex)
  2. 边(Edge)

在这里插入图片描述

  1. 路径: 比如从 D->C的路径有2个
  • (1) D->B->C
  • (2) D->A->B-C
  1. 无向图: 顶点之间的连接没有方向, 比如 A-B, 既可以是 A->B也可以 B->A
  2. 有向图: 顶点之间的连接有方向, 比如 A-B, 只能是 A->B不能是 B->A

在这里插入图片描述

  1. 带权图: 边带权值的图, 又称为网

在这里插入图片描述

2种表示方式

  1. 邻接矩阵(Adacency Matrix)是表示图形中顶点之间相邻关系的矩阵, 一般使用二维数组来实现. 此方式会给没有的边分配空间
  2. 邻接表的实现是只关心存在的边, 不表示没有的边, 因此也不会给没有的边分配空间, 一般使用数组+链表结构来实现

两种访问策略

  1. 深度优先搜索(Depth First Search)
  • (1) 深度优先遍历, 从初始访问节点出发, 初始访问节点可能有多个邻接节点, 深度优先遍历的策略就是首先访问第一个邻接节点, 然后再以这个被访问的邻接节点作为初始节点, 访问它的第一个邻接节点, 可以这样理解: 每次都在访问完当前节点后首先访问当前节点的第一个邻接节点
  • (2) 我们可以看到, 这样的访问策略是优先往纵向挖掘深入, 而不是对一个节点的所有邻接节点进行横向访问
  • (3) 显然, 深度优先搜索是一个递归的过程
  1. 广度优先搜索(Broad First Search)
  • 类似于一个分层搜索的过程, 广度优先遍历需要使用一个队列以保持访问过的节点的顺序, 以便按这个顺序来访问这些节点的邻接节点
  • 广度优先遍历算法步骤:
  1. 访问初始节点 v并标记节点 v为已访问
  2. 节点 v入队列
  3. 当队列非空时, 继续执行, 否则算法结束
  4. 出队列, 取得队头节点 u
  5. 查找节点 u的第一个邻接节点 w
  6. 若节点 u的邻接节点 w不存在, 则转到步骤3; 否则循环执行以下三个步骤:
  • (1) 若节点 w尚未被访问, 则访问节点 w并标记为已访问
  • (2) 节点 w入队列
  • (3) 查找节点 u的继 w邻接节点后的下一个邻接节点 w, 转到步骤6

public class GraphApp {
    /** 存储节点(顶点)列表*/
    private List<String> vertexList;
    /** 存储图对应的邻结矩阵*/
    private int[][] adjacencyMatrix;
    /** 记录节点是否已被访问*/
    private boolean[] isVisited;

    public static void main(String[] args) {
//        /** 定义节点(顶点)*/
//        String[] vertexs = {"A", "B", "C", "D", "E"};
//        /** 创建图实例*/
//        GraphApp graph = new GraphApp(vertexs.length);
//        /** 更新顶点与顶点间的边的关系*/
//        graph.insertEdge(0, 1, 1); // A->B 和 B->A
//        graph.insertEdge(0, 2, 1);
//        graph.insertEdge(1, 2, 1);
//        graph.insertEdge(1, 3, 1);
//        graph.insertEdge(1, 4, 1);

        /** 定义顶点*/
        String[] vertexs = {"1", "2", "3", "4", "5", "6", "7", "8"};
        /** 创建图实例*/
        GraphApp graph = new GraphApp(vertexs.length);
        /** 更新顶点与顶点间的边的关系*/
        graph.insertEdge(0, 1, 1); // 0->1 和 1->0
        graph.insertEdge(0, 2, 1); // 0->2 和 2->0
        graph.insertEdge(1, 3, 1); // 1->3 和 3->1
        graph.insertEdge(1, 4, 1);
        graph.insertEdge(3, 7, 1);
        graph.insertEdge(4, 7, 1);
        graph.insertEdge(2, 5, 1);
        graph.insertEdge(2, 6, 1);
        graph.insertEdge(5, 6, 1);

        /** 循环的添加顶点*/
        for(String vertex: vertexs) {
            graph.insertVertex(vertex);
        }
        /** 打印图对应的邻接矩阵*/
        graph.showGraph();
        System.out.println("深度遍历:");
        graph.depthFirstSearch();
        System.out.println();

        System.out.println("广度优先:");
        graph.broadFirstSearch();
    }

    public GraphApp(int n) {
        adjacencyMatrix = new int[n][n];
        vertexList = new ArrayList<>(n);
    }

    /**
     * @param v1 表示图横向下标 row
     * @param v2 表示图纵向下标 col
     * @param weight 表示有效标记*/
    public void insertEdge(int v1, int v2, int weight) {
        adjacencyMatrix[v1][v2] = weight;
        adjacencyMatrix[v2][v1] = weight;
    }

    /** 打印图对应的邻接矩阵*/
    public void showGraph() {
        for(int[] link : adjacencyMatrix) {
            System.out.println(Arrays.toString(link));
        }
    }

    /** 添加节点(顶点)*/
    public void insertVertex(String vertex) {
        vertexList.add(vertex);
    }

    /** 获取第一个邻接节点(顶点)的下标 w*/
    public int getFirstNeighbor(int index) {
        for(int w = 0; w < vertexList.size(); w++) {
            if(adjacencyMatrix[index][w] > 0) {
                return w;
            }
        }
        return -1;
    }

    /** 根据前一个邻接节点的下标来获取下一个邻接节点*/
    public int getNextNeighbor(int v1, int v2) {
        for(int j = v2 + 1; j < vertexList.size(); j++) {
            if(adjacencyMatrix[v1][j] > 0) {
                return j;
            }
        }
        return -1;
    }

    /** 深度优先遍历算法
     *  - i的起始值是 0
     *  - 纵向遍历*/
    private void depthFirstSearch(boolean[] isVisited, int i) {
        /** 打印当前节点(顶点)*/
        System.out.print(vertexList.get(i) + "->");
        /** 同时将当前节点标记为已经访问*/
        isVisited[i] = true;
        /** 查找当前节点的第一个邻接节点 w*/
        int w = getFirstNeighbor(i);
        while(w != -1) { /** 说明有*/
            if(!isVisited[w]) { /** 如果 w节点未被访问过, 递归深度优先遍历*/
                depthFirstSearch(isVisited, w);
            }
            /** 如果 w节点已经被访问过*/
            w = getNextNeighbor(i, w);
        }
    }

    /** 遍历所有的节点, 进行深度优先遍历(搜索)*/
    public void depthFirstSearch() {
        isVisited = new boolean[vertexList.size()];
        /** 遍历所有的节点, 回溯*/
        for(int i = 0; i < vertexList.size(); i++) {
            if(!isVisited[i]) {
                depthFirstSearch(isVisited, i);
            }
        }
    }

    /** 广度优先遍历算法
     *  - i的起始值是 0
     *  - 横向遍历*/
    private void broadFirstSearch(boolean[] isVisited, int i) {
        /** 队列的头节点对应下标*/
        int u ;
        /** 邻接节点下标 w*/
        int w ;
        /** 队列中, 记录节点的访问顺序*/
        LinkedList<Integer> queue = new LinkedList<>();
        /** 打印当前节点(顶点)*/
        System.out.print(vertexList.get(i) + "=>");
        /** 同时将当前节点标记为已经访问*/
        isVisited[i] = true;
        /** 将当前访问节点加入队列*/
        queue.addLast(i);
        while(!queue.isEmpty()) {
            /** 取出队列的头节点下标*/
            u = queue.removeFirst();
            /** 得到第一个邻接节点的下标 w*/
            w = getFirstNeighbor(u);
            while(w != -1) {
                if(!isVisited[w]) { /** 如果 w节点未被访问过*/
                    System.out.print(vertexList.get(w) + "=>");
                    /** 标记已经访问*/
                    isVisited[w] = true;
                    /** 第一个邻接节点入队*/
                    queue.addLast(w);
                }
                /** 以 u为前驱点(u 这个行的), 找 w后面的下一个邻节点*/
                w = getNextNeighbor(u, w);
            }
        }
    }

    /** 遍历所有的节点, 进行广度优先遍历(搜索)*/
    public void broadFirstSearch() {
        isVisited = new boolean[vertexList.size()];
        /** 遍历所有的节点, 回溯*/
        for(int i = 0; i < vertexList.size(); i++) {
            if(!isVisited[i]) {
                broadFirstSearch(isVisited, i);
            }
        }
    }

}

输出:
[0, 1, 1, 0, 0, 0, 0, 0]
[1, 0, 0, 1, 1, 0, 0, 0]
[1, 0, 0, 0, 0, 1, 1, 0]
[0, 1, 0, 0, 0, 0, 0, 1]
[0, 1, 0, 0, 0, 0, 0, 1]
[0, 0, 1, 0, 0, 0, 1, 0]
[0, 0, 1, 0, 0, 1, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0]
深度遍历:
1->2->4->8->5->3->6->7->
广度优先:
1=>2=>3=>4=>5=>6=>7=>8=>

如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值