例题详解Java dfs与记忆化搜索和分治递归算法的使用

目录
  • 一、dfs(深度优先搜索)
    • 1.图的dfs
    • 2.树的dfs
  • 二、记忆化搜索
    • 1.普通递归:O(2^n)
    • 2.记忆化搜索: O(n)
  • 三、分治
  • 四、算法题
    • 1.dia和威严
      • 示例
    • 2.小红点点点
      • 示例1
    • 3.kotori和素因子
      • 示例1
      • 示例2
    • 4.kotori和糖果
      • 示例

一、dfs(深度优先搜索)

1.图的dfs

/**
 * 深度优先搜索
 *
 * @param node
 * @param set
 */
public void DFS(Node node, Set<Node> set) {
    if (node == null) {
        //当没有节点时,退出此次方法
        return;
    }
    if (!set.contains(node)) {
        //没有搜到过就保存,并且继续向下推进
        set.add(node);
        System.out.print(node.value + " ");
        for (Node node1 : node.nexts) {
            DFS(node1, set);
        }
    }
}

2.树的dfs

树(边数=顶点数-1的图)的dfs

public static void dfs(Node treeNode) {
        if (treeNode == null) {
            return;
        }
        System.out.print(treeNode.value + " ");
        // 遍历左节点
        dfs(treeNode.left);
        // 遍历右节点
        dfs(treeNode.right);
    }

使用dfs代替状压枚举:  dfs 优于 状压枚举

二、记忆化搜索

记忆化搜索,指通过记录已经搜索到的值来降低重复操作次数,从而加快运行速度。

斐波那契数列问题: 1,1,2,3,5,8,... 求斐波那契数列第n项

1.普通递归:O(2^n)

public static int f(int n){
    if(n<=1)return 1;
    return f(n-1)+f(n-2);
}

2.记忆化搜索: O(n)

    static int[] dp = new int[1000] ;
    public static int f1(int n){
        if(n<=1) return dp[n] = 1;
        if(dp[n]!=0)return dp[n];
        return dp[n]=f1(n-1)+f1(n-2);
    }

三、分治

将问题拆分成子问题进行求解。

例如归并排序: 1,4,7,2,5,8,3,6,9

第一步 拆分: 左边:1,4,7,2 右边:5,8,3,6,9

第二步 递归排序 左边:1,2,4,7 右边:3,5,6,8,9

第三步 合并两个有序数组 左边:1,2,4,7 右边:3,5,6,8,9

四、算法题

1.dia和威严

难度

知识点:dfs

从根开始dfs即可,dfs的过程中就能找到每个点需要的时间,维护一下最大值。

题目描述:

dia是学生会长。她经常有信息要传达给别人。

学生会的阶层等级森严。每个阶级传达信息只能传达到自己的下属。

一个人可以有多个下属,但一个人最多只能有一个上级。(树)

dia作为学生会长,很明显是没有上级的(即全学生会权利最大者)。

已知每个人传达到下属所消耗的时间。(传达给不同的下属,时间不一定相同) 现在dia有一个信息要传达给一个指定者。只有信息接收的指定者才需要理解信息(花费ai的时间)。她不想让效率太慢,于是她想找出最大时间消耗的那个路线(包括信息接收指定者所需要理解信息的时间),这样就能方便整改。

输入描述:

第一行一个正整数n,代表学生会的人数(dia是1号,其他人标记为2号到n号)。 (1≤n≤200000)第二行有n-1个正整数ai,代表从2号到n号,每个人需要理解信息的时间。 (1≤a1≤100)接下来的n-1行,每行有3个正整数x,y,k,代表x是y的上级,x向y传播信息需要消耗k的时间。(1≤x,y≤n,1≤k≤100)

输出描述:

一个正整数,代表dia选定某人作为信息接收指定者后,花费总时间的最大值。

示例

输入

3

3 4

1 2 4

1 3 2

输出

7

