递归算法的总结与应用
一、步骤
- 把问题转化为规模缩小了的同类问题的子问题
- 有明确的不需要继续进行递归的条件(base case)
- 有当得到了子问题的结果之后的决策过程
- 不记录每一个子问题的解
二、实例
2.1 汉诺塔问题
打印n层汉诺塔从左边移动到最右边的全部过程
2.1.1 思路
不考虑左中右的情况。只考虑需要从源位置from移动到目标位置to,中间暂存的位置是help。那么问题就可以抽象为:
- 1~i-1:from - > help
- i :from - > to
- 1~i-1:help - > to
2.1.2 代码解析
public class Hanoi {
public static void hanoi(int n) {if (n <= 1) return;// n层汉诺塔,起始位置圆盘全放在left,目标是全部移动到rightfunc(n,"left", "mid", "right");}
public static void func(int i, String from, String help, String to) {// 当i等于1,说明达到递归终止条件,只剩一个最后圆盘,将它从from移动到to即可if (i == 1){System.out.println("Move 1 from " + from + " to " + to);} else {// 递归过程,和上述思路完全一致,中间i移动的过程打印func(i-1, from, help, to);System.out.println("Move " + i + " from " + from + " to " + to);func(i-1, help, to, from);}}
public static void main(String[] args) {// 3层汉诺塔的打印示例int n = 3;hanoi(n);}
}
2.2 打印字符串的全部子序列
打印一个字符串的全部子序列,包括空字符串
2.2.1 思路
对于字符串中的每个字符,都有两个选择:要or不要,最后就能得到所有情况的子序列
2.2.2 代码解析
public class PrintAllSubsquences {
public static void printAllSubsquence(String str) {char[] chs = str.toCharArray();process(chs, 0);}
public static void process(char[] chs, int i) {if(i == chs.length){System.out.println(String.valueOf(chs));return;}// 要当前字符接着走的路process(chs, i+1);char tmp = chs[i];chs[i] = 0;// 不要当前字符接着走的路process(chs, i+1);chs[i] = tmp;}public static void main(String[] args) {String test = "abc";printAllSubsquence(test);}
}
2.3 全排列
打印一个字符串的全排列
2.3.1 思路
从字符串的起点字符开始,将其后续的任一字符与其交换位置则构成一次排列,然后将其恢复继续处理下一位置。(可以利用visited数组优化递归过程,相当于剪枝,避免重复访问造成重复的全排列)
2.3.2 代码解析
public class PrintAllPermutations {
public static ArrayList<String> Permutation(String str) {// 结果数组ArrayList<String> res = new ArrayList<>();if (str == null || str.length() == 0) return res;char[] chs = str.toCharArray();process(chs, 0, res);return res;
}
public static void process(char[] chs, int i, ArrayList<String> res) {// 如果i等于chs的长度,说明完成了一次全排列,将其加入res中if(i == chs.length){res.add(String.valueOf(chs));}// visited存储当前位置是否被访问过,防止重复访问造成重复全排列boolean[] visited = new boolean[26];// i之前的位置是已经选择的位置,i之后的是可以进行选择的位置for (int j = i; j < chs.length; j++) {// 对于i之后的任意位置如果没有被访问过,则可以进行排列if(!visited[chs[j] - 'a']){visited[chs[j] - 'a'] = true;// 排列方式是交换这两个位置完成一次不同排列swap(chs, i, j);// 然后继续处理下一位置process(chs, i+1, res);// 再将之前交换的位置恢复swap(chs, i, j);}
}}
public static void swap(char[] chs, int i, int j) {char tmp = chs[i];chs[i] = chs[j];chs[j] = tmp;}
}
2.4 玩纸牌
给定一个整型数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸 牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A 和玩家B都绝顶聪明。请返回最后获胜者的分数。
【举例】
arr=[1,2,100,4]
开始时,玩家A只能拿走1或4。如果开始时玩家A拿走1,则排列变为[2,100,4],接下来 玩家 B可以拿走2或4,然后继续轮到玩家A...
如果开始时玩家A拿走4,则排列变为[1,2,100],接下来玩家B可以拿走1或100,然后继 续轮到玩家A...
玩家A作为绝顶聪明的人不会先拿4,因为拿4之后,玩家B将拿走100。所以玩家A会先拿1, 让排列变为[2,100,4],接下来玩家B不管怎么选,100都会被玩家 A拿走。玩家A会获胜, 分数为101。所以返回101。
arr=[1,100,2]
开始时,玩家A不管拿1还是2,玩家B作为绝顶聪明的人,都会把100拿走。玩家B会获胜, 分数为100。所以返回100。
2.4.1 思路
主函数返回先手拿牌和后手拿牌中分数高的那个值
- 作为先手拿牌,他有两种选择:
- (选择左边+下一次作为后手拿牌)或者(选择右边+下一次作为后手拿牌)中的更大值
- 当拿到最后一张牌,即L等于R的时候,应该先手拿牌,返回arr[L]作为终止递归的条件
- 作为后手拿牌,也有两种选择:
- 如果别人拿走L,那作为先手在L+1和R上进行选择。如果别人拿走R,则作为先手在L和R-1上进行选择,取这两者中的更小值(对方会决定对我最不利的)
- 当拿到最后一张牌,即L等于R的时候,后手不拿牌,返回0作为终止递归的条件。
2.4.2 代码解析
public class CardsInLine {
// 暴力递归public static int win1(int[] arr) {if(arr == null || arr.length == 0){return 0;}return Math.max(f(arr, 0, arr.length-1), s(arr, 0, arr.length-1));}
public static int f(int[] arr, int i, int j) {if(i == j){return arr[i];}return Math.max(arr[i] + s(arr, i+1, j), arr[j] + s(arr, i, j-1));}
public static int s(int[] arr, int i, int j) {if(i == j) {return 0;}return Math.min(f(arr, i+1, j), f(arr, i, j-1));}
// 动态优化版本public static int win2(int[] arr) {if(arr == null || arr.length == 0) {return 0;}int[][] f = new int[arr.length][arr.length];int[][] s = new int[arr.length][arr.length];for (int j = 0; j < arr.length; j++) {f[j][j] = arr[j];for (int i = j-1; i >= 0; i--) {f[i][j] = Math.max(arr[i] + s[i+1][j], arr[j] + s[i][j-1]);s[i][j] = Math.min(f[i+1][j], f[i][j-1]);}}return Math.max(f(arr, 0, arr.length-1), s(arr, 0, arr.length-1));}
public static void main(String[] args) {int[] arr = { 1, 2, 100, 4};System.out.println(win1(arr));System.out.println(win2(arr));
}
}
2.5 逆序栈
给定一个栈,请逆序这个栈,不能申请额外的数据结构,只能使用递归函数。
2.5.1 思路
首先设计一个递归函数f,f函数可以实现将栈中最底部元素弹出,其余元素覆盖当前栈的功能。
然后设计逆序栈的函数,通过调用f函数进行递归,最后可以实现栈的逆序。
2.5.2 代码解析
public class ReverseStackUsingRecursive {
public static void reverse(Stack<Integer> stack) {if(stack.isEmpty()) return;else{int i = getAndRemoveLastElement(stack);reverse(stack);stack.push(i);}}
public static int getAndRemoveLastElement(Stack<Integer> stack) {int result = stack.pop();if(stack.isEmpty()){return result;} else{int last = getAndRemoveLastElement(stack);stack.push(result);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());}
}
}
2.6 数字字符串转化为字母字符串
规定1和A对应、2和B对应、3和C对应...
那么一个数字字符串比如"111",就可以转化为"AAA"、"KA"和"AK"。
给定一个只有数字字符组成的字符串str,返回有多少种转化结果。
2.6.1 思路
假设0到i-1的位置上已经确定,从i位置开始做转化
- 如果i位置上为‘0‘,返回0,因为0没办法转化为字母
- 如果i位置上为’3‘ ~ ’9‘,因为字母只有26个,只能将其转化为对应字母,接着尝试下一个位置。
- 如果i位置上为’1‘ ,有两种选择:
- i自己作为单独部分,接着尝试i+1~最后
- i和i+1作为单独部分,接着尝试i+2~最后
- 如果i位置上为’2‘ ,有两种选择:
- i自己作为单独部分,接着尝试i+1~最后
- 如果i+1的值为’0‘ ~ ’6’,可以将i和i+1作为单独部分,接着尝试i+2~最后
2.6.2 代码解析
public class Code06_ConvertToLetterString {
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 i) {if (i == chs.length) return 1;if (chs[i] == '0') return 0;if (chs[i] == '1'){int res = process(chs, i+1);if (i + 1 < chs.length){res += process(chs, i+2);}return res;}if (chs[i] == '2'){int res = process(chs, i+1);if (i + 1 < chs.length && chs[i+1] >= '0' && chs[i+1] <= '6'){res += process(chs, i+2);}return res;} else{return process(chs, i+1);}}
public static void main(String[] args) {System.out.println(number("11111"));}
}
2.7 装物品
给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表 i号物品的重量和价值。给定一个正数bag,表示一个载重bag的袋子,你装的物 品不能超过这个重量。返回你能装下最多的价值是多少?
2.7.1 思路
从weights数组中依次拿出一个物品,看bag剩余空间是否能放这个物品,如果能放则有放与不放两种选择。选择结束之后选过的位置不再变化,继续对下一个物品进行选择。
2.7.2 代码解析
public class Code07_Knapsack {
public static int maxValue1(int[] weights, int[] values, int bag) {return process1(weights, values, 0, bag);}
/*** 暴力递归* @param weights 重量数组* @param values 价值数组* @param i:i之前的是已经确定的,在i之后进行选择* @param bag:剩余空间* @return*/public static int process1(int[] weights, int[] values, int i, int bag) {// 如果i来到了最后,则没有选择的了,返回0if (i == weights.length){return 0;}// 如果剩余空间大于等于放下的物品,有放与不放两种选择,返回其中更大的那个if (bag >= weights[i]){return Math.max(process1(weights, values, i+1, bag),values[i] + process1(weights, values, i+1, bag-weights[i]));}return 0;}
// 动态规划版本public static int maxValue2(int[] c, int[] p, int bag) {int[][] dp = new int[c.length+1][bag+1];for(int i = c.length-1; i >= 0 ; i--){for(int j = bag; j >= 0; j--){dp[i][j] = dp[i+1][j];if(j + c[i] <= bag){dp[i][j] = Math.max(dp[i][j], p[i] + dp[i+1][j+c[i]]);}}}return dp[0][0];}
public static void main(String[] args) {int[] weights = { 3, 2, 4, 7 };int[] values = { 5, 6, 3, 19 };int bag = 11;System.out.println(maxValue1(weights, values, bag));System.out.println(maxValue2(weights, values, bag));}
}
有帮助到你的点赞、收藏和关注一下吧
需要更多教程,微信扫码即可
递归算法的总结与应用相关推荐
- 算法图解/二分查找/简单查找/选择排序/递归算法/快速排序算法/
大 O 表示法 大 O 表示法在讨论运行时间时,log 指的都是 log2 大 O 表示法指出了算法有多快,让你能够比较操作数,它指出了算法运行时间的增速,而并非以秒为单位的速度. 大 O 表示法指出 ...
- 什么是php递归算法_PHP递归算法(一)
在前面的文章中,我们为大家介绍了PHP算法系列之<PHP随机取一算法>和<PHP冒泡排序算法>,需要的朋友可以了解学习.本篇文章我们将继续为大家带来常见的PHP算法,即PHP递 ...
- 漫画:5分钟弄懂分治算法!它和递归算法的关系!
分治顾名思义"分而治之",英文的意思翻译为"分割并征服". 分治思想,简而言之就是将原问题分解成与"原问题相同但是规模更小"的子问题,并可以 ...
- 算法笔记-递归算法、递归排序、递归的时间复杂度、master公式(也叫主方法)
1. 递归排序题 通过递归算法来获取一个数组中的最大值 2. 算法思路 采用递归的思路来进行解题,那么肯定是需要自己调用自己的,这也就是递归,于是考虑怎么自己调用自己呢?我们可以采用二分法来制造递归的 ...
- 后序遍历的非递归算法python_二叉树后序遍历(递归与非递归)算法C语言实现...
二叉树后序遍历的实现思想是:从根节点出发,依次遍历各节点的左右子树,直到当前节点左右子树遍历完成后,才访问该节点元素. 图 1 二叉树 如图 1 中,对此二叉树进行后序遍历的操作过程为: 从根节点 1 ...
- PHP开发之递归算法的三种实现方法
递归算法对于任何一个编程人员来说,应该都不陌生.因为递归这个概念,无论是在PHP语言还是Java等其他编程语言中,都是大多数算法的灵魂.对于PHP新手来说,递归算法的实现原理可能不容易理解.但是只要你 ...
- 汉诺塔递归与非递归算法
问题描述: 在印度,有这么一个古老的传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针.印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这 ...
- 2.2基本算法之递归和自调用函数_你为什么学不会递归?读完这篇文章轻松理解递归算法...
对于很多编程初学者来说,递归算法是学习语言的最大障碍之一.很多人也是半懂不懂,结果学到很深的境地也会因为自己基础不好,导致发展太慢. 可能也有一大部分人知道递归,也能看的懂递归,但在实际做题过程中,却 ...
- 实习二 栈、队列和递归算法设计 (题目:停车场管理 )
一.需求分析 1.每一组输入数据包括:汽车"到达"或"离去"信息.汽车牌照号码以 及到达或离去的时刻. 2.输出信息:若是车辆到达,则输出汽车在停车场内或便道上 ...
- 递归算法转换为非递归算法的技巧
递归算法转换为非递归算法的技巧 递归函数具有很好的可读性和可维护性,但是大部分情况下程序效率不如非递归函数,所以在程序设计中一般喜欢先用递归解决问题,在保证方法正确的前提下再转换为非递归函数以提高效率 ...
最新文章
- 【实用总结】DOM节点className操作
- 关于hive中.conf配置文档中sink为avro的端口问题
- 牛客 - 共鸣问题(贪心+思维)
- Essential MSBuild: .NET 工具生成引擎概述
- 【软件开发底层知识修炼】十 链接器-main函数不是第一个被执行的函数
- oracle过程包保存乱码_这些火遍网络的哆啦A梦表情包,你知道出处吗?
- SQLServer-Error Log
- UVa10006 Carmichael Numbers【素数判定+快速模幂】
- angular.js 验证码注册登录
- Java 常用工具类 - 校验银行卡号 BankCardUtils
- 《代码整洁之道》第14章 逐步改进 的代码片段
- matlab pdetool解热传导方程,传热学与应用(李晓炜)基于pdetool的热传导数值计算.pdf...
- select默认选中
- 【esp32lvgl】-2.1 # esp32移植lvgl7驱动st7789屏幕(ESP-IDF框架)
- 教你三步实现CDH到星环TDH的平滑迁移
- 概率论中PDF、PMF和CDF的区别与联系
- 【牛客网专项练习题】
- 商务部关于网上交易的指导意见(暂行)
- 安卓模拟器登录微信自动化测试最佳实践
- PS制作红色拟物化时钟icon图标