圖的概述及DFS與BFS

一、圖的基本概念

1、圖的組成及應用場景

        圖由頂點(vertex)和邊(edge)組成,通常表示爲 G = (V, E) ,G表示一個圖,V是頂點集,E是邊集 。頂點集V有窮且非空 ,任意兩個頂點之間都可以用邊來表示它們之間的關係,邊集E可以是空的。

        下面就是圖的應用:

Alt


2、圖的分類及基本概念

        有向圖:邊有明確方向的圖。
        無向圖:所有邊都沒有方向的圖。
        混合圖:既有有方向的邊又有無方向的邊的圖。

        有向無環圖:從任意頂點出發無法經過若干條邊回到該頂點的圖。

        無向完全圖:任意兩個頂點之間都存在邊的圖。n個頂點的無向完全圖有(n-1) + (n-2) + (n-3) + ... + 2 + 1條邊
        有向完全圖:任意兩個頂點之間都存在兩條相反邊的圖。n個頂點的有向完全圖有n x (n-1)條邊

        入度:以該點爲終點的所有邊的條數。
        出度:以該點爲起點的所有邊的條數。

        連通圖:無向圖任意兩個頂點之間都可相互抵達(直接或間接都算)的圖。
        強連通圖:有向圖任意兩個頂點之間都可相互抵達(直接或間接都算)的圖。

        連通分量:無向圖的極大連通子圖。
        強連通分量:有向圖的極大強連通子圖。






二、圖的構建

1、圖的設計

Alt

2、接口代碼

	public interface Graph<E, V> {
	    int edgesSize();
	
	    int verticesSize();
	
	    void addVertex(E e);
	
	    void addEdge(E from, E to, V weight);
	
	    void addEdge(E from, E to);
	
	    void removeVertex(E e);
	
	    void removeEdge(E from, E to);
	
	    void bfs(E e);
	
	    void dfs(E e);
	}

3、注意點

① 怎麼維護邊的信息和頂點的信息?頂點信息(頂點出度邊、入度邊、外部傳的對象)可用哈希表維護---勢必要重寫hashCode和equals函數,邊信息(起點、終點、權值)用集合足以。

② 怎麼刪除一條邊或一個頂點?刪除邊的時候先判斷便是否存在,存在就從起點的出度邊和終點的入度邊中分別刪除,再從總集合中刪除即可。刪除頂點的時候,需要先遍歷該頂點的出度邊,將所有出度邊終點的入度邊集合中刪除這條邊,同時也要刪除該頂點的出度邊,相當於變遍歷邊刪除,可以用java官方的迭代器完成。


