【算法&数据结构体系篇class17】:递归

文章详细介绍了如何使用暴力递归解决汉诺塔问题,包括从左到右的移动过程,并展示了打印字符串的所有子序列(含去重)的方法,以及字符串的全排列算法,同时提供了不允许重复字面值的子序列和排列的实现。此外,还讨论了如何在不使用额外数据结构的情况下,仅通过递归函数逆序一个栈。
摘要由CSDN通过智能技术生成

一、暴力递归

暴力递归就是尝试
1,把问题转化为规模缩小了的同类问题的子问题
2,有明确的不需要继续进行递归的条件(basecase)
3,有当得到了子问题的结果之后的决策过程
4,不记录每一个子问题的解

二、熟悉什么叫尝试

•打印n层汉诺塔从最左边移动到最右边的全部过程

•打印一个字符串的全部子序列

•打印一个字符串的全部子序列,要求不要出现重复字面值的子序列

•打印一个字符串的全部排列

•打印一个字符串的全部排列,要求不要出现重复的排列

2.1打印n层汉诺塔从最左边移动到最右边的全部过程

package class17;

/**
 * 打印n层汉诺塔从最左边移动到最右边的全部过程
 *  汉诺塔,左中右 三个棍子,一开始左侧放着一堆环,从上到下依次环变大放着 要求从将这个顺序环移动到右侧棍子 移动过程还要遵守小环在大环上面
 */

public class Hanoi {
    //方法一 汉诺塔调用递归函数 一共有六个递归函数。左到中 左到右  中到左 中到右 右到左 右到中

    /**
     * 宏观考虑:
     * 左->右:
     * 1.将顶部n-1个环 从左移动到中  再将下面第n个环 打印输出移动顺序:n从左移动到右,再将n-1个环从中移动到右
     * 这里涉及  左->中  中->右 两个递归函数
     *
     * 左->中:
     * 1.将顶部n-1个环 从左移动到右  再将下面第n个环 打印输出移动顺序:n从左移动到中,再将n-1个环从右移动到中
     * 这里涉及  左->右  右->中 两个递归函数...
     *
     *
     */
    public static void hannoi1(int n){
        //调用左移动到右的递归函数
        leftToRight(n);
    }

    // 请把1~N层圆盘 从左 -> 右
    public static void leftToRight(int n){
        //停止递归的条件 base case 层数只剩1层 可以直接从左侧移动到右侧 直接打印 1 从左移动到右,然后退出当前递归
        if(n == 1){
            System.out.println("Move 1 from left to right");
            return;
        }
        //先将顶部n-1个环移动到中间
        leftToMid(n-1);
        //移动完后,将最底部第n个环从左移动到右 打印出来 因为只剩一个环 直接移动
        System.out.println("Move "+ n + " from left to right");
        //最后就是将n-1个环 从中间再移动到右边
        midToRight(n-1);

    }

    // 请把1~N层圆盘 从左 -> 中
    public static void leftToMid(int n){
        if(n == 1){
            //n只有1个环 直接打印从 1环 左->中
            System.out.println("Move 1 from left to mid");
            return;
        }
        //先将顶部n-1个环从左移动到右侧
        leftToRight(n-1);
        //移动完成 左侧只剩一个环,直接打印 左->中
        System.out.println("Move "+n+" from left to mid");
        //最后再将右侧的n-1个环移动到中间
        rightToMid(n-1);
    }

    // 请把1~N层圆盘 从中 -> 右
    public static void midToRight(int n){
        if(n == 1){
            //n只有1个环 那就直接打印中->右 只剩一个环可以直接移动
            System.out.println("Move 1 from mid to right");
            return;
        }
        //先将中间 顶部n-1个环移动到左
        midToLeft(n-1);
        //移动完中间剩下底部第n个环 直接打印输出 n 从中间->右
        System.out.println("Move " + n + " from mid to right");
        //最后再将中间n-1个环 移动到右边
        leftToRight(n-1);
    }

    // 请把1~N层圆盘 从中 -> 左
    public static void midToLeft(int n){
        if(n == 1){
            //一个环 直接打印 1 中->左
            System.out.println("Move 1 from mid to left");
            return;
        }
        //先将中间 顶部n-1个环 移动到右
        midToRight(n-1);
        //移动完中间就是底部第n个环 直接打印 中-> 左
        System.out.println("Move " + n + " from mid to left");
        //然后最后将右边n-1个环移动到左边
        rightToMid(n-1);
    }