说明

若dia指定3号为信息接受者,消耗时间为2+4=6。

若dia指定2号为信息接受者,消耗为4+3=7。

故最大值为7。

备注:

注:只有指定者需要理解信息,传达者不需要花费时间理解信息。

import java.util.*;
import java.io.*;
public class Main{
    static class Edge{
        int to;
        int weight;
        //保存子节点,与线权(传播时间)。
        Edge(int to,int weight){
            this.to = to;
            this.weight = weight;
        }
    }
    static ArrayList<Edge>[] g;
    static int maxtime;
    static int weight[];
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine()); //n为总人数
        weight = new int[n+1];
        g = new ArrayList[n+1];
        // 每个桶中保存一个ArrayList(可达)
        //这里的i代表了第几个数(从1开始)
        for(int i = 1;i<=n;i++){
            g[i] = new ArrayList();
        }
        //保存所有点权(理解时间)纪录了从2到n的点权,与数字的位子相对应。
        String[] s = br.readLine().split(" ");
        for(int i = 0;i<n-1;i++){
            weight[i+2] = Integer.parseInt(s[i]);
        }
        for(int i = 0;i<n-1;i++){
            String[] s1 = br.readLine().split(" ");
            int x = Integer.parseInt(s1[0]);
            int y = Integer.parseInt(s1[1]);
            int z = Integer.parseInt(s1[2]);
            g[x].add(new Edge(y,z));
        }
        dfs(1,0);
        System.out.println(maxtime);
    }
    static void dfs(int i ,int time){
        if(maxtime<time+weight[i])maxtime = time+weight[i];
        for(int k = 0;k<g[i].size();k++){
            dfs(g[i].get(k).to,time+g[i].get(k).weight);
        }
    }
}

非常典型的dfs模板题目:dfs一个树,寻找根到某点的线权总合+某点的点权

思考:???

思考1:假如变成根到某点的线权总合+根到某点的点权总合,程序应该怎么改?

解:

dfs(g[i].get(k).to,time+g[i].get(k).weight+weight[i]);//那就加个点权

思考2:假如变成根到叶子点的线权总合+叶子点的点权,程序应该怎么改?

(1)首先思考一个问题:这个递归没有任何判断结束的条件。他是怎么结束的?

因为我们将他递归的过程安排的明明白白,他只是在执行有序有限的递归操作。所以没有给出判断条件。

(2)那么我们应该怎么判断他到叶子节点了呢?

我们单单在递归的代码上修改是不能判断出是否到达了叶子节点,所以还需要想清楚结构体是咋样的。

解:

if(maxtime<time+weight[i]&&g[i].isEmpty())maxtime = time+weight[i];

思考3:假如变成随意定点到某点的线权总合+某点的点权,程序应该怎么改?

解:

dfs(点的编号,0); 0代表时间从0开始递归

2.小红点点点

难度

知识点:dfs

开一个visited数组用来标记是否访问。对于每个没被访问过的红色节点,开始dfs并标记其相邻的红色节点。只要标号是从小到大开始遍历,最终形成的方案就一定是字典序最小的

题目描述:

小红拿到了一个图,其由 n个顶点、m 条边组成。

这个图上面的一些点被染成了红色。

小红有一个能力:她每次可以点击一个红色的顶点,并将和这个顶点相邻的红色连通块的所有红色顶点全部标记。

当两个红色顶点有一条边相连时,这两个红色顶点被称为连通的。另外,若a和b连通且b和c连通,那么a和c也是连通的。

小红想知道自己至少要点击多少次,可以把所有红色的顶点全部标记。

你能告诉她点击的次数吗?并请你输出一个点击的方案,如果有多个方案合法,请你输出一个字典序最小的方案。

注:字典序的定义:两个不同方案的字典序比较:对于从左到右数第一个不同的数,哪个方案最小,那么它的字典序最小。

