【数据结构Java】--图、BFS、DFS、拓扑结构

目录

一、图(Graph)

1.概念

2.有向图

 3.出度、入度

4.无向图

5.简单图、多重图

6.无向完全图

 7.有向完全图

8.有权图

9.连通图

 10.连通分量(无向图)

 11.强连通图(有向图)

12.强连通分量

 13.邻接矩阵

1.基本概念

2.邻接矩阵--有权图

14.邻接表

 1.基本概念 

 2.有权图

15.图的实现

图的基础接口

 添加边addEdge

图的总边(edgesSize)

图的点的接口

图的边的接口

删除边removeEdge

完整代码

二、图的遍历

新的图接口

三、广度优先搜索(BFS)

1.图解

​编辑 2.思路

3.代码实现

四、深度优先搜索(DFS)

1.前序遍历

 2.图解

3.递归实现

 4.通过栈-----非递归实现

1)步骤

2)图解

 3)代码实现

五、AOV网(Activity On Vertex Network)

六、拓扑排序(Topological Sort) 

1.简介

 2.思路

3.代码实现

一、图(Graph)

1.概念

【数据结构Java】--图、BFS、DFS、拓扑结构_第1张图片

2.有向图

【数据结构Java】--图、BFS、DFS、拓扑结构_第2张图片

 3.出度、入度

【数据结构Java】--图、BFS、DFS、拓扑结构_第3张图片

4.无向图

 【数据结构Java】--图、BFS、DFS、拓扑结构_第4张图片

5.简单图、多重图

【数据结构Java】--图、BFS、DFS、拓扑结构_第5张图片

6.无向完全图

【数据结构Java】--图、BFS、DFS、拓扑结构_第6张图片

 7.有向完全图

【数据结构Java】--图、BFS、DFS、拓扑结构_第7张图片

8.有权图

 【数据结构Java】--图、BFS、DFS、拓扑结构_第8张图片

9.连通图

【数据结构Java】--图、BFS、DFS、拓扑结构_第9张图片

 10.连通分量(无向图)

【数据结构Java】--图、BFS、DFS、拓扑结构_第10张图片

 11.强连通图(有向图)

【数据结构Java】--图、BFS、DFS、拓扑结构_第11张图片

12.强连通分量

【数据结构Java】--图、BFS、DFS、拓扑结构_第12张图片

 13.邻接矩阵

1.基本概念

【数据结构Java】--图、BFS、DFS、拓扑结构_第13张图片

2.邻接矩阵--有权图

 【数据结构Java】--图、BFS、DFS、拓扑结构_第14张图片

14.邻接表

 1.基本概念 

【数据结构Java】--图、BFS、DFS、拓扑结构_第15张图片

 2.有权图

【数据结构Java】--图、BFS、DFS、拓扑结构_第16张图片

15.图的实现

图的基础接口

package graph;

public interface Graph {
//    边的数量
    int edgesSize();
//    顶点数量
    int verticesSize();

//    添加顶点
    void addVertex(V v);
//    添加边(无权值)
    void addEdge(V from,V to);
//    添加边(有权值)
    void addEdge(V from,V to,E weight);

//    删除顶点
    void removeVertex(V v);
//    删除边
    void removeEdge(V from,V to);

    interface vertexVisitor{
        boolean visit(V v);
    }
    
    void print();
}

 添加边addEdge

    /**
     * 添加无权值的边
     * @param from
     * @param to
     */
    @Override
    public void addEdge(V from, V to) {
        addEdge(from,to,null);
    }

    /**
     * 添加有权值的边
     * @param from
     * @param to
     * @param weight
     */
    @Override
    public void addEdge(V from, V to, E weight) {
        //根据传入的参数from找到出发点,如果不存在则创建
        Vertex fromVertex=vertices.get(from);
        if (fromVertex==null){
            fromVertex=new Vertex<>(from);
            //将点和对应的点关系存入
            vertices.put(from,fromVertex);
        }
        //根据传入参数to找到终点,如果找不到则创建
        Vertex toVertex=vertices.get(to);
        if (toVertex==null){
            toVertex=new Vertex<>(to);
            //将点和对应的点关系存入
            vertices.put(to,toVertex);
        }
        //根据出发点和终点,创建边
        Edge edge=new Edge<>(fromVertex,toVertex);
        edge.weight=weight;//有权值加上,无权值则为null

        //不管原来是否存在,都先删除,在添加进去
        if (fromVertex.outEdges.contains(edge)){ //说明存在
            toVertex.inEdges.remove(edge);
            //在整个图中的边减少
            edges.remove(edge);
        }
        fromVertex.outEdges.add(edge);
        toVertex.inEdges.add(edge);
        //在整个图中的边增加
        edges.add(edge);

    }

