Java实现:无向图、深度优先搜索、广度优先搜索、路径查找

1 无向图介绍

1.1 特征

无向图的边仅仅连接两个顶点,没有其他含义,区别于有向图(边不仅连接两个顶点,且具有方向)。

1.2 相关术语

  1. 相邻顶点:通过一条边相连的两个顶点,并且称这条边依附于这两个顶点;
  2. :某顶点的度指依附于该顶点的边的个数
  3. 子图:一幅图的所有边的子集(包含这些边依附的顶点)组成的图;
  4. 路径:由边顺序连接的一系列的顶点组成;
  5. :含有至少一条边且终点和起点相同的路径。
    在这里插入图片描述
  6. 连通图:如果图中任意一个顶点都存在一条路径到达另外一个顶点,那么这幅图就称为连通图;
    在这里插入图片描述
  7. 连通子图:一个非连通图由若干连通的部分组成,每一个连通的部分称为该图的连通子图。

1.3 图的存储结构

要表示图,只需表示清楚以下两部分内容:

  1. 图中所有的顶点;
  2. 所有连接顶点的边;

常见图的存储结构有两种:邻接矩阵和邻接表

1.3.1 邻接矩阵

  1. 使用一个V*V的二维数组int[V][V] adj,把索引看作顶点;
  2. 如果顶点v和顶点w相连,将adj[V][W]和adj[W][V]的值设置为1,否则设置为0;

在这里插入图片描述

  1. 显然,邻接矩阵的空间复杂度为O(N^2),不适合处理规模较大的问题;

1.3.2 邻接表(使用)

  1. 使用一个大小为V的数组Deque[V] adj,索引即顶点;
  2. 每个索引的adj[V]是队列,队列中存储的是所有与该顶点相邻的其他顶点;

在这里插入图片描述

2 无向图实现

public class Graph {
    //顶点数目
    private final int V;
    //边的数目
    private int E;
    //邻接表
    private Deque<Integer>[] adj;

    public Graph(int V){
        //初始化顶点数量
        this.V = V;
        //初始化边的数量
        this.E = 0;
        //初始化邻接表
        this.adj = new Deque[V];
        
        for (int i = 0; i < adj.length; i++) {
            adj[i] = new LinkedList<>();
        }
    }

    //获取顶点数目
    public int V(){
        return V;
    }

    //获取边的数目
    public int E(){
        return E;
    }

    //向图中添加一条边 v-w
    public void addEdge(int v, int w) {
        //在无向图中,边是没有方向的,所以该边既可以说是从v到w的边,又可以说是从w到v的边,因此,需要让w出现在v的邻接表中,并且还要让v出现在w的邻接表中
        adj[v].offer(w);
        adj[w].offer(v);
        //边的数量+1
        E++;
    }

    //获取和顶点v相邻的所有顶点
    public Deque<Integer> adj(int v){
        return adj[v];
    }
}

3 深度优先搜索

  1. 深度优先搜索,是指在搜索时,如果遇到一个结点既有子结点,又有兄弟结点,那么先找子结点,再找兄弟结点
  2. 由于无向图的边是没有方向的,如果4和5顶点相连,4会出现在5的邻接表中,5也会出现在4的邻接表中,为了不对顶点重复搜索,用marked来标记当前顶点有没有被搜索过;
  3. 定义一个数组boolean[V] marked,索引代表顶点,值为true表示已经被搜索过,值为false表示还没有被搜索;

在这里插入图片描述

public class DepthFirstSearch {
    //索引代表顶点,值表示当前顶点是否已经被搜索
    private boolean[] marked;
    //记录有多少个顶点与s顶点相通
    private int count;

    //构造深度优先搜索对象,使用深度优先搜索找出g图中s顶点的所有相通顶点
    public DepthFirstSearch(Graph g,int s){
        //初始化marked数组
        this.marked = new boolean[g.V()];
        //初始化跟顶点s相通的顶点数量
        this.count=0;

        dfs(g,s);
    }