例如:方案[1,4,5]和方案[1,3,6]相比,后者更小。因为第一个出现的不同的数是第二个数,4>3。

输入描述:

第一行输出两个正整数 n 和 m ,用空格隔开。分别代表顶点数和边数。

第二行输入一个长度为 n 的字符串,代表每个顶点的染色情况。第 i 个字符为'R'代表第 i 个点被染成红色,为'W'代表未被染色。

接下来的 m 行,每行两个正整数 x 和 y ,代表 x 和 y 有一条无向边相连。

不保证图是整体连通的。不保证没有重边和自环。

数据范围:

输出描述:

第一行输出一个正整数 cnt ,代表小红的点击次数。

第二行输出 cnt 个正整数,用空格隔开,代表小红点击的顺序。

示例1

输入

5 5

RRRWR

3 4

3 1

2 5

5 5

4 5

输出

2

1 2

说明

可以发现,共有2个红色连通块:{1,3}和{2,5}。只需要点击2次即可,字典序最小的方案是[1,2]

import java.util.*;
import java.io.*;

public class Main {
    static ArrayList<Integer>[] g;
    static String[] strings;
    static int[] visited;

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] firstLine = br.readLine().split(" ");
        int n = Integer.parseInt(firstLine[0]);
        int m = Integer.parseInt(firstLine[1]);
        g = new ArrayList[n+1];
        visited = new int[n+1];
        for (int i=1;i<n+1;i++) {
            g[i] = new ArrayList<Integer>();
        }
        //一个字符一个字符的读取
        strings = br.readLine().split("");
        for (int i=0;i<m;i++) {
            //描绘双向图
            String[] temp = br.readLine().split(" ");
            int x = Integer.parseInt(temp[0]);
            int y = Integer.parseInt(temp[1]);
            g[x].add(y);
            g[y].add(x);
        }
        int cnt = 0;
        StringBuilder sb = new StringBuilder();
        for (int i=1;i<n+1;i++) {
            if (visited[i] ==0 && strings[i-1].equals("R")) {
                cnt++;
                sb.append(i + " ");
                //从糖葫芦小的开始纪录,然后延深。
                dfs(i);
            }
        }
        System.out.println(cnt);
        System.out.println(sb);
    }

    static void dfs(int x) {
        if (visited[x] ==0 && strings[x-1].equals("R")) {
            //点亮它
            visited[x] = 1;
            for (int i=0;i<g[x].size();i++) {
                dfs(g[x].get(i));
            }
        }
    }
}

3.kotori和素因子

难度

知识点:dfs(回溯法)

按照题意进行dfs即可。注意素因子的判重,可以使用set或者visited数组。

题目描述:

kotori拿到了一些正整数。她决定从每个正整数取出一个素因子。但是,kotori有强迫症,她不允许两个不同的正整数取出相同的素因子。

她想知道,最终所有取出的数的和的最小值是多少?

注:若a%k==0,则称k是a的因子。若一个数有且仅有两个因子,则称其是素数。显然1只有一个因子,不是素数。

输入描述:

第一行一个正整数n,代表kotori拿到正整数的个数。

第二行共有n个数ai,表示每个正整数的值。

保证不存在两个相等的正整数。

1<=n<=10

2<=ai<=1000

输出描述:

一个正整数,代表取出的素因子之和的最小值。若不存在合法的取法,则输出-1。

示例1

输入

4

12 15 28 22

输出

17

说明

分别取3,5,7,2,可保证取出的数之和最小

示例2

输入

5

4 5 6 7 8

输出

-1

备注:        1<=n<=10        2<=ai<=1000

import java.util.*;
import java.io.*;

