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);
}
}
}