算法设计与分析 暴力递归

简介: 算法设计与分析 暴力递归

暴力递归

概述

  1. 暴力递归就是尝试(局部尝试)
  2. 把问题转化为规模缩小了的同类问题的子问题
  3. 明确递归结束的条件(base case)
  4. 得到子问题的结果后有决策过程
  5. 不记录每一个子问题的解,尝试最重要

题目一:汉诺塔问题

image.png

  • 思路:
    (1)将 1 ~ n - 1 层移动到中间
    (2)将 n 层移动到最右边
    (3)将 1 ~ n - 1 层移动到最右边
    public static void hanoi(int n){
        if (n > 0){
            move(n, "left", "mid", "right");
        }
    }
    public static void move(int n, String from, String to, String temp){
        //移动from:出发地;to:目的地;temp:辅助位置
        if (n == 1){
            //递归结束条件
            System.out.println("Move 1 form " + from + " to " + to);
        }
        else {
            //问题规模的缩小
            //移动 n - 1 由from到temp
            move(n - 1, from, temp, to);
            //移动 n 由from到to
            System.out.println("Move " + n + " from " + from + " to " + to);
            //移动n - 1 由temp到to
            move(n - 1, temp, to, from);
        }
    }
  • 结果演示:
    当 n = 3image.png

题目二:字符串的全部子序列问题

image.png

  • 思路:
    尝试从0 到 n位置
    (1)将字符串转化为字符数组,每个字符位置分为要和不要
    (2)问题从0 到 n的位置,每次增加一个位置
    (3)当长度等于 n 结束递归
    注意点:一个递归结束后若对字符数组进行了改变,再下一个递归开始前要恢复字符数组
    public static void printAllSubsquence(String str){
        char[] chs = str.toCharArray();
        progress(chs, 0);
    }
    public static void progress(char[] chs, int index){
        //index,在字符数组中目前到达的位置
        if (index == chs.length){
            //走完整个字符数组
            System.out.println(String.valueOf(chs));
            return;
        }
        //直接后移保留index位置,规模的缩小
        progress(chs, index + 1);
        char temp = chs[index];
        //ASCLL中0表示空字符
        chs[index] = 0;
        //将index出使用空字符代替,规模的缩小
        progress(chs, index + 1);
        //恢复index位置
        chs[index] = temp;
    }
  • 结果演示
    str = “ljk”image.png

题目三:字符串的全排列问题(分支限界)

image.png

  • 思路:
    若字符串的长度为n
    尝试从0到n位置
    不考虑重复排列时:使用交换的方法
    (1)第一个位置可以有N个位置与其交换
    (2)第二个位置可以有N - 1个位置与其交换
    ……
    (3)第N - 1个位置有2个位置可以与其交换
    (4)第N个位置只有1个位置与其交换(递归结束的条件,交换到第N个位置)
    注意点:一个递归结束后若对字符数组进行了改变,再下一个递归开始前要恢复字符数组
//全排列
    public static void permutation(String str){
        char[] chs = str.toCharArray();
        progress(chs, 0);
    }
    public static void progress(char[] chs, int index){
        //index,在字符数组中目前到达的位置
        if (index == chs.length){
            //走完整个字符数组
            System.out.println(String.valueOf(chs));
            return;
        }
        for (int i = index; i < chs.length; i++){
            //index位置与其后面的位置进行交换
            swap(chs, index, i);
            //规模的缩小
            progress(chs, index + 1);
            //一次递归结束恢复原字符数组
            swap(chs, index, i);
        }
    }
    public static void swap(char[] chs, int i, int j){
        char temp = chs[i];
        chs[i] = chs[j];
        chs[j] = temp;
    }
  • 结果演示
    str = “abb”image.png
  • 思路:
    若字符串的长度为n,字符均为小写字母
    考虑重复排列时:出现相同的字母时不进行位置交换
    public static void permutation(String str){
        char[] chs = str.toCharArray();
        progress(chs, 0);
    }
    public static void progress(char[] chs, int index){
        //index,在字符数组中目前到达的位置
        if (index == chs.length){
            //走完整个字符数组
            System.out.println(String.valueOf(chs));
            return;
        }
        //记录26个小写字母是否有相同的
        boolean[] visit = new boolean[26]; //默认为false
        for (int i = index; i < chs.length; i++) {
            if (!visit[chs[i] - 'a']) {
                //i位置的元素没有重复,可以与index位置交换
                //分支限界的思想,提前结束不可能的分支
                visit[chs[i] - 'a'] = true;
                //index位置与其后面的位置进行交换
                swap(chs, index, i);
                //规模的缩小
                progress(chs, index + 1);
                //一次递归结束恢复原字符数组
                swap(chs, index, i);
            }
        }
    }
    public static void swap(char[] chs, int i, int j){
        char temp = chs[i];
        chs[i] = chs[j];
        chs[j] = temp;
    }
  • 结果演示:
    str = “abb"image.png