public class Main{
    static boolean[] check = new boolean[2000];
    static int[] ai;
    static int n;
    static int min = Integer.MAX_VALUE;
    //判断是否为素数
    static boolean isPrime(int x){
        if(x<2) return false;
        //这是根据开根号的演化版本,提高了效率。
        for(int i=2;i*i<=x;i++){
            if(x%i==0)return false;
        }
        return true;
    }
    static void dfs(int index,int sum){
        if(index==n){
            min=Math.min(min,sum);
            return;
        }
        //查找这个数没有被占用的素因子。
        for(int i=2;i<=ai[index];i++){
            if(ai[index]%i==0&&check[i]==false&&isPrime(i)){
                check[i]=true;
                dfs(index+1,sum+i);
                //回溯的方法。
                check[i]=false;
            }
        }
    }
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        String[] a = br.readLine().split(" ");
        //负责保存所有输入的数
        ai = new int[n];
        for(int i=0;i<n;i++){
            ai[i]=Integer.parseInt(a[i]);
        }
        dfs(0,0);
        System.out.println(min==Integer.MAX_VALUE ? -1:min);
        br.close();
    }
}

4.kotori和糖果

难度

知识点:记忆化搜索

正难则反,我们反向推理。

如果共有n个糖果:

  • 若n为偶数,最后一次合并一定是两堆n/2合并最优。
  • 若n为奇数,最后一次合并一定是一堆n/2和一堆n/2+1合并最优(合并的价值为1)。

所以得到递推式:f(n)=f(n/2)+f(n-n/2)+n%2

题目描述:

kotori共有n块糖果,每块糖果的初始状态是分散的,她想把这些糖果聚在一堆。但她每次只能把两堆糖果合并成一堆。

已知把两堆数量为a和b的糖果聚在一堆的代价是|a-b|。

kotori想知道,她把这n块糖果聚在一堆的最小代价是多少?

输入描述:

第一行是一个正整数T,代表数据组数。

第二行到第T+1行,每行一个非负整数n,代表kotori的糖果总数量。

输出描述:

每行一个整数,代表kotori需要花费的最小代价。

示例

输入

2

5

6

输出

2

2

说明

n=5时,kotori可以这样聚集糖果:

1 1 1 1 1

2 1 1 1

2 2 1

2 3

5

每一步的代价分别是0,0,1,1,总代价为2。

备注:

对于50%的数据,0<T≤100000, 0≤n≤100000

对于另外50%的数据,T=1 , 0≤n≤1e18(这个数据范围是为了为了让你动态规划做不了,上面的范围可以做)

import java.util.*;
import java.io.*;
public class Main{
    static HashMap<Long,Long> map = new HashMap<>();
    public static void main (String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int num = Integer.parseInt(br.readLine());
        for(int i = 0; i<num; i++){
            System.out.println(recurse(Long.parseLong(br.readLine())));
        }
    }
    static long recurse(long num){
        if(num <= 1) return 0;
        //用于保存
        if(map.containsKey(num)) return map.get(num);
        long pay = recurse(num/2) + recurse(num-num/2) +num%2;
        map.put(num,pay);
        return pay;
    }
}

num%2的作用就是为了计数,且保存。