图的总边(edgesSize)

    public int edgesSize() {
        return edges.size();
    }

图的点的接口

    /**
     * 顶点
     * @param 
     * @param 
     */
    private static class Vertex{
        //顶点值
        V value;
        Set> inEdges=new HashSet<>();
        Set> outEdges=new HashSet<>();

        public Vertex(V value) {
            this.value = value;
        }

        /**
         * 比较两个顶点是否相等
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            return Objects.equals(value,((Vertex)obj).value);
        }

        @Override
        public int hashCode() {
            return value==null ? 0: value.hashCode();
        }
    }

图的边的接口

    /**
     * 边
     * @param 
     * @param 
     */
    private static class Edge {
        Vertex from;
        Vertex to;
        E weight;

        public Edge(Vertex from, Vertex to) {
            this.from = from;
            this.to = to;
        }

        /**
         * 判断两条边是否相同
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            Edge edge=(Edge) obj;
            return Objects.equals(from,edge.from) && Objects.equals(to,edge.to);
        }

        /**
         * 如果equals的结果是true说明一样,则hashcode值也一样
         * @return
         */
        @Override
        public int hashCode() {
            return from.hashCode()+to.hashCode();
        }
    }

删除边removeEdge

    /**
     * 删除边
     * @param from
     * @param to
     */
    @Override
    public void removeEdge(V from, V to) {
//        根据传入的from获得起点,不存在则不需要删除
        Vertex fromVertex=vertices.get(from);
        if (fromVertex ==null){
            return;
        }
//        根据传入的to找到终点,不存在则不需要删除
        Vertex toVertex=vertices.get(to);
        if (toVertex==null) return;

//        根据起点和终点获得边,然后删除
        Edge edge=new Edge<>(fromVertex,toVertex);
        if (fromVertex.outEdges.remove(edge)){ //表示删除成功
            toVertex.inEdges.remove(edge);
            edges.remove(edge);
        }
    }

完整代码

package graph;

import java.util.*;

public class ListGraph implements Graph{

    // 传入的V与顶点类Vertex的映射【要记录该点的出入是inEdges还是outEdges】
    private Map> vertices = new HashMap<>();
    // 边的Set集合
    private Set> edges = new HashSet<>();

    public void print(){
        System.out.println("[顶点]-------------------");
        vertices.forEach((V v, Vertex vertex) -> {
            System.out.println(v);
            System.out.println("out-----------");
            System.out.println(vertex.outEdges);
            System.out.println("int-----------");
            System.out.println(vertex.inEdges);
        });
        System.out.println("[边]-------------------");
        edges.forEach((Edge edge) -> {
            System.out.println(edge);
        });
    }

    @Override
    public int edgesSize() {
        return edges.size();
    }

    @Override
    public int verticesSize() {
        return vertices.size();
    }

    @Override
    public void addVertex(V v) {
//        put(key,value)
        vertices.put(v,new Vertex<>(v));
    }

    /**
     * 添加无权值的边
     * @param from
     * @param to
     */
    @Override
    public void addEdge(V from, V to) {
        addEdge(from,to,null);
    }

    /**
     * 添加有权值的边
     * @param from
     * @param to
     * @param weight
     */
    @Override
    public void addEdge(V from, V to, E weight) {
        //根据传入的参数from找到出发点,如果不存在则创建
        Vertex fromVertex=vertices.get(from);
        if (fromVertex==null){
            fromVertex=new Vertex<>(from);
            //将点和对应的点关系存入
            vertices.put(from,fromVertex);
        }
        //根据传入参数to找到终点,如果找不到则创建
        Vertex toVertex=vertices.get(to);
        if (toVertex==null){
            toVertex=new Vertex<>(to);
            //将点和对应的点关系存入
            vertices.put(to,toVertex);
        }
        //根据出发点和终点,创建边
        Edge edge=new Edge<>(fromVertex,toVertex);
        edge.weight=weight;//有权值加上,无权值则为null

        //不管原来是否存在,都先删除,在添加进去
        if (fromVertex.outEdges.contains(edge)){ //说明存在
            toVertex.inEdges.remove(edge);
            //在整个图中的边减少
            edges.remove(edge);
        }
        fromVertex.outEdges.add(edge);
        toVertex.inEdges.add(edge);
        //在整个图中的边增加
        edges.add(edge);

    }

    /**
     * 删除点
     * @param v
     */
    @Override
    public void removeVertex(V v) {
        //根据传入的值找到点并且删除,不存在则不做操作
        Vertex vertex=vertices.remove(v);
        if (vertex==null) return;
//        迭代器遍历集合vertex.outEdges,删除所有从该点出去的边
//        iterator.hasNext():相当于i++
        for (Iterator> iterator = vertex.inEdges.iterator();iterator.hasNext();){
            Edge edge=iterator.next();//遍历从该点出去的边
            edge.to.inEdges.remove(edge);//获取终点进入的边,并从中删除遍历到的边
            //这个remove方法是将当前的这个边删除
            iterator.remove();//将当前遍历到当前遍历到的元素edge从集合vertex.outEdges中删除
            edges.remove(edge);
        }
        // 迭代器遍历集合vertex.inEdges, 删除所有进入该点的边
        for (Iterator> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {
            Edge edge = iterator.next(); // 遍历到的进入该点的边
            edge.from.outEdges.remove(edge); // 获取起点出去的边,并从中删除遍历到的边
            iterator.remove(); // 将当前遍历到的元素edge从集合vertex.inEdges中删掉
            edges.remove(edge);
        }
    }

    /**
     * 删除边
     * @param from
     * @param to
     */
    @Override
    public void removeEdge(V from, V to) {
//        根据传入的from获得起点,不存在则不需要删除
        Vertex fromVertex=vertices.get(from);
        if (fromVertex ==null){
            return;
        }
//        根据传入的to找到终点,不存在则不需要删除
        Vertex toVertex=vertices.get(to);
        if (toVertex==null) return;

//        根据起点和终点获得边,然后删除
        Edge edge=new Edge<>(fromVertex,toVertex);
        if (fromVertex.outEdges.remove(edge)){ //表示删除成功
            toVertex.inEdges.remove(edge);
            edges.remove(edge);
        }
    }

    /**
     * 顶点
     * @param 
     * @param 
     */
    private static class Vertex{
        //顶点值
        V value;
        Set> inEdges=new HashSet<>();
        Set> outEdges=new HashSet<>();

        public Vertex(V value) {
            this.value = value;
        }

        /**
         * 比较两个顶点是否相等
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            return Objects.equals(value,((Vertex)obj).value);
        }

        @Override
        public int hashCode() {
            return value==null ? 0: value.hashCode();
        }
    }

    /**
     * 边
     * @param 
     * @param 
     */
    private static class Edge {
        Vertex from;
        Vertex to;
        E weight;

        public Edge(Vertex from, Vertex to) {
            this.from = from;
            this.to = to;
        }

        /**
         * 判断两条边是否相同
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            Edge edge=(Edge) obj;
            return Objects.equals(from,edge.from) && Objects.equals(to,edge.to);
        }

        /**
         * 如果equals的结果是true说明一样,则hashcode值也一样
         * @return
         */
        @Override
        public int hashCode() {
            return from.hashCode()+to.hashCode();
        }
    }
}

二、图的遍历

图的遍历

从图中某一顶点出发访问图中其余顶点,且每一个顶点仅被访问一次
图有2种常见的遍历方式(有向图、无向图都适用)

  • 广度优先搜索(Breadth First Search,BFS),又称为宽度优先搜索、横向优先搜索。
  • 深度优先搜索(Depth First Search,DFS)

发明“深度优先搜索”算法的2位科学家在1986年共同获得计算机领域的最高奖:图灵奖。

新的图接口

package graph;

import java.util.List;
import java.util.Set;

public interface Graph {
//    边的数量
    int edgesSize();
//    顶点数量
    int verticesSize();

//    添加顶点
    void addVertex(V v);
//    添加边(无权值)
    void addEdge(V from,V to);
//    添加边(有权值)
    void addEdge(V from,V to,E weight);

//    删除顶点
    void removeVertex(V v);
//    删除边
    void removeEdge(V from,V to);

    void print();



    void bfs(V begin, vertexVisitor visitor); // 广度优先搜索


    /**
     * 非递归版的深度优先搜索
     * @param begin
     * @param visitor
     */
    void dfs(V begin, vertexVisitor visitor);


    List topologicalSort(); // 拓扑排序

    interface vertexVisitor{
        boolean visit(V v);
    }
}

三、广度优先搜索(BFS)

1.图解

之前所学的二叉树层序遍历就是一种广度优先搜索。

注:BFS结果不唯一

经历一层可以访问到的

【数据结构Java】--图、BFS、DFS、拓扑结构_第17张图片

【数据结构Java】--图、BFS、DFS、拓扑结构_第18张图片 2.思路

将该点从队列取出来,然后再将其所子节点依次放入队列

【数据结构Java】--图、BFS、DFS、拓扑结构_第19张图片

从某个点开始,将它可以到达的点放入队列,如果已经访问过则跳过,然后从队列中取出点重复该过程。

第一层:假设从点A开始,它可以到达B、F,则将B、F入队。
此时队列中元素 [B、F]
第二层:队头B出队,B可以到达C、I、G,将C、I、G入队。
此时队列中元素 [F、C、I、G]
第三层:队头F出队,F可以到达G、E,但G已访问过,将E入队。
此时队列中元素 [C、I、G、E]
第四层:队头C出队,C可以到达I、D,但I已访问过,将D入队。
此时队列中元素 [I、G、E、D]
第五层:队头I出队,I可以到达D,但D已访问过,不执行操作。
此时队列中元素 [G、E、D]
第六层:队头G出队,G可以到达D、H,但D已访问过,将H入队。
此时队列中元素 [E、D、H]
第七层:队头E出队,E可以到达D、H、F,都访问过,不执行操作。
此时队列中元素 [D、H]
第八层:队头D出队,D可以到达C、H、E,都访问过,不执行操作。
此时队列中元素 [H]
第九层:队头H出队,H可以到达D、G、E,都访问过,不执行操作。
此时队列中元素 []
队列为空,广度优先搜索结束。

3.代码实现

    /**
     * 广度优先搜索:BFS
     * @param begin
     * @param visitor
     */
    @Override
    public void bfs(V begin, vertexVisitor visitor) {
        if (visitor ==null) return;
        //根据传入的值begin找到顶点
        Vertex beginVertex=vertices.get(begin);
        //该顶点不存在,不做操作
        if (beginVertex==null) return;

        //存放已经访问过的节点
        //因为如果不存放,则遍历到下一个顶点的时候,会将刚刚出队列的数值再一次放入
        Set> visitedVertices=new HashSet<>();
        //临时存放
        Queue> queue=new LinkedList<>();
        queue.offer(beginVertex); //元素入队
        //将已经加入过队列的元素标记一下
        visitedVertices.add(beginVertex);

        //思考参考二叉树层次遍历,队列存放每一层的顶点,用集合记录已经访问过的点
        while (!queue.isEmpty()){
            //从队列中取出第一个顶点
            Vertex vertex=queue.poll();
            if (visitor.visit(vertex.value)) return;
            //遍历【从队列中取出的顶点】的出去的边,将【这些边的终点】入队,并且标记为已经访问过
            for (Edge edge:vertex.outEdges){
                //如果集合中已经记录该顶点,说明已经访问过,跳过进行下一轮
                if (visitedVertices.contains(edge.to)) continue;
                queue.offer(edge.to);
                visitedVertices.add(edge.to);
            }
        }
    }

四、深度优先搜索(DFS)

之前所学的二叉树前序遍历就是一种深度优先搜索。

注:DFS结果不唯一。

1.前序遍历

【数据结构Java】--图、BFS、DFS、拓扑结构_第20张图片

 2.图解

【数据结构Java】--图、BFS、DFS、拓扑结构_第21张图片

【数据结构Java】--图、BFS、DFS、拓扑结构_第22张图片 

3.递归实现

    /**
     * 递归实现--->深度优先遍历:DFS
     * @param begin
     * @param visitedVertices 已经访问过的节点
     */
    /**
     * 递归实现深度优先搜索DFS
     */
    public void dfs(V begin) {
        Vertex beginVertex = vertices.get(begin); // 根据传入的值获取顶点
        if (beginVertex == null) return; // 顶点不存在则不执行操作
        dfs(beginVertex, new HashSet<>()); // 传入的集合,用来记录访问过的顶点
    }
    private void dfs(Vertex vertex, Set> vistedVertices){
        System.out.println(vertex.value);
        vistedVertices.add(vertex);

        for(Edge edge : vertex.outEdges){
            if(vistedVertices.contains(edge.to)) continue;
            //dfs会自己后退
            dfs(edge.to, vistedVertices);
        }
    }

 4.通过栈-----非递归实现

1)步骤

1.从outEdge中选择一条边

2.将选择边的from和to边,按顺序入栈【先入from再入to】

3.打印选择边的to

4.将to边添加到已经访问的范围中

5.break

2)图解

【数据结构Java】--图、BFS、DFS、拓扑结构_第23张图片

 3)代码实现

    /**
     * 非递归版(通过栈实现):深度优先搜索(DFS)
     * @param
     */

    @Override
    public void dfs(V begin, vertexVisitor visitor) {
        if (visitor ==null) return;

        //查看该点是否存在
        Vertex beginVertex=vertices.get(begin);
        if (begin==null) return;
        //创建一个set存放已经访问过的节点
        Set> visitedVertices=new HashSet<>();
        //创建一个栈临时存放
        Stack> stack=new Stack<>();

        //先访问起点
        stack.push(beginVertex);
        visitedVertices.add(beginVertex);
        //如果该节点已经访问过,则不再放入
        if (visitor.visit(begin)) return;

        while (!stack.isEmpty()){ //如果栈不为空
            Vertex vertex=stack.pop();

            for (Edge edge :vertex.outEdges){ //访问出发点的每一条边
                if (visitedVertices.contains(edge.to)) continue; //表示这条边已经访问过
                //找了一条适合且未遍历过的边
                stack.push(edge.from);//将出发点加入栈中
                stack.push(edge.to);//将出度点加入栈中
                //将加入栈中的节点加入已经访问过发范围中
                visitedVertices.add(edge.to);
                if (visitor.visit(edge.to.value)) return;
                break;
            }
        }
    }

