java算法day24

java算法day24

  • 图的存储
  • 做题知识补充
  • 所有可达路径
  • 797 所有可能的路径

现在开始图论的学习。首先要学会图怎么存。

图的存储

邻接矩阵法

就是用一个二维数组来存,int[][] graph
举个例子快速理解:
[][]里面的下标都代表点。比如graph[1][2],里面的下标代表点1,点2。
graph[][]的值代表边,这个边在题目中有不同的含义:
比如:
情况1:graph[1][2] = 1也就是点1到点2之间有边。如果等于0就是点1到点2之间没边。
情况2:边代表权值,graph[1][2] = 3,点1到点2之间有边,而且边的长度为3。

所以说用二维数组就可以记录一个图。用这样的方式,可以全部记录下,图中的所有点和边的关系。这是图的第一种存法。

该方法构建图的过程就是,点直接通过下标建立好了,接下来要做的就是把边设置好。


邻接表法

直接上图。
在这里插入图片描述
从这个图应该也看出来了,实现就是数组+链表。
int[] nums 下标就代表点,然后nums[1]对应的值就是他存的链表。用上面1举例。点1中存的链表3->5。这代表点1可以到达点3,还可以到达点5。

所以基于邻接表法构建图的方式有了。一维数组下标就代表着点。数组中存的链表就代表该点能连接到的边,所以构建的时候,该点能连接到的点,就挂到数组中该点的链表中。

简单总结:把每个点能连接到的点,挂到他对应的链表中去。全挂好了图就构建好了。


通过上面的例子可以看出,dfs的过程,每个点该如何扩展了。
对于邻接矩阵
遍历graph[当前的点][i] 就是在看这个点能连到哪个点
对于邻接表
基于当前点,那么你就要获得当前点的链表,才能得到他能连接到的所有点的关系。拿到链表了就遍历链表,就能够看到当前点所能到达的所有点。


图论的做题模板:
说白了还是搜索。这里先看dfs的。

void dfs(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本节点所连接的其他节点) {
        处理节点;
        dfs(图,选择的节点); // 递归
        回溯,撤销处理结果
    }
}

感觉就是回溯的模板,一模一样。因为图的搜索就是这样。


图做题补充

这里将做一些输入的解释。
有的时候题目的输入并不像我们想的,按这两种结构传给我们。有时候会按这种形式。
比如题目给的函数前面为这样:
就是给了一个二维数组

public List<List<Integer>> allPathsSourceTarget(int[][] graph) {

    }

然后有的同学就会误以为,他给了你一个邻接矩阵,然后以为这个题要用邻接矩阵来写。
然后现在看他的输入样例。
在这里插入图片描述
是不是和想的不太一样,而且从这个输入来进行分析,这貌似很像邻接表法。但是和我们的数组+链表的结构又不太符合。

解答:
这种方式是很特殊的方式,称之为交错数组。这种方式允许你后序为每个子数组分配不同大小的内存。
好处:
(1)这种结构和邻接表的理念一致,保证了邻接表的性质,它直接存储每个节点的邻接节点列表
(2)空间效率很高,只存储实际存在的边。只为实际需要的元素分配
(3)遍历效率很高

与常规二维数组的不同:
常规二维数组,所有子数组长度相同。而交错数组允许每个子数组有不同的长度。

首先介绍创建方式:

int[][] graph = new int[4][];  
//一开始是分配内存,包含四个int[]类型的引用。
//此时4个引用都被初始化为null。
graph[0] = new int[]{1, 2};    // 长度为 2 的数组  
graph[1] = new int[]{3};       // 长度为 1 的数组  
graph[2] = new int[]{3};       // 长度为 1 的数组  
graph[3] = new int[]{};        // 空数组

也就是graph这样声明是合法的。

所有可达路径

ACM模式:做图论的题目时,如果是ACM模式,那么就需要自己来决定用哪种存储方式,然后因为存储方式不一样,构建图的方式也有略微的不一样。然后输出结果要自己print。

邻接矩阵写法:
dfs纯粹是模板

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
 
 
public class Main {
//全局变量,用来存结果
    static List<List<Integer>> result = new ArrayList<>();
    static List<Integer> path = new ArrayList<>();
     
     
     //主函数
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
         
        int[][] graph = new int[n+1][n+1];
         
        for(int i = 0;i<m;i++){
            int s = scanner.nextInt();
            int t = scanner.nextInt();
            graph[s][t] = 1;
        }
         
        path.add(1);
        dfs(graph,1,n);
         
        if(result.isEmpty()){
            System.out.println(-1);
        }
        for(List<Integer> pa : result){
            for(int i = 0;i<pa.size()-1;i++){
                System.out.print(pa.get(i)+" ");
            }
            System.out.println(pa.get(pa.size()-1));
        }
         
         
    }
     
     //深度优先
    public static void dfs(int[][] graph,int x,int n){
    //当前节点为目标节点,将当前路径加入结果集
        if(x==n){
            result.add(new ArrayList<>(path));
            return;
        }
         
        for(int i = 1;i<=n;i++){
        //遍历当前点的所有边,找能走的路
        //能找到路就收集结果,进入下一层。
        //可以看到这里遍历节点的边的方式是邻接矩阵的方式
            if(graph[x][i]==1){
                path.add(i);
                dfs(graph,i,n);
		//这里是回溯了。                path.remove(path.size()-1);
            }
        }
    }
     
     
     
     
     
     
}

邻接表法:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

public class Main{
    
    static List<List<Integer>> result = new ArrayList<>();
    static List<Integer> path = new ArrayList<>();
    
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        
        List<LinkedList<Integer>> graph = new ArrayList<>(n+1);
        //先给数组中每个节点放空链表
        for(int i = 0;i<=n;i++){
            graph.add(new LinkedList<>());
        }
        //开始构建图,s是点,通过点可拿到链表
        //然后把目标点挂到s的链表上去。
        while(m-->0){
            int s = scanner.nextInt();
            int t = scanner.nextInt();
            graph.get(s).add(t);
        }
        
        path.add(1);
        dfs(graph,1,n);
        
        if(result.isEmpty()){
            System.out.println(-1);
        }
        for(List<Integer> pa : result){
            for(int i = 0;i<pa.size()-1;i++){
                System.out.print(pa.get(i)+" ");
            }
            System.out.println(pa.get(pa.size()-1));
        }
        
        
    }
    
    public static void dfs(List<LinkedList<Integer>> graph,int x,int n){
        if(x==n){
            result.add(new ArrayList<>(path));
            return;
        }
        //特点就在这里,获取当前点的链表,然后遍历就是找到了当前这个点能到的所有边。
        for(int i : graph.get(x)){
            path.add(i);
            dfs(graph,i,n);
            path.remove(path.size()-1);
        }
        
    }
    
    
    
    
}

797 所有可能的路径

和前面这个题一样,但是是核心代码模式。
通过输入,可判断是邻接表。

class Solution {

    List<List<Integer>> result = new ArrayList<>();
    List<Integer> path = new ArrayList<>();

    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
    //题目说了起点是0.而且后面处理节点也是从开始找节点0所能走的下一步。
        path.add(0);
        dfs(graph,0,graph.length-1);
        return result;
    }

    void dfs(int[][] graph,int x,int n){
        if(x==n){
            result.add(new ArrayList<>(path));
            return;
        }

        //处理当前节点
        for(int temp : graph[x]){
            path.add(temp);
            dfs(graph,temp,n);
            path.remove(path.size()-1);
        }

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值