到此这篇关于例题详解Java dfs与记忆化搜索和分治递归算法的使用的文章就介绍到这了,更多相关Java 递归算法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java编程之递归算法总结

    1.何为递归 个人理解就是自己调用自己,直到满足一个条件结束自己调用自己的过程,这个就是递归.举一个通俗的点的例子: 假设你在一个电影院,你想知道自己坐在哪一排,但是前面人很多,你懒得去数了,于是你问前一排的人「你坐在哪一排?」,这样前面的人 (代号 A) 回答你以后,你就知道自己在哪一排了--只要把 A 的答案加一,就是自己所在的排了,不料 A 比你还懒,他也不想数,于是他也问他前面的人 B「你坐在哪一排?」,这样 A 可以用和你一模一样的步骤知道自己所在的排.然后 B 也如法炮制,直到他们这

  • java利用递归算法实现对文件夹的删除功能

    前提: 集成开发环境(IDE):eclipse jdk版本:8.0 File类的几个方法: 1)isFile() 测试此抽象路径名表示的文件是否为普通文件. 2)list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录. 3)delete() 删除由此抽象路径名表示的文件或目录. 4)listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件. File类的一个属性: separator 与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串

  • Java利用递归算法实现查询斐波那契数

    package 斐波那契数; import java.util.Scanner; class 斐波那契数 { public static void main(String[] args) { System.out.println("请输入想查询的第几个斐波拉楔数"); long n = new Scanner(System.in).nextLong(); System.out.println(f(n)); } private static int f(long n) { if(n==1

  • java递归算法的实例详解

    递归三要素: 1.明确递归终止条件: 2.给出递归终止时的处理办法: 3.提取重复的逻辑,缩小问题规模. 1.1+2+3+-+n import java.util.Scanner; public class Recursion { public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); System.out.println(sum(n)); } publ

  • Java的递归算法详解

    目录 一.介绍 1.介绍 2.案例 二.迷宫问题 三.八皇后问题 四.汉诺塔问题 1.问题 2.思想 3.代码 总结 一.介绍 1.介绍 递归:递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂的问题,同时可以让代码变得简洁. 迭代和递归区别:迭代使用的是循环结构,递归使用的选择结构.使用递归能使程序的结构更清晰.更简洁.更容易让人理解,从而减少读懂代码的时间.其时间复杂度就是递归的次数. 但大量的递归调用会建立函数的副本,会消耗大量的时间和内存,而迭代则不需要此种付出

  • java递归算法实例分析

    递归算法设计的基本思想是: 对于一个复杂的问题,把原问题分解为若干个相对简单类同的子问题,继续下去直到子问题简单到能够直接求解,也就是说到了递推的出口,这样原问题就有递推得解. 在做递归算法的时候,一定要把握住出口,也就是做递归算法必须要有一个明确的递归结束条件.这一点是非常重要的.其实这个出口是非常好理解的,就是一个条件,当满足了这个条件的时候我们就不再递归了. 关键要抓住的是: (1)递归出口 (2)地推逐步向出口逼近 递归就是方法自身调用自身的行为,注意要写好递归头,也就是什么时候退出递归

  • Java递归算法简单示例两则

    本文实例讲述了Java递归算法.分享给大家供大家参考,具体如下: 1.实现1到100的和,用递归实现 public class RecursionTest { public static void main(String[] args) { System.out.println(diGui(100));// 5050 } public static int diGui(int n) { int sum; if (n == 1) return 1; else { sum = n + diGui(n

  • 例题详解Java dfs与记忆化搜索和分治递归算法的使用

    目录 一.dfs(深度优先搜索) 1.图的dfs 2.树的dfs 二.记忆化搜索 1.普通递归:O(2^n) 2.记忆化搜索: O(n) 三.分治 四.算法题 1.dia和威严 示例 2.小红点点点 示例1 3.kotori和素因子 示例1 示例2 4.kotori和糖果 示例 一.dfs(深度优先搜索) 1.图的dfs /** * 深度优先搜索 * * @param node * @param set */ public void DFS(Node node, Set<Node> set)

  • 基于Java语言的递归运算例题详解

    目录 一.实例演示:递归求N的阶乘 二. 递归调用练习 递归求1+2+3+……10的和 顺序打印一个数字的每一位 返回一个数组成本身的数字之和 求解汉诺塔问题 求斐波那契数列第N项 递归定义:一个方法在执行过程中调用自身, 就称为 "递归". 递归的必要条件: 1. 将原问题划分成其子问题,注意:子问题必须要与原问题的解法相同. 2. 递归出口. 一.实例演示:递归求N的阶乘 public class fac { public static int factorial(int x){

  • 详解Java 虚拟机垃圾收集机制

    1 垃圾收集发生的区域 之前我们介绍过 Java 内存运行时区域的各个部分,其中程序计数器.虚拟机栈.本地方法栈三个区域随线程共存亡.栈中的每一个栈帧分配多少内存基本上在类结构确定下来时就已知,因此这几个区域的内存分配和回收都具有确定性,不需要考虑如何回收的问题,当方法结束或线程结束,内存自然也跟着回收了 而 Java 堆和方法区这两个区域则有显著的不确定性,只有在程序运行时我们才能知道程序究竟创建了哪些对象,创建了多少对象,所以这部分内存的分配和回收是动态的,垃圾收集器所关注的正是这部分内存该

  • 详解Java网络编程

    一.网络编程 1.1.概述 1.计算机网络是通过传输介质.通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来,实现资源共享和数据传输的系统.网络编程就就是编写程序使联网的两个(或多个)设备(例如计算机)之间进行数据传输.Java语言对网络编程提供了良好的支持,通过其提供的接口我们可以很方便地进行网络编程. 2.Java是 Internet 上的语言,它从语言级上提供了对网络应用程 序的支持,程序员能够很容易开发常见的网络应用程序. 3.Java提供的网络类库,可以实现无痛的网络连接,联

  • 详解Java中的 枚举与泛型

    详解Java中的 枚举与泛型 一:首先从枚举开始说起 枚举类型是JDK5.0的新特征.Sun引进了一个全新的关键字enum来定义一个枚举类.下面就是一个典型枚举类型的定义: public enum Color{ RED,BLUE,BLACK,YELLOW,GREEN } 显然,enum很像特殊的class,实际上enum声明定义的类型就是一个类. 而这些类都是类库中Enum类的子类(Java.lang.Enum).它们继承了这个Enum中的许多有用的方法.我们对代码编译之后发现,编译器将 enu

  • 详解Java面试官最爱问的volatile关键字

    本文向大家分享的主要内容是Java面试中一个常见的知识点:volatile关键字.本文详细介绍了volatile关键字的方方面面,希望大家在阅读过本文之后,能完美解决volatile关键字的相关问题.  在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度,而以volatile关键字作为一个小的切入点,往往可以一问到底,把Java内存模型(JMM),Java并发编程的一些特性都牵扯出来,深入地话还可以考察JVM底层实现以及操作系统的相关知识. 下面我们以一次假想的面试过

  • 详解JAVA类加载机制

    1.一段简单的代码 首先来一段代码,这个是单例模式,可能有的人不知道什么是单例模式,我就简单说一下 单例模式是指一个类有且只有一种对象实例.这里用的是饿汉式,还有懒汉式,双检锁等等.... 写这个是为了给大家看一个现象 class SingleTon{ public static int count1; public static int count2=0; private static SingleTon instance=new SingleTon(); public SingleTon()

  • 详解关于SpringBoot的外部化配置使用记录

    更新: 工作中突然想起来,关于Yaml的使用,并不属于Spring的范畴,是org.yaml.snakeyaml处理的.所以yaml的使用应该参考官方,不过貌似打不开... Spring利用snakeyaml将配置解析成PropertySource,然后写入到Environment,就能使用了 记录下使用SpringBoot配置时遇到的一些麻烦,虽然这种麻烦是因为知识匮乏导致的. 记录下避免一段时间后自己又给忘记了,以防万一. 如果放到博客里能帮助到遇到同样问题的同志,自是极好! SpringB

  • 详解JAVA 字节流和字符流

    1.InputStream 和 Reader InputStream 和 Reader 是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以它们的方法是所有输入流都可使用的方法. 在 InputStream 里包含如下三个方法. int read():从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转换为int类型). int read(byte[] b):从输入流中最多读取 b.length 个字节的数据,并将其存储在字节数组 b 中,返回实际读

  • 详解JAVA 原型模式

    原型模式 原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆.当直接创建对象的代价比较大时,则采用这种模式.例如,一个对象需要在一个高代价的数据库操作之后被创建.我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用. 介绍 意图: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.

随机推荐