【恋上数据结构与算法 第二季】【04】图-基础实现_遍历_拓扑排序


持续学习&持续更新中…

学习态度:脚踏实地


代码实现图时并没有采用传统的邻接矩阵或者邻接表,因为它们都太复杂、太麻烦了。
代码实现采用了一种比较折中的方案,比较偏向于邻接表。

代码实现图时,一般实现为有向图,因为无向图可以用有向图来表达。

图的实现方案

在这里插入图片描述

邻接矩阵

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

邻接表

邻接表只需要一个一维数组

在这里插入图片描述

在这里插入图片描述

图的基础接口

在这里插入图片描述

public interface Graph<V, E> {
    int vertexSize(); // 顶点的数量
    int edgeSize(); // 边的数量

    void addVertex(V v); // 添加一个顶点
    void removeVertex(V v); // 删除一个顶点

    void addEdge(V from, V to); // 添加一条边
    void addEdge(V from, V to, E weight); // 添加一条边(带权值)
    void removeEdge(V from, V to); // 删除一条边
}

顶点、边的定义

在这里插入图片描述

在这里插入图片描述

public class ListGraph<V, E> implements Graph<V, E> {
    // 顶点
    private static class Vertex<V, E> {
        V value; // 顶点存储的元素
        Set<Edge<V, E>> inEdges = new HashSet<>(); // 以该顶点为终点的边(到达该顶点的边)
        Set<Edge<V, E>> outEdges = new HashSet<>(); // 以该顶点为起点的边(从该顶点出发的边)

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

        @Override
        public boolean equals(Object o) {
            Vertex<V, E> vertex = (Vertex<V, E>) o;
            return Objects.equals(value, vertex.value);
        }

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

        @Override
        public String toString() {
            return value == null ? "null" : value.toString();
        }
    }

    // 边
    private static class Edge<V, E> {
        E weight; // 边的权值
        Vertex<V, E> from; // 这条边从哪个顶点出发
        Vertex<V, E> to; // 这条边要到达哪个顶点

        Edge(Vertex<V, E> from, Vertex<V, E> to, E weight) {
            this.from = from;
            this.to = to;
            this.weight = weight;
        }

        @Override
        public boolean equals(Object o) {
            Edge<V, E> edge = (Edge<V, E>) o;
            return from.equals(edge.from) && to.equals(edge.to);
        }

        @Override
        public int hashCode() {
            int result = 0;
            result = 31 * result + from.hashCode();
            result = 31 * result + to.hashCode();
            return result;
        }

        @Override
        public String toString() {
            return "Edge{" +
                    "from=" + from +
                    ", to=" + to +
                    ", weight=" + weight +
                    '}';
        }
    }
}

图的基础实现

public class ListGraph<V, E> implements Graph<V, E> {
    private final Map<V, Vertex<V, E>> vertices = new HashMap<>();
    private final Set<Edge<V, E>> edges = new HashSet<>();

    public void print() {
        System.out.println("顶点:");
        vertices.forEach((k, v) -> {
            System.out.println(k);
            System.out.println("in:");
            System.out.println(v.inEdges);
            System.out.println("out:");
            System.out.println(v.outEdges);
            System.out.println("---------------------");
        });
        System.out.println("边:");
        edges.forEach(System.out::println);
    }

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

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

    @Override
    public void addVertex(V v) {
        if (vertices.containsKey(v)) return;
        vertices.put(v, new Vertex<>(v));
    }

    @Override
    public void removeVertex(V v) {
//        Vertex<V, E> vertex = vertices.get(v);
//        if (null == vertex) return;
        final Vertex<V, E> removeVertex = vertices.remove(v);
        if (null == removeVertex) return;

        for (Iterator<Edge<V, E>> iterator = removeVertex.outEdges.iterator(); iterator.hasNext(); ) {
            final Edge<V, E> edge = iterator.next();
            edges.remove(edge);
            edge.to.inEdges.remove(edge);
//            iterator.remove();
        }
//        removeVertex.outEdges.clear();
        removeVertex.outEdges = null;
        for (Iterator<Edge<V, E>> iterator = removeVertex.inEdges.iterator(); iterator.hasNext(); ) {
            final Edge<V, E> edge = iterator.next();
            edges.remove(edge);
            edge.from.outEdges.remove(edge);
//            iterator.remove();
        }
//        removeVertex.inEdges.clear();
        removeVertex.inEdges = null;
    }

    @Override
    public void addEdge(V from, V to) {
        addEdge(from, to, null);
    }

