图的源码、BFS和DFS

图的底层基础代码实现

基础接口

package _02_图._01_graph;

import java.util.List;

public interface Graph<V,E> {
    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<V>{
        boolean visit(V v);
    }
}

完整源码

package _02_图._01_graph;


import java.util.*;
/**
 * 邻接表实现图
 */
@SuppressWarnings("unchecked")
public class ListGraph<V, E> implements Graph<V, E> {
    // 传入的V与顶点类Vertex的映射
    private Map<V, Vertex<V, E>> vertices = new HashMap<>();
    // 边的Set集合
    private Set<Edge<V, E>> edges = new HashSet<>();

    /**
     * 顶点
     */
    private static class Vertex<V, E> {
        V value;
        Set<Edge<V, E>> inEdges = new HashSet<>(); // 进来的边
        Set<Edge<V, E>> outEdges = new HashSet<>(); // 出去的边
        public Vertex(V value){
            this.value = value;
        }
        @Override
        public boolean equals(Object obj) {
            return Objects.equals(value, ((Vertex<V, E>)obj).value);
        }
        @Override
        public int hashCode() {
            return value == null ? 0 : value.hashCode();
        }
        @Override
        public String toString() {
            return value == null ? "null" : value.toString();
        }

    }
    /**
     * 边
     */
    private static class Edge<V, E> {
        Vertex<V, E> from; // 出发点
        Vertex<V, E> to; // 到达点
        E weight;	// 权值

        public Edge(Vertex<V, E> from, Vertex<V, E> to) {
            this.from = from;
            this.to = to;
        }
        @Override
        public boolean equals(Object obj) {
            Edge<V, E> edge = (Edge<V, E>) obj;
            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 + ", to=" + to + ", weight=" + weight + "]";
        }

    }