五、AOV网(Activity On Vertex Network)

一项大的工程常被分为多个小的子工

子工程之间可能存在一定的先后顺序,即某些子工程必须在其他的一些子工程完成后才能开始。
在现代化管理中,人们常用有向图来描述和分析一项工程的计划和实施过程,子工程被称为活动(Activity)

以顶点表示活动、有向边表示活动之间的先后关系,这样的图简称为 AOV 网。【有很强的依赖关系】
标准的AOV网必须是一个有向无环图(Directed Acyclic Graph,简称 DAG)

【数据结构Java】--图、BFS、DFS、拓扑结构_第24张图片

六、拓扑排序(Topological Sort) 

1.简介

前驱活动:有向边起点的活动称为终点的前驱活动

  • 只有当一个活动的前驱全部都完成后,这个活动才能进行

后继活动:有向边终点的活动称为起点的后继活动

【数据结构Java】--图、BFS、DFS、拓扑结构_第25张图片

 2.思路

可以使用卡恩算法(Kahn于1962年提出)完成拓扑排序。

假设 L 是存放拓扑排序结果的列表:

  • ① 把所有入度为 0 的顶点放入 queue 中,然后把这些顶点从图中去掉,放入list中
  • ② 然后找到已经从queue中移除的入度为0的节点的下一个节点,将下一个节点的入度-1,如果-1后,入度为0,则直接加入queue
  • ③ 重复操作 ①,直到找不到入度为 0 的顶点
  • 如果此时 L 中的元素个数和顶点总数相同,说明拓扑排序完成
  • 如果此时 L 中的元素个数少于顶点总数,说明原图中存在环,无法进行拓扑排序