    //使用深度优先搜索找出g图中v顶点的所有相通顶点
    private void dfs(Graph g, int v){
        //把v顶点标识为已搜索
        marked[v] = true;

        for (Integer w : g.adj(v)) {
            //判断当前w顶点有没有被搜索过,如果没有被搜索过,则递归调用dfs方法进行深度搜索
            if (!marked[w]){
                //相通顶点数量+1
                count++;
                dfs(g,w);
            }
        }
    }

    //判断w顶点是否被搜索过
    public boolean marked(int w){
       return marked[w];
    }

    //获取与顶点s相通的所有顶点的总数
    public int count(){
        return count;
    }
}
public class Test {
    public static void main(String[] args) {
        Graph g = new Graph(7);
        g.addEdge(0,1);
        g.addEdge(0,2);
        g.addEdge(0,6);
        g.addEdge(4,6);
        g.addEdge(4,3);
        g.addEdge(3,5);
        DepthFirstSearch ds = new DepthFirstSearch(g,0);
        System.out.println(ds.count());
        System.out.println(g.adj(0));
    }
}
6
[1, 2, 6]

4 广度优先搜索

广度优先搜索,是指在搜索时,如果遇到一个结点既有子结点又有兄弟结点,那么先找兄弟结点,再找子结点

在这里插入图片描述

public class BreadthFirstSearch {
    //索引代表顶点,值表示当前顶点是否已经被搜索
    private boolean[] marked;
    //记录有多少个顶点与s顶点相通
    private int count;
    //用来存储待搜索邻接表的点
    private Deque<Integer> waitSearch;

    //构造广度优先搜索对象,使用广度优先搜索找出g图中s顶点的所有相邻顶点
    public BreadthFirstSearch(Graph g, int s) {
        this.marked = new boolean[g.V()];
        this.count=0;
        this.waitSearch = new LinkedList<>();
        bfs(g,s);
    }

    //使用广度优先搜索找出g图中v顶点的所有相通顶点
    private void bfs(Graph g, int v) {
    	//把当前顶点v标识为已搜索
        marked[v] = true;
        //让顶点v进入队列,待搜索
        waitSearch.offer(v);
        //通过循环,如果队列不为空,则从队列中弹出一个待搜索的顶点进行搜索
        while(!waitSearch.isEmpty()){
            //弹出一个待搜索的顶点
            Integer wait = waitSearch.poll();
            //遍历wait顶点的邻接表
            for (Integer w : g.adj(wait)) {
                if (!marked[w]){
                	//把当前顶点w标识为已搜索
                	marked[w] = true;
                    //让相通的顶点+1
                    count++;
                    //把当前顶点w加入等待队列
                    waitSearch.offer(w);
                }
            }
        }
    }

    //判断w顶点是否已经被搜索
    public boolean marked(int w) {
        return marked[w];
    }
    
    //获取与顶点s相通的所有顶点的总数
    public int count() {
        return count;
    }
}
public class Test {
    public static void main(String[] args) {
        Graph g = new Graph(7);
        g.addEdge(0,1);
        g.addEdge(0,2);
        g.addEdge(0,6);
        g.addEdge(4,6);
        g.addEdge(4,3);
        g.addEdge(3,5);
        BreadthFirstSearch ds = new BreadthFirstSearch(g,0);
        System.out.println(ds.count());
        System.out.println(g.adj(0));
    }
}
6
[1, 2, 6]

5 案例:畅通工程

5.1 题目要求

  1. 某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接相连的城镇;

  2. 畅通工程的目标是使全省任何两个城镇间都可以实现交通(不一定直接相连,间接相连即可);

  3. 有文件traffic_project.txt,下面是文件内容及数据解释:

在这里插入图片描述

  1. 总共有20个城市,目前已经修改好了7条道路,问9号城市和10号城市是否相通?9号城市和8号城市是否相通?

5.2 解题思路(基于深度/广度优先搜索)

  1. 创建一个Graph对象,表示城市;
  2. 分别调用addEdge(0,1),addEdge(6,9),addEdge(3,8),addEdge(5,11),addEdge(2,12),addEdge(6,10),addEdge(4,8),表示修建道路将对应的城市连起来;
  3. 通过Graph对象和顶点9,构建DepthFirstSearch对象或BreadthFirstSearch对象;
  4. 调用搜索对象的marked(10)方法和marked(8)方法,即可得知9号城市和10号城市是否相通?9号城市和8号城市是否相通?