4、搭建圖的代碼

	public class ListGraph<E, V> implements Graph<E, V> {
	    private Map<E, Vertex<E, V>> vertices;
	    private Set<Edge<E, V>> edges;
	
	    private static class Vertex<E, V> {
	        E element;
	        Set<Edge<E, V>> outEdges = new HashSet<>();
	        Set<Edge<E, V>> inEdges = new HashSet<>();
	
	        public Vertex(E element) {
	            this.element = element;
	        }
	
	        @Override
	        public String toString() {
	            return "Vertex{" +
	                    "element=" + element + '}';
	        }
	
	        @Override
	        public boolean equals(Object o) {
	            if (this == o) return true;
	            if (o == null || getClass() != o.getClass()) return false;
	            Vertex<?, ?> vertex = (Vertex<?, ?>) o;
	            return Objects.equals(element, vertex.element);
	        }
	
	        @Override
	        public int hashCode() {
	            return Objects.hash(element);
	        }
	    }
	
	    private static class Edge<E, V> {
	        Vertex<E, V> from, to;
	        V weight;
	
	        public Edge(Vertex<E, V> from, Vertex<E, V> to) {
	            this.from = from;
	            this.to = to;
	        }
	
	        public EdgeInfo<E, V> castToInfo() {
	            EdgeInfo<E, V> info = new EdgeInfo<>();
	            info.setFrom(this.from.element);
	            info.setTo(this.to.element);
	            info.setWeight(this.weight);
	            return info;
	        }
	
	        @Override
	        public boolean equals(Object o) {
	            if (this == o) return true;
	            if (o == null || getClass() != o.getClass()) return false;
	            Edge<?, ?> edge = (Edge<?, ?>) o;
	            return Objects.equals(from, edge.from) &&
	                    Objects.equals(to, edge.to);
	        }
	
	        @Override
	        public int hashCode() {
	            return from.hashCode() * 31 + to.hashCode();
	        }
	
	        @Override
	        public String toString() {
	            return "Edge{" +
	                    "from=" + from.element +
	                    ", to=" + to.element +
	                    ", weight=" + weight +
	                    '}';
	        }
	    }
	
	    public ListGraph() {
	        edges = new HashSet<>();
	        vertices = new HashMap<>();
	    }
	
	    @Override
	    public int edgesSize() {
	        return edges.size();
	    }
	
	    @Override
	    public int verticesSize() {
	        return vertices.size();
	    }
	
	    @Override
	    public void addVertex(E e) {
	        if (vertices.get(e) != null)
	            return;
	        vertices.put(e, new Vertex<>(e));
	    }
	
	    @Override
	    public void addEdge(E from, E to, V weight) {
	        Vertex<E, V> fromVertex = vertices.get(from);
	        Vertex<E, V> 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<E, V> edge = new Edge<>(fromVertex, toVertex);
	        edge.weight = 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 addEdge(E from, E to) {
	        addEdge(from, to, null);
	    }
	
	    @Override
	    public void removeVertex(E e) {
	        Vertex<E, V> vertex = vertices.get(e);
	        if (vertex == null)
	            return;
	
	        //先遍歷頂點的出邊進行相應刪除(邊遍歷邊刪除需要用迭代器)
	        for (Iterator iterator = vertex.outEdges.iterator(); iterator.hasNext(); ) {
	            Edge<E, V> edge = (Edge<E, V>) iterator.next();
	            edge.to.inEdges.remove(edge);
	            iterator.remove();      //將當前遍歷到的元素從遍歷的集合中刪除
	            edges.remove(edge);
	        }
	        //再遍歷頂點的入邊進行相應刪除(邊遍歷邊刪除需要用迭代器)
	        for (Iterator iterator = vertex.inEdges.iterator(); iterator.hasNext(); ) {
	            Edge<E, V> edge = (Edge<E, V>) iterator.next();
	            edge.from.outEdges.remove(edge);
	            iterator.remove();      //將當前遍歷到的元素從遍歷的集合中刪除
	            edges.remove(edge);
	        }
	    }
	
	    @Override
	    public void removeEdge(E from, E to) {
	        Vertex<E, V> fromVertex = vertices.get(from);
	        Vertex<E, V> toVertex = vertices.get(to);
	        if (fromVertex == null || toVertex == null)
	            return;
	
	        Edge<E, V> edge = new Edge<>(fromVertex, toVertex);
	        if (edges.remove(edge)) {
	            fromVertex.outEdges.remove(edge);
	            toVertex.inEdges.remove(edge);
	        }
	    }
    }






二、深度優先搜索

1、遞歸版思路

        先訪問起點,再依次對起點的出度邊進行深度優先搜索。爲了避免重複訪問,還需要增加一個函數參數,用來記錄已經訪問過的頂點,訪問過的結點就不再訪問。當然遞歸結束的條件就是到達的頂點沒有出度邊爲止。


2、遞歸版實現

	@Override
	    public void dfs(E e) {
	        Vertex<E, V> vertex = vertices.get(e);
	        if (vertex == null)
	            return;
	        dfs(vertex, new HashSet<Vertex<E, V>>());
	    }
	
	    /**
	     * 遞歸版深搜
	     *
	     * @param vertex          起點
	     * @param visitedVertices 訪問過的頂點集合
	     */
	    private void dfs(Vertex<E, V> vertex, Set<Vertex<E, V>> visitedVertices) {
	        System.out.println(vertex.element);
	        visitedVertices.add(vertex);
	
	        for (Edge<E, V> edge : vertex.outEdges) {
	            if (visitedVertices.contains(edge.to))
	                continue;
	            dfs(edge.to, visitedVertices);
	        }
	    }



3、非遞歸版思路

        利用棧模擬遞歸。先將起點入棧並訪問,再從棧頂彈出起點,以後每次當棧頂彈出頂點的時候立即選擇一條該點的出度邊(終點未訪問過),將這條邊的起點、終點依次壓入棧中,並訪問該終點。如此反覆,直到棧爲空。這裏也需要一個集合記錄訪問過的頂點

4、非遞歸版實現

	    /**
	     * 非遞歸版深搜
	     *
	     * @param beginVertex     起點
	     * @param visitedVertices 訪問過的頂點集合
	     */
	    private void dfs2(Vertex<E, V> beginVertex, Set<Vertex<E, V>> visitedVertices) {
	        Stack<Vertex<E, V>> stack = new Stack<>();
	        stack.push(beginVertex);
	        System.out.println(beginVertex.element);
	        visitedVertices.add(beginVertex);
	
	        while (!stack.isEmpty()) {
	            Vertex<E, V> vertex = stack.pop();
	            for (Edge<E, V> edge : vertex.outEdges) {
	                if (visitedVertices.contains(edge.to))
	                    continue;
	                stack.push(edge.from);
	                stack.push(edge.to);
	                System.out.println(edge.to.element);
	                visitedVertices.add(edge.to);
	                break;
	            }
	        }
	    }






二、廣度優先搜索

1、BFS思路

        和二叉樹的層次遍歷類似,利用隊列實現。先將起點入隊並訪問,再從隊列中出隊一個頂點,將該頂點所有出度邊的終點(未被訪問過的頂點)依次入隊並訪問,每次從隊列中出隊都是相同操作,直到隊列爲空。

2、BFS實現

	@Override
    public void bfs(E e) {
        Vertex<E, V> startVertex = vertices.get(e);
        if (startVertex == null)
            return;

        Queue<Vertex<E, V>> queue = new LinkedList<>();
        Set<Vertex<E, V>> visitedVertices = new HashSet<>();
        queue.offer(startVertex);
        System.out.println(startVertex.element);
        visitedVertices.add(startVertex);
        while (!queue.isEmpty()) {
            Vertex<E, V> vertex = queue.poll();

            for (Edge<E, V> edge : vertex.outEdges) {
                if (visitedVertices.contains(edge.to))
                    continue;
                queue.offer(edge.to);
                System.out.println(edge.to.element);
                visitedVertices.add(edge.to);
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章