题目四:拿纸牌比最大问题

image.png

  • 思路:
    (1)分为两种拿牌方式,先手和后手,若范围为left ~ right
    (2)先手分为左右,若拿left,值就是就是left位置的值 + 后手的值(left + 1 ~ right后手);若拿right,值就是right位置的值 + 后手的值(left ~ right - 1后手),返回二者值中较大的
    (3)后手分为对手拿左还是右,若对手拿left,值就是先手的值(left + 1 ~ right先手);若拿right,值就是先手的值(left ~ right - 1先手),因为对手一定拿最优解,那么后手一定是二者值中较小的情况
    (4)若 left == right 先手就是 left位置的值,后手就是0
    public static int win(int[] arr){
        if (arr == null || arr.length == 0){
            return 0;
        }
        int valueA = first(arr, 0, arr.length - 1); //A的先手情况
        int valueB = second(arr, 0, arr.length - 1); //B的后手情况
        return Math.max(valueA, valueB);
    }
    public static int first(int[] arr, int left, int right){
        //先手获得的最好分数
        if (left == right){
            //递归结束条件
            return arr[left];
        }
        int l = arr[left] + second(arr, left + 1, right); //拿左边
        int r = arr[right] + second(arr, left, right - 1); //拿右边
        return Math.max(l, r);
    }
    public static int second(int[] arr, int left, int right){
        //先手获得的最好分数
        if (left == right){
            //递归结束条件
            return 0;
        }
        int l = first(arr, left + 1, right); //对手拿走了左边
        int r = first(arr, left, right - 1); //对手拿走了右边
        //因为对手一定会挑选最优情况,所以留给后手一定是俩者中较差的选择
        return Math.min(l, r);
    }
  • 结果演示:
    arr = [1, 100, 2]


    image.png

题目五:递归逆序栈

  • 思维:
    (1)利用系统栈保存数据的性能解决问题
    (2)首先要实现得到栈底元素的函数,使用递归一直调用该函数是元素出栈,若栈为空返回栈底元素, 否则返回递归的临时变量
    (3)逆序函数的实现,一次逆序就是得到栈底元素,递归调用,直至栈为空,重新压入系统栈的保存数据
    public static void reverse(Stack<Integer> stack){
        //逆序栈
        if (stack.isEmpty()){
            //栈空返回
            return;
        }
        //得到栈底元素
        int bottom = getBottom(stack);
        //递归调用
        reverse(stack);
        //栈空后重新压入,利用系统栈的保存数据
        stack.push(bottom);
    }
    public static int getBottom(Stack<Integer> stack){
        //得到栈底元素
        int result = stack.pop();
        if (stack.isEmpty()){
            //到达栈底返回
            return result;
        }
        //递归调用
        int temp = getBottom(stack);
        //将非栈底元素重新压入栈
        stack.push(result);
        //temp在系统栈中保存递归数据
        return temp;
    }
  • 结果演示:
    image.png

题目六:数字与字符串的转化问题