    // 请把1~N层圆盘 从右 -> 中
    public static void rightToMid(int n){
        if(n == 1 ){
            System.out.println("Move 1 from right to mid");
            return;
        }
        //先将右侧 顶部n-1个环 移动到左边
        rightToLeft(n-1);
        //然后右侧只剩最后一个n环 直接打印输出 右->中
        System.out.println("Move " + n + " from right to mid");
        //最后将左边的n-1个环移动到中间
        leftToMid(n-1);
    }

    // 请把1~N层圆盘 从右 -> 左
    public static void rightToLeft(int n){
        if(n==1){
            System.out.println("Move 1 from right to left");
            return;
        }
        //先将右侧 顶部n-1个环移动到中间
        rightToMid(n-1);
        //移动完 右侧就只剩最后n环,直接打印移动 右->左
        System.out.println("Move " + n + " from right to left");
        //最后再将中间n-1个环移动到左边
        midToLeft(n-1);
    }

    /**
     * 方法二: 只用一个递归函数  压缩递归函数,给递归函数加四个参数N(多少层)  from to other 表示三个汉诺塔的棍 从那里from
     *         移动到那里to  然后第三棍就是other
     * @param n
     */
    public static void hannoi2(int n){
        if(n > 0){
            func(n,"left","right","mid");
        }
    }

    //递归函数
    public static void func(int n,String from,String to,String other){
        if(n == 1){
            //base case:当n为一个环时,则可以直接打印输出 将1  从 from 左边 移动到to 右边
            System.out.println("Move 1 from "+from + " to " + to);
            return;
        }
        //先将from位置也就是左边 顶部n-1个环 移动到other位置 中间 ; 注意这里第二个参数移动到other mid中间 而第三个参数就是to 右边
        func(n-1,from,other,to);
        //移动完之后 from位置 也就是左边 剩下一个环 底部N 只有一个可以直接打印输出
        System.out.println("Move "+ n +" from " + from + " to "+to);
        //最后再将在other位置 中间的n-1个环移动到 to 右边位置, 这个时候第三参数 其他棍就是from 右边
        func(n-1,other,to,from);
    }

    public static void main(String[] args){
        int n = 3;
        hannoi1(3);
        System.out.println("============");
        hannoi2(3);
    }
}

2.2 打印一个字符串的全部子序列 / 以及要求不要出现重复字面值的全部子序列

package class17;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

/**
 * 打印一个字符串的全部子序列
 *比如: "ab"   全部子序列是  ""  a ab b  (空也算一个)
 *
 * 打印一个字符串的全部子序列,要求不要出现重复字面值的子序列
 * 去重比如 "acc"  如果不去重 两个c就会认为不一样 会有 ac 索引1的c  ac 索引2的c
 * 基于第一种 采用有序表接收 即可去重
 */
public class PrintAllSubsquences {
    //打印字符串的全部子序列
    public static List<String> subs(String s){
        char[] str = s.toCharArray();      //字符串转换成字符数组
        String path = "";                  //初始化 选择的字符路径
        List<String> ans = new ArrayList<>();  //存放全部子序列结果
        //递归函数 传递字符串 , 开始遍历的字符索引,结果集,访问的当前字符路径
        process1(str,0,ans,path);
        return ans;
    }

    /**
     *
     * @param str  字符数组内容
     * @param index 字符数组索引
     * @param ans   保存子序列
     * @param path  以访问过的字符路径
     */

    public static void process1(char[] str,int index,List<String> ans,String path){
        if(index == str.length){
            //base case: 如果遍历字符数组索引到了最后越界,就将当前的path添加到ans结果集中 返回上层
            ans.add(path);
            return;
        }
        //递归遍历 如果当前index索引字符内容不选择,即该索引字符不选 为空。 那么就继续递归。index+1下个索引,路径以及传递上层的path不变
        process1(str,index+1,ans,path);
        //递归遍历 如果当前index索引字符内容选择 即该索引字符选 则str[index]追加path,并递归下个索引
        process1(str,index+1,ans,path+ str[index]);
    }

