图的概述及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);
            }
        }
    }
发布了54 篇原创文章 · 获赞 5 · 访问量 4600

猜你喜欢

转载自blog.csdn.net/cj1561435010/article/details/104826174