    // 如果发现某个顶点不存在那么需要创建该顶点
    @Override
    public void addEdge(V from, V to, E weight) {
        Vertex<V, E> fromVertex = vertices.get(from);
        Vertex<V, E> toVertex = vertices.get(to);
        if (fromVertex == null) {
            fromVertex = new Vertex<>(from);
            vertices.put(from, fromVertex);
        }
        if (toVertex == null) {
            toVertex = new Vertex<>(to);
            vertices.put(to, toVertex);
        }
        Edge<V, E> edge = new Edge<>(fromVertex, toVertex, weight);
        if (fromVertex.outEdges.remove(edge)) { // 如果已经存在这条边了
            toVertex.inEdges.remove(edge);
            edges.remove(edge);
        }
        fromVertex.outEdges.add(edge);
        toVertex.inEdges.add(edge);
        edges.add(edge);
    }

    @Override
    public void removeEdge(V from, V to) {
        Vertex<V, E> fromVertex = vertices.get(from);
        Vertex<V, E> toVertex = vertices.get(to);
        if (fromVertex == null || toVertex == null) {
            return;
        }
        Edge<V, E> edge = new Edge<>(fromVertex, toVertex, null);
        if (edges.remove(edge)) { // 如果存在这条边的话,才需要删除
            toVertex.inEdges.remove(edge);
            fromVertex.outEdges.remove(edge);
        }
    }

    // 顶点
    private static class Vertex<V, E> {
        V value; // 顶点存储的元素
        Set<Edge<V, E>> inEdges = new HashSet<>(); // 以该顶点为终点的边(到达该顶点的边)
        Set<Edge<V, E>> outEdges = new HashSet<>(); // 以该顶点为起点的边(从该顶点出发的边)

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

        @Override
        public boolean equals(Object o) {
            Vertex<V, E> vertex = (Vertex<V, E>) o;
            return Objects.equals(value, vertex.value);
        }

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

        @Override
        public String toString() {
            return value == null ? "null" : value.toString();
        }
    }

    // 边
    private static class Edge<V, E> {
        E weight; // 边的权值
        Vertex<V, E> from; // 这条边从哪个顶点出发
        Vertex<V, E> to; // 这条边要到达哪个顶点

        Edge(Vertex<V, E> from, Vertex<V, E> to, E weight) {
            this.from = from;
            this.to = to;
            this.weight = weight;
        }

        @Override
        public boolean equals(Object o) {
            Edge<V, E> edge = (Edge<V, E>) o;
            return from.equals(edge.from) && to.equals(edge.to);
        }

        @Override
        public int hashCode() {
            int result = 0;
            result = 31 * result + from.hashCode();
            result = 31 * result + to.hashCode();
            return result;
        }

        @Override
        public String toString() {
            return "Edge{" +
                    "from=" + from +
                    ", to=" + to +
                    ", weight=" + weight +
                    '}';
        }
    }
}

图的遍历

在这里插入图片描述

广度优先搜索

使用广度优先搜索遍历图时,从不同的顶点出发,遍历到的结果是不一样的(需要注意的是,有的顶点有时是遍历不到的)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

    // 广度优先搜索
    @Override
    public void bfs(V begin) {
        final Vertex<V, E> beginVertex = vertices.get(begin);
        if (null == beginVertex) return;

        Queue<Vertex<V, E>> queue = new LinkedList<>();
        Set<Vertex<V, E>> set = new HashSet<>();
        queue.offer(beginVertex);
        set.add(beginVertex);

        while (!queue.isEmpty()) {
            final Vertex<V, E> vertex = queue.poll();

            System.out.println(vertex); // 这里的遍历只是简单的打印一下顶点

            for (Edge<V, E> edge : vertex.outEdges) {
                if (set.contains(edge.to)) continue;
                queue.offer(edge.to);
                set.add(edge.to);
            }
        }
    }

深度优先搜索

使用深度优先搜索遍历图时,从不同的顶点出发,与广度优先搜索一样,遍历到的结果也会是不一样的(需要注意的是,有的顶点有时是遍历不到的);

使用深度优先搜索遍历图时,从相同的顶点出发进行遍历,也会有很多条路径可以走。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

    // 深度优先搜索——递归实现
    @Override
    public void dfs(V begin) {
        final Vertex<V, E> beginVertex = vertices.get(begin);
        if (null == beginVertex) return;
        dfs(beginVertex, new HashSet<>());
    }

    private void dfs(Vertex<V, E> vertex, Set<Vertex<V, E>> set) {
        System.out.println(vertex);
        set.add(vertex);
        for (Edge<V, E> edge : vertex.outEdges) {
            if (set.contains(edge.to)) continue;
            dfs(edge.to, set);
        }
    }

在这里插入图片描述