image.png

  • 思路:
    从左往右的尝试
    在数组中index为当前遍历到的位置,尝试index位置的所有可能出现的情况后,index继续移动,直至数组结束即可
  • 情况分析:
    (1)若index位置出现0,说明前面转化出错,返回0
    (2)若index位置为 1 或 2,可以考虑与 index + 1位置结合转化,也可以单独转化,产生两种递归的方式:直接到下一个位置,或者跳过下一个位置
    (3)若index位置为3 ~ 9,就直接单独转化,没有产生多余的情况,直接递归到下一个位置
    (4)若index等于数组长度,说明此次转化结束返回 1
    public static int number(String str){
        if (str == null || str.length() == 0){
            return 0;
        }
        return process(str.toCharArray(), 0);
    }
    public static int process(char[] chs, int index){
        //遍历数组
        if (index == chs.length){
            //index到最后一个说明此次递归得到了一种情况
            return 1;
        }
        if (chs[index] == '0'){
            //index位置出现0,说明此次递归出错
            return 0;
        }
        if (chs[index] == '1'){
            //index位置出现1
            //单独转化index位置
            int result = process(chs, index + 1);
            if (index + 1 < chs.length){
                //结合index + 1位置转化
                result += process(chs, index + 2);
            }
            return result;
        }
        if (chs[index] == '2') {
            //index位置出现2
            //单独转化index位置
            int result = process(chs, index + 1);
            if (index + 1 < chs.length && (chs[index + 1] <= '6')){
                //结合index位置转化
                //该转化时,数字小于等于26
                result += process(chs, index + 2);
            }
            return result;
        }
        //index位置为 3 ~ 9
        return process(chs, index + 1);
    }
  • 结果演示:
    str = “121451202”image.png

题目七:重量和价值问题

image.png

  • 思路:
    尝试位置0 ~ i,分为要和不要
    (1)记录当前重量,若超过bag,则无法获得
    (2)若要,当前重量增加,价值增加,遍历下一个;若不要直接遍历到下一个
    (3)若已经将全部都遍历完则返回0
    public static int maxValue(int[] weights, int[] values, int bag){
        if (weights.length == 0){
            return 0;
        }
        return process(weights, values, bag, 0, 0);
    }
    public static int process(int[] weights, int[] values, int bag, int index, int alreadyWeight){
        //index:目前遍历到的位置
        //alreadyWeight:目前的重量
        if (index == values.length){
            //已经全部遍历完
            return 0;
        }
        int getValue;
        if (alreadyWeight + weights[index] <= bag){
            //当前仍然可以增加index的重量
            //要index位置的物品,alreadyWeight增加,返回value的增加
            getValue = values[index] + process(weights, values, bag, index + 1, alreadyWeight + weights[index]);
        }else {
            //无法增加,与noValue相同
            getValue = process(weights, values, bag, index + 1, alreadyWeight);
        }
        //不要index位置的物品,value, alreadyWeight均不变
        int noValue = process(weights, values, bag, index + 1, alreadyWeight);
        //决策出两者的较大值,返回
        return Math.max(getValue, noValue);
    }   
  • 结果演示:
    weights = [10, 3, 7, 8, 9]
    values = [4, 2, 3, 9, 5]
    bag = 20image.png

题目八:N皇后问题

image.png

  • 思路:
    (1)尝试在每一行的不同列位置放一个皇后
    (2)要求:以后放的都不能与前面的同列或者同斜线
    public static int num(int n){
        if (n < 0){
            return 0;
        }
        //record[i]表示第 i - 1行的皇后放在第几列
        int[] record = new int[n];
        return process(0, record, n);
    }
    public static int process(int index, int[] record, int n){
        //index目前遍历到的行
        //n为棋盘大小
        if (index == n){
            //遍历正常完成,之前的摆放正确
            return 1;
        }
        int result = 0;
        for (int row = 0; row < n; row++){
            //当前index行,皇后放到row列
            if (isValid(record, index, row)){
                //若位置有效
                record[index] = row;
                result += process(index + 1, record, n);
            }
        }
        return result;
    }
    public static boolean isValid(int[] record, int index, int row){
        //判断加入的皇后是否合法
        for (int i = 0; i < index; i++){
            //遍历之前行加入的皇后
            if (record[i] == row || Math.abs(record[i] - row) == Math.abs(i - index)){
                //判断是否同列,或者同斜线
                //同列的判断:两点的斜率是否为1(45度),或者-1(135度)
                //就是行差的绝对值与列差的绝对值是否相同
                return false;
            }
        }
        return true;
    }
  • 结果演示:
    n = 8image.png