    //打印一个字符串的全部子序列,要求不要出现重复字面值的子序列
    public static List<String> subsNoRepeat(String s){
        char[] str = s.toCharArray();              //转换字符数组处理
        HashSet<String> set = new HashSet<>();     //定义有序表存储数据去重 重复不保存
        String path = "";                          //已选字符路径
        process2(str,0,set,path);
        List<String> ans = new ArrayList<>();
        for(String ss: set){
            ans.add(ss);
        }
        return ans;
    }
    public static void process2(char[] str,int index, HashSet<String> set,String path){
        if(index == str.length){
            //base case:如果索引越界了就将当前path加入set中
            set.add(path);
            return;
        }
        //递归遍历 当前index不取字符 path保持上层不变 index+1
        process2(str,index+1,set,path);
        //递归遍历 当前index取字符  path加上当前字符 index+1
        process2(str,index+1,set,path+str[index]);
    }


    public static void main(String[] args) {
        String test = "acc";
        List<String> ans1 = subs(test);
        List<String> ans2 = subsNoRepeat(test);

        for (String str : ans1) {
            System.out.println(str);
        }
        System.out.println("=================");
        for (String str : ans2) {
            System.out.println(str);
        }
        System.out.println("=================");

    }

}

2.3  打印一个字符串的全部排列 / 以及要求不要出现重复的全部排列

package class17;


import java.util.ArrayList;
import java.util.List;

/**
 * 打印一个字符串的全部排列
 * "abc" ->   abc acb bac bca cba cab
 * <p>
 * 打印一个字符串的全部排列,要求不要出现重复的排列
 * "acc"  acc cac cca    如果不去重 因为存在相同字符两个c就会有相同的排序结果
 */
public class PrintAllPermutations {
    //打印一个字符串的全部排列  方法一
    public static List<String> permutation1(String s) {
        //定义一个结果集合
        List<String> ans = new ArrayList<>();
        if (s == null || s.length() == 0) {
            //字符串空 直接返回空集合
            return ans;
        }
        //定义一个集合保存每个字符
        List<Character> list = new ArrayList<>();
        char[] chars = s.toCharArray();
        for (char c : chars) {
            list.add(c);
        }
        //定义一个访问路径
        String path = "";
        //调用递归函数  返回全部的排列给ans
        f(list, path, ans);
        return ans;
    }

    /**
     * @param list 字符集合 每次选后就进行删除 最后回到该层递归就要在复原
     * @param path 访问过的字符路径
     * @param ans  将访问过的字符路径转换字符串保存到结果集中
     */
    public static void f(List<Character> list, String path, List<String> ans) {
        if (list.isEmpty()) {
            //当字符集合都已经遍历完 就将当前路径添加到ans 再返回上层调用
            ans.add(path);
            return;
        }
        //全部排列 每个字符都可以尝试做第一个元素排列 遍历整个字符集合
        for (int i = 0; i < list.size(); i++) {
            //取出当前来到的字符 删除
            Character ch = list.get(i);
            list.remove(i);
            //接着递归下层 路径加上当前来到的字符ch
            f(list, path + ch, ans);
            //当我们首次 遍历得到的就是原字符顺序 开始返回上层调用 需要将当前层删除的元素 对应索引去恢复元素内容 不然会存在有些字符顺序缺失
            list.add(i, ch);
        }
    }

    //打印一个字符串的全部排列 方法二
    public static List<String> permutation2(String s) {
        //定义一个结果集合
        List<String> ans = new ArrayList<>();
        if (s == null || s.length() == 0) {
            //字符串空 直接返回空集合
            return ans;
        }
        //转换字符数组
        char[] chars = s.toCharArray();
        //调用递归 从索引0开始递归遍历字符数组 返回全部排列ans
        g(chars, 0, ans);
        return ans;
    }

    public static void g(char[] ch, int index, List<String> ans) {
        if (index == ch.length) {
            //当遍历的索引越界 遍历完成 就将当前字符数组转换加入结果集合
            ans.add(String.valueOf(ch));
            return;
        }
        for (int i = index; i < ch.length; i++) {
            //进行字符数组的顺序交换 以达变化以每个字节做开始的全部排序序列
            swap(ch, i, index);
            //接着递归下一个索引字符
            g(ch, index + 1, ans);
            //最后一层遍历完得到一个结果,要返回上层继续调用 先恢复当前的交换位置字符
            swap(ch, i, index);
        }
    }

    public static void swap(char[] ch, int i, int j) {
        char temp = ch[i];
        ch[i] = ch[j];
        ch[j] = temp;
    }


