[falling in love with data structures and algorithms Season 2] [04] Figure - basic implementation_ Traversal_ Topological sorting

Posted by firedrop84 on Fri, 04 Mar 2022 12:27:07 +0100

Continuous learning & continuous updating

Learning attitude: down to earth

The traditional adjacency matrix or adjacency table is not used in the code implementation diagram, because they are too complex and troublesome.
The code implementation adopts a compromise scheme, which is more inclined to the adjacency table.

When the code implements a graph, it is generally implemented as a directed graph, because an undirected graph can be expressed by a directed graph.

Implementation scheme of graph

adjacency matrix

Adjacency table

The adjacency table only needs a one-dimensional array

Basic interface of figure

public interface Graph<V, E> {
    int vertexSize(); // Number of vertices
    int edgeSize(); // Number of edges

    void addVertex(V v); // Add a vertex
    void removeVertex(V v); // Delete a vertex

    void addEdge(V from, V to); // Add an edge
    void addEdge(V from, V to, E weight); // Add an edge (weighted)
    void removeEdge(V from, V to); // Delete an edge
}

Definition of vertex and edge

public class ListGraph<V, E> implements Graph<V, E> {
    // vertex
    private static class Vertex<V, E> {
        V value; // Vertex stored elements
        Set<Edge<V, E>> inEdges = new HashSet<>(); // The edge that ends at the vertex (the edge that reaches the vertex)
        Set<Edge<V, E>> outEdges = new HashSet<>(); // Edge starting from this vertex (edge starting from this vertex)

        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();
        }
    }

    // edge
    private static class Edge<V, E> {
        E weight; // Weight of edge
        Vertex<V, E> from; // Which vertex does this edge start from
        Vertex<V, E> to; // Which vertex does this edge reach

        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 +
                    '}';
        }
    }
}

Basic implementation of graph

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("Vertex:");
        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("Edge:");
        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);
    }

    // If you find that a vertex does not exist, you need to create it
    @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)) { // If this edge already exists
            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)) { // If this edge exists, it needs to be deleted
            toVertex.inEdges.remove(edge);
            fromVertex.outEdges.remove(edge);
        }
    }

    // vertex
    private static class Vertex<V, E> {
        V value; // Vertex stored elements
        Set<Edge<V, E>> inEdges = new HashSet<>(); // The edge that ends at the vertex (the edge that reaches the vertex)
        Set<Edge<V, E>> outEdges = new HashSet<>(); // Edge starting from this vertex (edge starting from this vertex)

        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();
        }
    }

    // edge
    private static class Edge<V, E> {
        E weight; // Weight of edge
        Vertex<V, E> from; // Which vertex does this edge start from
        Vertex<V, E> to; // Which vertex does this edge reach

        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 +
                    '}';
        }
    }
}

Traversal of Graphs

Breadth first search

When using breadth first search to traverse a graph, the traversal results are different from different vertices (it should be noted that some vertices can not be traversed sometimes)

    // Breadth first search
    @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); // The traversal here is simply to print the vertices

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

Depth first search

When using the depth first search to traverse the graph, starting from different vertices, the traversal results will be different as the breadth first search (it should be noted that some vertices can not be traversed sometimes);

When using depth first search to traverse the graph, there will be many paths to traverse from the same vertex.

    // Depth first search -- recursive implementation
    @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);
        }
    }

    // Depth first search -- non recursive implementation
    @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;
            }
        }
    }

Modify traversal interface

public interface Graph<V, E> {
    int vertexSize(); // Number of vertices
    int edgeSize(); // Number of edges
    void addVertex(V v); // Add a vertex
    void removeVertex(V v); // Delete a vertex
    void addEdge(V from, V to); // Add an edge / / if a vertex does not exist, it will be created automatically
    void addEdge(V from, V to, E weight); // Add an edge (with weight) / / if a vertex does not exist, it will be created automatically
    void removeEdge(V from, V to); // Delete an edge
    
//    void bfs(V begin); //  Breadth first search traversal
//    void dfs(V begin); //  depth first search 
    void bfs(V begin, Visitor<V> visitor); // Breadth first search traversal
    void dfs(V begin, Visitor<V> visitor); // depth first search 
    interface Visitor<V> {
        void vertex(V v);
    }
}
    // Breadth first search
    @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);
            }
        }
    }

    // Depth first search -- non recursive implementation
    @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;
            }
        }
        
	    // Depth first search -- recursive implementation
	    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);
	        }
	    }
    }

If you want to stop (exit) traversal during traversal:

    void bfs(V begin, Visitor<V> visitor); // Breadth first search traversal
    void dfs(V begin, Visitor<V> visitor); // depth first search 
// You can stop traversal
    interface Visitor<V> {
        boolean vertex(V v); // If true is returned, the traversal is terminated
    }
// You can stop traversal

    // Breadth first search
    @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);
            }
        }
    }

    // Depth first search -- non recursive implementation
    @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 network

Homework: self study AOV network

Topological sorting

The idea of Kahn algorithm is right, but when implementing the code, we must not delete the vertices, because this will destroy the original graph structure. Therefore, when using Kahn algorithm to realize topological sorting, we should make some modifications:

    // The DAG is topologically sorted by Kahn algorithm
    @Override
    public List<V> topologicalSort() {
        List<V> list = new ArrayList<>(); // list is used to store the traversal results and return them to the user
        Queue<Vertex<V, E>> queue = new LinkedList<>(); // queue is used to store vertices with a penetration of 0
        Map<Vertex<V, E>, Integer> map = new HashMap<>(); // Vertex entry table

        // Initialize the degree table and queue (put the nodes with degree 0 into the queue)
        vertices.forEach((v, vertex) -> {
            int inSize = vertex.inEdges.size();
            if (inSize == 0) queue.offer(vertex);
            else
                // When initializing the in meter, it is not necessary to put the vertices with the in degree of 0 into the in meter
                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)
                    // After joining this vertex into the team, you don't need to update the penetration of this vertex
                    queue.offer(edge.to);
                else map.put(edge.to, integer);
            });
        }

        return list;
    }

Test:

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}
    };
}
    /**
     * Directed graph
     */
    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;
    }

    /**
     * Undirected 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();
    }

reference resources

Little brother Li Mingjie's course: Fall in love with data structures and algorithms Season 2.

At the end of this article, thank you for your attention and support!

Topics: data structure