在这里插入图片描述

    // 深度优先搜索——非递归实现
    @Override
    public void dfs(V begin) {
        final Vertex<V, E> beginVertex = vertices.get(begin);
        if (null == beginVertex) return;

        Set<Vertex<V, E>> set = new HashSet<>();
        Stack<Vertex<V, E>> stack = new Stack<>();
        stack.push(beginVertex);
        set.add(beginVertex);
        System.out.println(beginVertex);
        while (!stack.isEmpty()) {
            final Vertex<V, E> vertex = stack.pop();
            for (Edge<V, E> edge : vertex.outEdges) {
                if (set.contains(edge.to)) continue;
                stack.push(edge.from);
                stack.push(edge.to);
                set.add(edge.to);
                System.out.println(edge.to);
                break;
            }
        }
    }

修改遍历接口

public interface Graph<V, E> {
    int vertexSize(); // 顶点的数量
    int edgeSize(); // 边的数量
    void addVertex(V v); // 添加一个顶点
    void removeVertex(V v); // 删除一个顶点
    void addEdge(V from, V to); // 添加一条边 // 如果发现某个顶点不存在那么自动创建该顶点
    void addEdge(V from, V to, E weight); // 添加一条边(带权值) // 如果发现某个顶点不存在那么自动创建该顶点
    void removeEdge(V from, V to); // 删除一条边
    
//    void bfs(V begin); // 广度优先搜索遍历
//    void dfs(V begin); // 深度优先搜索遍历
    void bfs(V begin, Visitor<V> visitor); // 广度优先搜索遍历
    void dfs(V begin, Visitor<V> visitor); // 深度优先搜索遍历
    interface Visitor<V> {
        void vertex(V v);
    }
}
    // 广度优先搜索
    @Override
    public void bfs(V begin, Visitor<V> visitor) {
        if (null == visitor) return;
        final Vertex<V, E> beginVertex = vertices.get(begin);
        if (null == beginVertex) return;

        Queue<Vertex<V, E>> queue = new LinkedList<>();
        Set<Vertex<V, E>> set = new HashSet<>();
        queue.offer(beginVertex);
        set.add(beginVertex);
        while (!queue.isEmpty()) {
            final Vertex<V, E> vertex = queue.poll();
            visitor.vertex(vertex.value);
            for (Edge<V, E> edge : vertex.outEdges) {
                if (set.contains(edge.to)) continue;
                queue.offer(edge.to);
                set.add(edge.to);
            }
        }
    }

    // 深度优先搜索——非递归实现
    @Override
    public void dfs(V begin, Visitor<V> visitor) {
        if (null == visitor) return;
        final Vertex<V, E> beginVertex = vertices.get(begin);
        if (null == beginVertex) return;

        Set<Vertex<V, E>> set = new HashSet<>();
        Stack<Vertex<V, E>> stack = new Stack<>();
        stack.push(beginVertex);
        set.add(beginVertex);
        visitor.vertex(beginVertex.value);
        while (!stack.isEmpty()) {
            final Vertex<V, E> vertex = stack.pop();
            for (Edge<V, E> edge : vertex.outEdges) {
                if (set.contains(edge.to)) continue;
                stack.push(edge.from);
                stack.push(edge.to);
                set.add(edge.to);
                visitor.vertex(edge.to.value);
                break;
            }
        }
        
	    // 深度优先搜索——递归实现
	    public void dfs2(V begin, Visitor<V> visitor) {
	        if (null == visitor) return;
	        final Vertex<V, E> beginVertex = vertices.get(begin);
	        if (null == beginVertex) return;
	        dfs2(beginVertex, new HashSet<>(), visitor);
	    }
	
	    private void dfs2(Vertex<V, E> vertex, Set<Vertex<V, E>> set, Visitor<V> visitor) {
	        visitor.vertex(vertex.value);
	        set.add(vertex);
	        for (Edge<V, E> edge : vertex.outEdges) {
	            if (set.contains(edge.to)) continue;
	            dfs2(edge.to, set, visitor);
	        }
	    }
    }

如果想要在遍历的过程中可以停止(退出)遍历的话:

    void bfs(V begin, Visitor<V> visitor); // 广度优先搜索遍历
    void dfs(V begin, Visitor<V> visitor); // 深度优先搜索遍历
// 可以停止遍历
    interface Visitor<V> {
        boolean vertex(V v); // 返回true,就终止遍历
    }