    //打印一个字符串的全部排列  要求不要出现重复的排列
    //增加一个 布尔数组 判断是否已经重复的排列就跳过 剪枝操作
    public static List<String> permutation3(String s) {
        //定义一个结果集合
        List<String> ans = new ArrayList<>();
        if (s == null || s.length() == 0) {
            //字符串空 直接返回空集合
            return ans;
        }
        //转换字符数组
        char[] chars = s.toCharArray();
        //调用递归函数 从0索引开始遍历 返回ans全部去重排列
        h(chars, 0, ans);
        return ans;
    }

    public static void h(char[] ch, int index, List<String> ans) {
        if (index == ch.length) {
            //越界 将当前字符数组转换加入集合 返回上层调用
            ans.add(String.valueOf(ch));
            return;
        }
            //定义一个布尔数组判断是否有重复排列 有则不进行该层递归 剪枝操作
            //ASCII字符表 一个字符最多表示256个字符
            boolean[] visited = new boolean[256];
            for (int i = index; i < ch.length; i++) {
                //ch[i]字符如果有重复的 对应的布尔数组中就是同一位置 没有访问过就执行逻辑 访问过了就会跳过 剪枝 避免重复
                if (!visited[ch[i]]) {
                    //当该字符没有被取出时,进行逻辑,修改访问值true. 进行交换操作 往下个索引递归
                    //比如acc字符串  str[1]第一个c 取到之后得到cac cca两个排序 到第二c str[2] 如果继续做判断遍历
                    //就会又新增 cac cca 所有需要visited判断有相同字符 不再重复遍历 这样就不会重复排序出现
                    visited[ch[i]] = true;
                    swap(ch, i, index);
                    h(ch, index + 1, ans);
                    //最后返回上层 需要恢复现场 原数组顺序
                    swap(ch, i, index);
                }
            }
    }

    public static void main(String[] args) {
        String s = "acc";
        List<String> ans1 = permutation1(s);
        for (String str : ans1) {
            System.out.println(str);
        }
        System.out.println("=======");
        List<String> ans2 = permutation2(s);
        for (String str : ans2) {
            System.out.println(str);
        }
        System.out.println("=======");
        List<String> ans3 = permutation3(s);
        for (String str : ans3) {
            System.out.println(str);
        }

    }
}

三、给你一个栈,请你逆序这个栈,不能申请额外的数据结构,只能使用递归函数。 如何实现?

package class17;

import java.util.Stack;

/**
 * 给你一个栈,请你逆序这个栈,
 * 不能申请额外的数据结构,
 * 只能使用递归函数。 如何实现?
 *
 * 思路
 * 1.设计一个递归函数f,将栈底移除 然后上层元素依次往下压 同时返回移除的哪个原栈底元素
 * 2.然后外层函数,套进这个f函数,并且外层也是递归,一直是将栈底部返回并且移除。直到最后
 * 移除到空,就开始往上调用 依次push 前面移除的元素这样栈的顺序就会倒序
 */
public class ReverseStackUsingRecursive {
    //将栈倒序 利用递归  不使用额外数据结构
    public static void reverse(Stack<Integer> stack){
        //递归弹出栈直到空时就直接返回上层
        if(stack.isEmpty()) return;
        //调用f函数,移除并返回栈底元素
        int i = f(stack);
        //移除后就接着递归调用该倒序函数 直到栈空
        reverse(stack);
        //直到栈空了 那么就来到往上调用,依次当前移除的栈底元素 再重新依次入栈
        //比如一开始入栈 1 2 3  那么到这里第一次是栈空 3入栈 再上层 2入栈 1入栈
        //栈就变成 3 2 1 倒序了
        stack.push(i);
    }

    //将栈底部元素返回并移除
    public static int f(Stack<Integer> stack){
        //弹出当前栈顶元素
        int res = stack.pop();
        //如果此时栈空了 那么就将前面弹出的元素返回上层调用
        if(stack.isEmpty()){
            return res;
        }
        //递归调用,直到栈空,返回栈底元素 保存 用来返回上层结果
        int last = f(stack);
        //首次来到栈空位置,入栈的是原栈的倒数第二个元素 最后一个元素在前面已经弹出 并且保存在last
        stack.push(res);
        //返回上层 带上栈底元素,依次返回到顶部 返回都是栈底元素
        return last;
    }


    public static void main(String[] args) {
        Stack<Integer> test = new Stack<Integer>();
        test.push(1);
        test.push(2);
        test.push(3);
        test.push(4);
        test.push(5);
        reverse(test);
        while (!test.isEmpty()) {
            System.out.println(test.pop());
        }

    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值