【数据结构Java】--图、BFS、DFS、拓扑结构_第26张图片

3.代码实现

    /**
     * 拓扑排序
     * @return
     */
    @Override
    public List topologicalSort() {
        List list = new ArrayList<>();
        Queue> queue = new LinkedList<>();
        Map, Integer> ins = new HashMap<>();

        // 初始化(将度为0的节点放入队列)
        vertices.forEach((V v, Vertex vertex) -> {
            int indegree = vertex.inEdges.size(); // 入度
            if(indegree == 0) { // 入度为0,放入队列
                queue.offer(vertex);
            } else { // 入度不为0,用map记录它的入度
                ins.put(vertex, indegree);
            }
        });

        while(!queue.isEmpty()){ // 从队列中取节点
            //取出队列中的第一个元素
            Vertex vertex = queue.poll();
            list.add(vertex.value); // 放入返回结果中

            //遍历取出的节点的出度边
            for (Edge edge : vertex.outEdges){
                // 队列中取出节点所通向节点的入度
                int toIndegree = ins.get(edge.to) - 1;
                if(toIndegree == 0) { // 入度为0,放入队列
                    queue.offer(edge.to);
                } else { // 入度不为0,用map记录它的入度
                    ins.put(edge.to, toIndegree);
                }
            }
        }

        return list;
    }

你可能感兴趣的:(深度优先,数据结构,宽度优先,java)