public class Traffic_Project {
    public static void main(String[] args) throws IOException {
        // 创建输入流
        BufferedReader reader = new BufferedReader(new FileReader("src/traffic_project.txt"));
        // 读取城市数目,初始化并查集
        int number = Integer.parseInt(reader.readLine());
        Graph g = new Graph(number);
        // 读取已经修建好的道路数目
        int roadNumber = Integer.parseInt(reader.readLine());
        // 循环读取已经修建好的道路,并调用addEdge方法
        for (int i=0; i<roadNumber; i++) {
            String line = reader.readLine();
            int p = Integer.parseInt(line.split(" ")[0]);
            int q = Integer.parseInt(line.split(" ")[1]);
            g.addEdge(p, q);
        }
        // 根据图g和顶点9构建图的搜索对象
        // BreadthFirstSearch search = new BreadthFirstSearch(g,9);
        DepthFirstSearch search = new DepthFirstSearch(g, 9);
        // 调用搜索对象的marked(10)方法和marked(8)方法
        boolean flag1 = search.marked(10);
        boolean flag2 = search.marked(8);

        System.out.println("9号城市和10号城市是否已相通:" + flag1);
        System.out.println("9号城市和8号城市是否已相通:" + flag2);
    }
}
9号城市和10号城市是否已相通:true
9号城市和8号城市是否已相通:false

6 路径查找

6.1 问题分析

  1. 要实现路径查找,首先要遍历并搜索图,这里我们基于深度优先搜索来完成;
  2. 添加edgeTo[]数组,记录从s到每个相通顶点的路径;

设s为0,其搜索如图所示:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
根据最终edgeTo的结果,我们很容易能够找到从起点0到任意顶点的路径。

6.2 代码实现

public class DepthFirstPaths {
    //索引代表顶点,值表示当前顶点是否已经被搜索
    private boolean[] marked;
    //起点
    private int s;
    //索引代表顶点,值代表从起点s到当前顶点路径上的最后一个顶点
    private int[] edgeTo;

    //构造深度优先搜索对象,使用深度优先搜索找出g图中起点为s的所有路径
    public DepthFirstPaths(Graph g, int s){
        //初始化marked数组
        this.marked = new boolean[g.V()];
        //初始化起点
        this.s = s;
        //初始化edgeTo数组
        this.edgeTo = new int[g.V()];

        dfs(g,s);
    }

    //使用深度优先搜索找出g图中s顶点的所有相通顶点
    private void dfs(Graph g, int s){
        //把s表示为已搜索
        marked[s] = true;

        //遍历顶点s的邻接表,拿到每一个相邻的顶点
        for (Integer w : G.adj(s)) {
            //如果顶点w没有被搜索,则继续递归搜索
            if (!marked[w]){
                edgeTo[w] = s;//到达顶点w的路径上的最后一个顶点是s
                dfs(G,w);
            }
        }
    }

    //判断v顶点与s顶点是否相通
    public boolean hasPathTo(int v){
        return marked[v];
    }

    //找出从起点s到顶点v的路径
    public Deque<Integer> pathTo(int v){
        if (!hasPathTo(v)){
            return null;
        }
        //创建栈对象,保存路径中的所有顶点
        Deque<Integer> path = new LinkedList<>();

        //通过循环,从顶点v开始,一直往前找,到找到起点为止
        for (int x = v; x!=s;x = edgeTo[x]){
            path.push(x);
        }

        //把起点s放到栈中
        path.push(s);
        return path;
    }
}
public class Test {
    public static void main(String[] args) {
        Graph g = new Graph(6);
        g.addEdge(0,2);
        g.addEdge(0,1);
        g.addEdge(0,5);
        g.addEdge(1,2);
        g.addEdge(2,3);
        g.addEdge(3,4);

        DepthFirstPaths paths = new DepthFirstPaths(g,0);
        Deque<Integer> path = paths.pathTo(4);

        StringBuilder builder = new StringBuilder();
        while(!path.isEmpty()) {
            builder.append(path.pop() + "-");
        }

        builder.deleteCharAt(builder.length()-1);
        System.out.println(builder);
    }
}
0-2-3-4
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hellosc01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值