**递归**是一种解决问题的方法,将问题分解为更小的子问题,直到得到一个足够小的问题可以被很简单的解决。通常递归涉及函数调用自身。递归允许我们编写优雅的解决方案,解决可能很难编程的问题。
像阿西莫夫机器人,所有递归算法必须服从三个重要的定律:
1. 递归算法必须具有基本情况。
2. 递归算法必须改变其状态并向基本情况靠近。
3. 递归算法必须以递归方式调用自身。
让我们更详细地看看每一个定律,看看它如何在 `listsum` 算法中使用。首先,基本情况是算法停止递归的条件。基本情况通常是足够小以直接求解的问题。在`listsum` 算法中,基本情况是长度为 1 的列表。
为了遵守第二定律,我们必须将算法向基本情况的状态改变。状态的改变意味着该算法正在使用的一些数据被修改。通常,表示问题的数据在某种程度上变小。在 `listsum` 算法中,我们的主要数据结构是一个列表,因此我们必须将我们的状态转换工作集中在列表上。因为基本情况是长度 1 的列表,所以朝向基本情况的自然进展是缩短列表。在 Activecode 2 第五行,我们调用 `listsum` 生成一个较短的列表。
最后的法则是算法必须调用自身。这是递归的定义。递归对于许多新手程序员来说是一个混乱的概念。作为一个新手程序员,你已经知道函数是有益的,因为你可以将一个大问题分解成较小的问题。较小的问题可以通过编写一个函数来解决。我们用一个函数解决问题,但该函数通过调用自己解决问题!该逻辑不是循环;递归的逻辑是通过将问题分解成更小和更容易的问题来解决的优雅表达。
计算1到n的和
public static int sum(int n) {
if(n==1) {
return 1;
}else {
return n+sum(n-1);
}
}
计算列表中的数求和
public class digui {
public static void main(String[] args) {
int [] a={1,2,3,5};
int [] newdata= {};
int c=sum(a,newdata);
System.out.println(c);
}
public static int sum(int [] a,int []newdata) {
if(a.length==1) {
return a[0];
}else {
newdata=Arrays.copyOfRange(a, 1, a.length);
//列表切片,copyOfRange(obj,from,to)
return a[0]+sum(newdata,a);
}
}
计算1到n的阶乘
public static long multiply(long i) {
if (i==1) {
return 1;
}else {
return i*multiply(i-1);
}
}
硬币找零问题:
有n种硬币,面值为v1,v2,v3,v4,...,vn,例如{1,2,5,10,20} 给出一个比较大的数值money,例如63,可以选用多个硬币,使得面值之和恰好为money,求硬币的最小数
public class dpCoins {
/**
*
* @param changes 零钱数组,{1,2,5,10,20}
* @param money 需要找零的钱
* @param minCoins,数组用于存放每种面值的钱找零的最小数目,避免重复计算
* @return 面值位money时,所需的最小找零数
*/
public static int makeChange(int[] changes,int money,int minCoins[]) {
//对每种面值的钱进行遍历,依次计算其所需最小找零数,并存入数组minCoins
for(int i=0;i<(money+1);i++) {
//先定义假设当前面额的钱最小找零数为面额值
int coinCount=i;
//对所有零钱依次遍历
for(int j=0;j<changes.length;j++) {
//如果这个零钱小于当前的面值再进入,否则直接进行下一个面值的计算
if (changes[j]<=i) {
//如果选择了changes[j]这个零钱,我先查看下之前计算过的最小零钱列表中的(moeney-此零钱)
//的那个值,加上1则是当前面值的最小零钱数,如果比coinCount小,
//那我就把这个值记录成coinCount
if ((minCoins[i-changes[j]]+1)<coinCount) {
coinCount = minCoins[i-changes[j]]+1;
}
}
//更新minCoins列表,将当前面值下的最小零钱数保存到对应的位置 }
}
minCoins[i]=coinCount;
}
//循环体之外的语句,返回最后的零钱数
return minCoins[money];
}
public static void main(String[] args) {
int []changes= {1,2,5,10,20};
int money=53;
int [] mincoins=new int[money+1];
System.out.println(makeChange(changes, money, mincoins));
}
}
追踪零钱个数,则在每次增加面值时,把新添加的那个零钱值添加到coinUsed数组中,再写个打印零钱列表的函数
public class dpCoins {
/*有n中硬币,面值为v1,v2,v3,v4,...,vn,例如{1,2,5,10,20}
* 给出一个比较大的数值money,例如63,可以选用多个硬币,使得面值之和恰好为money
* 求硬币的最小数和最大数
* */
/**
*
* @param changes 零钱数组,{1,2,5,10,20}
* @param money 需要找零的钱
* @param minCoins,数组用于存放每种面值的钱找零的最小数目,避免重复计算
* @return 面值位money时,所需的最小找零数
*/
public static int makeChange(int[] changes,int money,int minCoins[],int [] coinUsed) {
//对每种面值的钱进行遍历,依次计算其所需最小找零数,并存入数组minCoins
for(int i=1;i<(money+1);i++) {
//先定义假设当前面额的钱最小找零数为面额值
int coinCount=i;
//对所有零钱依次遍历
int newCoin=1;
for(int j=0;j<changes.length;j++) {
//如果这个零钱小于当前的面值再进入,否则直接进行下一个面值的计算
if (changes[j]<=i) {
//如果选择了changes[j]这个零钱,我先查看下之前计算过的最小零钱列表中的(moeney-此零钱)
//的那个值,加上1则是当前面值的最小零钱数,如果比coinCount小,
//那我就把这个值记录成coinCount
if ((minCoins[i-changes[j]]+1)<coinCount) {
coinCount = minCoins[i-changes[j]]+1;
newCoin=changes[j];
}
}
//更新minCoins列表,将当前面值下的最小零钱数保存到对应的位置 }
}
minCoins[i]=coinCount;
coinUsed[i]=newCoin;
}
//循环体之外的语句,返回最后的零钱数
return minCoins[money];
}
public static void printCoins(int[] coinUsed,int money) {
/**
*coinUsed 使用过的零钱列表,长度位money+1,保存了每增加面值时对应位置增加的硬币面值
*money需要找零的面值
*如果我们知道添加的最后一个零钱值,我们可以简单地减去硬币的值,
*在表中找到前一个条目,找到该金额的最后一个硬币。
*我们可以通过表继续跟踪,直到我们开始的位置。
*/
int coin=money;
while(coin>0) {
//首先取出最后一次添加的零钱,并打印
int thisCoin=coinUsed[coin];
System.out.print(thisCoin+"\t");
//目前面值减去上一次添加的零钱,等于倒数第二次添加的零钱
//对于第一次调用,我们从数组位置 `65` 开始,然后打印 `5`。然后我们取 `65-5 = 60`,看看列表的第 60 个元素。
//我们再次看到20 存储在那里。数组第 60-20 个元素40 也包含20,再找第40-20=20个元素,储存的也是20。
//所以为1个5和3个20
coin=coin-thisCoin;
}
}
public static void main(String[] args) {
int []changes= {1,2,5,10,20};
int money=65;
int [] mincoins=new int[money+1];
int []coinUsed=new int[money+1];
System.out.println(makeChange(changes, money, mincoins,coinUsed));
printCoins(coinUsed, money);
}
}