算法设计与分析 暴力递归

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

暴力递归

概述

  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


目录
相关文章
|
9天前
|
JSON 监控 算法
员工上网行为监控:利用Scala编写数据处理和分析算法
企业在数字化时代利用Scala进行员工上网行为监控,以确保合规和网络安全。通过Scala的数据处理和分析能力,读取CSV日志数据转换为DataFrame,分析员工行为,如统计最常访问网站。此外,还展示了将监控数据以JSON格式提交至公司网站的函数,实现实时信息更新与安全防护。
37 5
|
3天前
|
机器学习/深度学习 算法 数据可视化
Matlab决策树、模糊C-均值聚类算法分析高校教师职称学历评分可视化
Matlab决策树、模糊C-均值聚类算法分析高校教师职称学历评分可视化
10 0
|
4天前
|
算法 搜索推荐 数据挖掘
MATLAB模糊C均值聚类FCM改进的推荐系统协同过滤算法分析MovieLens电影数据集
MATLAB模糊C均值聚类FCM改进的推荐系统协同过滤算法分析MovieLens电影数据集
13 0
|
4天前
|
算法 数据可视化 数据挖掘
数据分享|R语言改进的K-MEANS(K-均值)聚类算法分析股票盈利能力和可视化
数据分享|R语言改进的K-MEANS(K-均值)聚类算法分析股票盈利能力和可视化
10 0
|
4天前
|
数据采集 存储 算法
数据分享|Weka数据挖掘Apriori关联规则算法分析用户网购数据
数据分享|Weka数据挖掘Apriori关联规则算法分析用户网购数据
15 2
|
7天前
|
算法
数据结构与算法-递归思想
数据结构与算法-递归思想
6 0
|
7天前
|
机器学习/深度学习 数据采集 算法
共享单车需求量数据用CART决策树、随机森林以及XGBOOST算法登记分类及影响因素分析
共享单车需求量数据用CART决策树、随机森林以及XGBOOST算法登记分类及影响因素分析
14 0
|
8天前
|
移动开发 算法 数据可视化
数据分享|Spss Modeler关联规则Apriori模型、Carma算法分析超市顾客购买商品数据挖掘实例
数据分享|Spss Modeler关联规则Apriori模型、Carma算法分析超市顾客购买商品数据挖掘实例
|
10天前
|
算法 数据可视化 搜索推荐
数据分享|Python用Apriori算法关联规则分析亚马逊购买书籍关联推荐客户和网络图可视化
数据分享|Python用Apriori算法关联规则分析亚马逊购买书籍关联推荐客户和网络图可视化
31 11
|
10天前
|
算法 数据可视化 大数据
圆堆图circle packing算法可视化分析电商平台网红零食销量采集数据
圆堆图circle packing算法可视化分析电商平台网红零食销量采集数据
36 13