    public void print(){
        System.out.println("[顶点]-------------------");
        vertices.forEach((V v, Vertex<V, E> 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<V, E> 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) {
        if(vertices.containsKey(v)) return;
        vertices.put(v, new Vertex<>(v));
    }

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

    @Override
    public void addEdge(V from, V to, E weight) {
        // 根据传入的参数from找到起点,如果不存在则创建
        Vertex<V, E> fromVertex = vertices.get(from);
        if(fromVertex == null){
            fromVertex = new Vertex<>(from);
            vertices.put(from, fromVertex);
        }
        // 根据传入的参数to找到终点,如果不存在则创建
        Vertex<V, E> toVertex = vertices.get(to);
        if(toVertex == null){
            toVertex = new Vertex<>(to);
            vertices.put(to, toVertex);
        }

        // 根据出发点与终点,创建边
        Edge<V, E> edge = new Edge<>(fromVertex, toVertex);
        edge.weight = weight; // 有权值则加上权值,无权值则为null

        // 不管原来是否存在,都先删除此边,再添加进去
        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 removeVertex(V v) {
        // 根据传入的值找到点并删除,不存在则不做操作
        Vertex<V, E> vertex = vertices.remove(v);
        if(vertex == null) return;

        // 迭代器遍历集合vertex.outEdges, 删除所有从该点出去的边
        for (Iterator<Edge<V, E>> iterator = vertex.outEdges.iterator(); iterator.hasNext();) {
            Edge<V, E> edge = iterator.next(); // 遍历到的该点出去的边
            edge.to.inEdges.remove(edge);// 获取终点进入的边,并从中删除遍历到的边
            iterator.remove(); // 将当前遍历到的元素edge从集合vertex.outEdges中删掉
            edges.remove(edge);
        }

        // 迭代器遍历集合vertex.inEdges, 删除所有进入该点的边
        for (Iterator<Edge<V, E>> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {
            Edge<V, E> edge = iterator.next(); // 遍历到的进入该点的边
            edge.from.outEdges.remove(edge); // 获取起点出去的边,并从中删除遍历到的边
            iterator.remove(); // 将当前遍历到的元素edge从集合vertex.inEdges中删掉
            edges.remove(edge);
        }

    }

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

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

  
}

图的遍历

  • 从图中某一顶点出发访问图中其余顶点,且每一个顶点仅被访问一次

2种常见的遍历方式:

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

接口中添加bfsdfs方法:

package _02_图._01_graph;

import java.util.List;

public interface Graph<V,E> {
    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 bfs(V begin, vertexVisitor<V> visitor); // 广度优先搜索
    void dfs(V begin, vertexVisitor<V> visitor); // 深度优先搜索

    List<V> topologicalSort(); // 拓扑排序

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

BFS(广度优先搜索)(Breadth First Search)

  • 二叉树层序遍历就是一种广度优先搜索。
  • BFS结果不唯一
    在这里插入图片描述
    在这里插入图片描述

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

第一层:假设从点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,都访问过,不执行操作。
此时队列中元素 []
队列为空,广度优先搜索结束。

代码:

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

        //存放已经访问过的节点
        Set<Vertex<V,E>> visitedVertices = new HashSet<>();
        Queue<Vertex<V,E>> queue = new LinkedList<>();
        queue.offer(beginVertex);//元素入队
        visitedVertices.add(beginVertex);

        //路参考二叉树层次遍历,队列存放每一层的顶点,用集合记录已经访问过的点
        while (!queue.isEmpty()){
            Vertex<V,E> vertex = queue.poll();// 队列中取出一个顶点
            if (visitor.visit(vertex.value)) return;

            //遍历[队列中取出的顶点]的出去的边,将[这些边的终点]入队,并且标记为已经访问过
            for (Edge<V,E> edge : vertex.outEdges){
                //如果集合中已经记录该顶点,说明已经访问过,跳过进行下一轮
                if (visitedVertices.contains(edge.to)) continue;
                queue.offer(edge.to);
                visitedVertices.add(edge.to);
            }
        }

    }

DFS (深度优先搜索)(Depth First Search)

  • 二叉树前序遍历就是一种深度优先搜索
  • DFS结果不唯一

递归实现DFS:

	/**
     * 递归实现深度优先搜索DFS
     */
    @Override
    public void dfs(V begin) {
        Vertex<V, E> beginVertex = vertices.get(begin); // 根据传入的值获取顶点
        if (beginVertex == null) return; // 顶点不存在则不执行操作
        dfs(beginVertex, new HashSet<>()); // 传入的集合,用来记录访问过的顶点
    }


    private void dfs(Vertex<V, E> vertex, Set<Vertex<V, E>> vistedVertices){
        System.out.println(vertex.value);
        vistedVertices.add(vertex);

        for(Edge<V, E> edge : vertex.outEdges){
            if(vistedVertices.contains(edge.to)) continue;
            dfs(edge.to, vistedVertices);
        }
    }

非递归实现DFS:

	/**
     * 非递归实现深度优先搜索DFS
     */
    public void dfs(V begin, vertexVisitor<V> visitor){
        if(visitor == null) return;

        Vertex<V, E> beginVertex = vertices.get(begin);// 根据传入的值获取顶点
        if(begin == null) return;

        Set<Vertex<V, E>> visitedVertices = new HashSet<>();// 传入的集合,用来记录访问过的顶点
        Stack<Vertex<V, E>> stack = new Stack<>();

        stack.push(beginVertex); // 先访问起点
        visitedVertices.add(beginVertex);
        if(visitor.visit(begin)) return;

        while(!stack.isEmpty()){
            Vertex<V, E> vertex = stack.pop();

            for(Edge<V, E> 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;
            }
        }
    }

总结

  • 深度优先更适合目标比较明确,以找到目标为主要目的的情况;
  • 广度优先更适合在不断扩大遍历范围时找到相对最优解的情况。
统一图片采集程序acc商业版 程序功能简介: 系统核心为新云网站内容管理系统 v3.1.0.1231 正式acc版 文章采集的同时可以选择是否下载图片到本地及分页采集。 全站生成HTML页面;增加系统安全性,自由设置生成HTML文件扩展名和存放目录 广告管理功能全部由系统生成JS文件管理, 避免了修改广告代码后需要重新生成HTML文件; 强大的模板后台,可灵活自由的生成模板标签、让您的站点版式自由改变。 完善的上传文件清理功能,为您清除垃圾文件; 需要注意的是: 为了新手易于使用,程序本身已经内置提供了对应网站的采集规则。 附加说明: (1)本程序修改自网上的大色女库系统,对其发现的BUG进行修正,并增加了快车网图片频道的所有分类采集规则,由于快车网图片没有添加水印,我们推荐用户采集快车网库,上千万的图片数据。如果站长空间允许,建议您将采集的图片保存在您的本地,不影响您的访问速度,有需要可以对您所采集的图片进行批量增加水印,起到宣 传您站点的作用。最主要的是稳定。 (2) 首页部分文字和广告位需要手动修改跟目录下的index.asp文件,由于系统问题,建议用户不要修改静态文件的保存目录和文件名前缀,涉及修改的文件较多,我已经给大家 设置好了,除了首页不生成静态,其他全部内容页都能生成静态。 (3)快车下载联盟(union.flashget.com) 这点我们考虑到了站长的挣钱途径,现在国内使用迅雷的用户是在减少,而快车用户呈上升势头,所以我们考虑做快车的联盟,快车是全球用户数最多的下载工具,最近发布了迷你版,站长可以去下载安装,特别适合笔记本用。安装量上去了我们的钱挣的也就越多,再加上快车下载联盟稳定,收入 还按时发。没有注册的用户可以到快车下载联盟注册帐号,然后在本系统相关页面修改联盟ID为您的ID就可以了。 需要修改的文件:登录后台后在【常规设置】找【模板管理】,然后点SEX后对文章页面内容模板编辑,将其中的12943替换为你的联盟ID即可,应该有三个位置。快车搜索也能挣钱。 (4)采集说明:采集的时候,需要手动修改您要采集的页数,采集过的数据是不能再次采集的,在远程列表起始页选项修改。 有问题请及时关注我们的网站,我们不断对版本进行更新和完善,最后首席男生感谢您的使用。 (5)顶部广告位和导航修改 在根目录下的 /skin/sex/ top.js up.js 两个文件中修改。 后台登陆地址:/admin/admin_login.asp 默认管理员:admin 密码:123456 程序演示地址:http://www.abumei.com/ (除去生成的静态页面和本地图片源码只有11M) 您使用的时候把静态目录disp目录下的所有文件删除,然后后台文章管理把所有已经入库的文章删除,设定好您的网站信息后重新采集到你站点即可。 -------本程序由【刚果工作室】修改
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值