// 可以停止遍历

    // 广度优先搜索
    @Override
    public void bfs(V begin, Visitor<V> visitor) {
        if (null == visitor) return;
        final Vertex<V, E> beginVertex = vertices.get(begin);
        if (null == beginVertex) return;

        Queue<Vertex<V, E>> queue = new LinkedList<>();
        Set<Vertex<V, E>> set = new HashSet<>();
        queue.offer(beginVertex);
        set.add(beginVertex);
        while (!queue.isEmpty()) {
            final Vertex<V, E> vertex = queue.poll();
            if (visitor.vertex(vertex.value)) return;
            for (Edge<V, E> edge : vertex.outEdges) {
                if (set.contains(edge.to)) continue;
                queue.offer(edge.to);
                set.add(edge.to);
            }
        }
    }

    // 深度优先搜索——非递归实现
    @Override
    public void dfs(V begin, Visitor<V> visitor) {
        if (null == visitor) return;
        final Vertex<V, E> beginVertex = vertices.get(begin);
        if (null == beginVertex) return;

        Set<Vertex<V, E>> set = new HashSet<>();
        Stack<Vertex<V, E>> stack = new Stack<>();
        stack.push(beginVertex);
        set.add(beginVertex);
        if (visitor.vertex(beginVertex.value)) return;
        while (!stack.isEmpty()) {
            final Vertex<V, E> vertex = stack.pop();
            for (Edge<V, E> edge : vertex.outEdges) {
                if (set.contains(edge.to)) continue;
                stack.push(edge.from);
                stack.push(edge.to);
                set.add(edge.to);
                if (visitor.vertex(edge.to.value)) return;
                break;
            }
        }
    }

AOV网

作业:自学《AOV网络》

在这里插入图片描述

在这里插入图片描述

拓扑排序

在这里插入图片描述

在这里插入图片描述

卡恩算法的思路是对的,但代码实现时,我们肯定不能将顶点删掉,因为这样会破坏原有的图结构,所以我们代码使用卡恩算法实现拓扑排序时,应该变通一下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

    // 使用卡恩算法对DAG进行拓扑排序
    @Override
    public List<V> topologicalSort() {
        List<V> list = new ArrayList<>(); // list用来存放遍历结果,返回给使用者
        Queue<Vertex<V, E>> queue = new LinkedList<>(); // queue用来存放入度为0的顶点
        Map<Vertex<V, E>, Integer> map = new HashMap<>(); // 顶点的入度表

        // 初始化入度表和queue(将度为0的节点都放入队列)
        vertices.forEach((v, vertex) -> {
            int inSize = vertex.inEdges.size();
            if (inSize == 0) queue.offer(vertex);
            else
                // 初始化入度表时,没有必要将入度为0的顶点放入入度表中
                map.put(vertex, inSize);
        });

        while (!queue.isEmpty()) {
            Vertex<V, E> vertex = queue.poll();
            list.add(vertex.value);
            vertex.outEdges.forEach(edge -> {
                Integer integer = map.get(edge.to);
                integer--;
                if (integer == 0)
                    // 将这个顶点入队后,就不用更新该顶点的入度了
                    queue.offer(edge.to);
                else map.put(edge.to, integer);
            });
        }

        return list;
    }

测试:

在这里插入图片描述

public class Data {
    public static final Object[][] TOPO = {
            {0, 2},
            {1, 0},
            {2, 5}, {2, 6},
            {3, 1}, {3, 5}, {3, 7},
            {5, 7},
            {6, 4},
            {7, 6}
    };
}
    /**
     * 有向图
     */
    private static Graph<Object, Double> directedGraph(Object[][] data) {
        Graph<Object, Double> graph = new ListGraph<>();
        for (Object[] edge : data) {
            if (edge.length == 1) {
                graph.addVertex(edge[0]);
            } else if (edge.length == 2) {
                graph.addEdge(edge[0], edge[1]);
            } else if (edge.length == 3) {
                double weight = Double.parseDouble(edge[2].toString());
                graph.addEdge(edge[0], edge[1], weight);
            }
        }
        return graph;
    }

    /**
     * 无向图
     */
    private static Graph<Object, Double> undirectedGraph(Object[][] data) {
        Graph<Object, Double> graph = new ListGraph<>();
        for (Object[] edge : data) {
            if (edge.length == 1) {
                graph.addVertex(edge[0]);
            } else if (edge.length == 2) {
                graph.addEdge(edge[0], edge[1]);
                graph.addEdge(edge[1], edge[0]);
            } else if (edge.length == 3) {
                double weight = Double.parseDouble(edge[2].toString());
                graph.addEdge(edge[0], edge[1], weight);
                graph.addEdge(edge[1], edge[0], weight);
            }
        }
        return graph;
    }

    private static void testTopologicalSort() {
        Graph<Object, Double> graph = directedGraph(Data.TOPO);
        graph.topologicalSort().forEach(System.out::println);
    }
    
    public static void main(String[] args) {
        testTopologicalSort();
    }

参考

小码哥李明杰老师课程: 恋上数据结构与算法 第二季.


本文完,感谢您的关注支持!


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值