目录
相关文章
|
23天前
|
人工智能 算法 BI
第一周算法设计与分析 D : 两面包夹芝士
这篇文章介绍了解决算法问题"两面包夹芝士"的方法,通过找出两个数组中的最大最小值,计算这两个值之间的整数个数,包括特判不存在整数的情况。
|
15天前
|
算法 数据可视化
基于SSA奇异谱分析算法的时间序列趋势线提取matlab仿真
奇异谱分析(SSA)是一种基于奇异值分解(SVD)和轨迹矩阵的非线性、非参数时间序列分析方法,适用于提取趋势、周期性和噪声成分。本项目使用MATLAB 2022a版本实现从强干扰序列中提取趋势线,并通过可视化展示了原时间序列与提取的趋势分量。代码实现了滑动窗口下的奇异值分解和分组重构,适用于非线性和非平稳时间序列分析。此方法在气候变化、金融市场和生物医学信号处理等领域有广泛应用。
|
1天前
|
机器学习/深度学习 存储 人工智能
文本情感识别分析系统Python+SVM分类算法+机器学习人工智能+计算机毕业设计
使用Python作为开发语言,基于文本数据集(一个积极的xls文本格式和一个消极的xls文本格式文件),使用Word2vec对文本进行处理。通过支持向量机SVM算法训练情绪分类模型。实现对文本消极情感和文本积极情感的识别。并基于Django框架开发网页平台实现对用户的可视化操作和数据存储。
11 0
文本情感识别分析系统Python+SVM分类算法+机器学习人工智能+计算机毕业设计
|
23天前
|
算法
算法设计与分析作业
这篇文章是关于算法设计与分析的作业,其中包含了两个算法实现:一个是使用分治算法实现的十进制大整数相乘(包括加法、减法和乘法函数),并进行了正确性和健壮性测试;另一个是使用快速排序思想实现的分治查找第K小元素的程序,并分析了其平均和最坏时间复杂度。
算法设计与分析作业
|
19天前
|
编解码 算法 图形学
同一路RTSP|RTMP流如何同时回调YUV和RGB数据实现渲染和算法分析
我们播放RTSP|RTMP流,如果需要同时做渲染和算法分析的话,特别是渲染在上层实现(比如Unity),算法是python这种情况,拉两路流,更耗费带宽和性能,拉一路流,同时回调YUV和RGB数据也可以,但是更灵活的是本文提到的按需转算法期望的RGB数据,然后做算法处理
|
23天前
|
人工智能 算法
第一周算法设计与分析:C : 200和整数对之间的情缘
这篇文章介绍了解决算法问题"200和整数对之间的情缘"的方法,通过统计数组中每个数模200的余数,并计算每个同余类中数的组合数来找出所有满足条件的整数对(i, j),使得\( A_i - A_j \)是200的整数倍。
|
23天前
|
人工智能 算法
第一周算法设计与分析 G : 排队援救
这篇文章介绍了解决算法问题"排队援救"的方法,通过使用队列和映射来模拟救援点的排队过程,并确定最终得到救援的人的顺序和编号。
|
23天前
|
算法 C++
第一周算法设计与分析 E : 构造回文串
这篇文章介绍了解决算法问题"构造回文串"的方法,即判断给定的整数N(视为字符串)是否可以通过在前面添加任意个0(或不添加)来构造一个回文串,并给出了相应的C++代码实现。
|
23天前
|
算法 C++
第一周算法设计与分析 H : 括号匹配
这篇文章介绍了解决算法问题"括号匹配"的方法,通过使用栈来检查给定字符串中的括号是否合法匹配,并提供了相应的C++代码实现。
|
23天前
|
算法 C++
第一周算法设计与分析 F : 模拟计算器
该文章 "第一周算法设计与分析 F : 模拟计算器" 的摘要或讨论。这篇文章介绍了如何设计一个程序来模拟一个基本的计算器,处理包含加、减、乘运算的表达式,并给出了相应的C++代码实现