1.遞歸是什么?
定義:程序調用自身的編程技巧稱為遞歸。
遞歸使用的是選擇結構,對于解決同樣問題的孿生兄弟:迭代,它使用的則是循環結構。
2.遞歸的核心
先看一個例子,表達式1+2+3....+100=?要怎么寫程序來計算?
第一反應是for循環:
int sum =0;
for (int i = 1; i <= 100; i++) {
sum+=i;
}
System.out.println(sum);//5050
更簡潔的寫法
int start =1;
int end = 100;
int sum =(start+end)*end/2;//首項加末項乘以項數除以二
System.out.println(sum);//5050
遞歸的寫法
static int recursion(int n){
if(n==1){//遞歸出口
return 1;
}else{
return n+recursion(n-1);
}
}
遞歸的核心其實就是一個if...else...
一個條件跳出,一個條件繼續。
1.優點:使程序結構更清晰,更簡潔,更容易讓人理解,
2.缺點:使用遞歸調用時,**如果過多的調用容易造成java.lang.StackOverflowError即棧溢出和程序執行過慢。這是一個潛在Bug和影響程序執行效率問題,需要謹慎使用。**對于互聯網這種以速度和效率來維護用戶量,不得以用遞歸時,可以把處理的數據放入緩存,或者直接使用迭代等方式來解決。
3.規律:遞歸要有出口,不然成了死循環。解出遞歸的要點在于求出n-1,求出了n-1才能求解出n,這是為什么呢?
3.例題
3.1.題目:有一對兔子,從出生后第3個月起每個月都生一對兔子, 小兔子長到第四個月后每個月又生一對兔子, 假如兔子都不死,問每個月的兔子總數為多少?
分析,這個題目是著名的斐波那契數列:1,1,2,3,5,8,13,21....=Sn = Sn-1+Sn-2
規律是:從第三個數開始,每個數都是前兩個數的和。
迭代方式
int month =10;
int sum[]= new int[month] ; //初始化月份數組
sum[0] = 1; //第一個月
sum[1] = 1; //第二個月
for(int i=2;i<=month-1;i++){
sum[i] = sum[i-1]+sum[i-2]; //第三個月等于前兩個月之和
}
System.out.println("第"+month+"個月的兔子總數是:"+sum[month-1]);
遞歸方式
static int recursion(int i){
if( i == 1 || i == 2 ){
return 1;
}
else{
return recursion(i-1) + recursion(i-2);//第三項等于后兩項之和
}
}
3.2 題目: 逆序排列字符單詞,輸入:I love java-->java love I
迭代方式
static String reverse(String str){
String[] strs = str.split(" ");
StringBuilder sb = new StringBuilder();
//倒敘遍歷并拼接空格
for (int i = strs.length-1; i >= 0; i--) {
sb.append(strs[i]+" ");
}
return sb.toString();
}
遞歸方式
static String reverse(String s) {
int i = s.indexOf(" ");//搜索第一次出現" "的位置
if (s == null||i == -1) {
return s;
}
return reverse(s.substring(i + 1)) + " " + s.substring(0, i);//每次截取第一個單詞放在最后拼接
}
3.3.題目:使用遞歸來統計字符串String str="hello"的長度,不能使用統計變量(只能用遞歸求解).
static int test1(String str){
if(null==str||str.equals("")){
return 0;
}
String[] strs = str.split("");
StringBuffer sb = new StringBuffer();//每次截取最后一個字母
for(int i = 0; i <= strs.length-2; i++)
{
sb. append(strs[i]);
}
return test1(sb.toString())+1;//每次遞歸調用+1
}
3.4 題目:實現二分查找算法.
二分查找,不斷將數組進行對半分割,每次拿中間元素和goal進行比較(前提是數組元素的排序應該是遞增或者遞減)
public static void main(String[] args) {
int [] arr={1,2,5,6,8,9,12,64,78,90};//有序數組
System.out.println(test(arr,0,arr.length-1,8));//傳入數組和數組長度,最后一個是要查找的值
}
//遞歸方式實現
public static int recursion(int [] arr,int low,int high,int value){
if(low>high){
return -1;
}
int mid=(low+high)/2;//求中間的值
if(value==arr[mid]){//如果相等,則找到該值,直接返回
return mid;
}else if(value<arr[mid]){//如果要找的值在中間值得左邊,則下一次遞歸開始的右指針指向該次中間值-1
return recursion(arr,low,mid-1,value);
}else{////如果要找的值在中間值得右邊,則下一次遞歸開始的左指針指向該次中間值+1
return recursion(arr,mid+1,high,value);
}
}
//循環方式實現
public static int test(int [] arr,int low,int high,int value){
int mid;
while(high>=low){
mid=(low+high)/2;
if(value<arr[mid]){
high=mid-1;
}else if(value>arr[mid]){
low=mid+1;
}else{
return mid;
}
}
return -1;//都沒找到,則返回-1
}
3.5題目:求最大公約數和最小公倍數:
public static void main(String[] args) {
int x = 100;
int y = 18;
System.out.println("最大公約數:"+gcd(x,y));
System.out.println("最小公倍數:"+lcm(x,y));
}
//輾轉相除法實現最大公約數
public static int gcd(int x, int y) {
if (y == 0){
return x;
}else{
return gcd(y, x % y);//x%y時,如果x<y則返回x,例:4%20=4 5%20=5,迭代之后會把小數放在后面,所以不用做交換
}
}
//最小公倍數
public static int lcm(int p,int q){
int pq = p * q;
return pq / gcd(p,q);
}
3.6.題目:楊輝三角
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
分析:楊輝三角最本質的特征是,它的兩條斜邊都是由數字1組成的,而其余的數則是等于它肩上的兩個數之和。
public static void main(String[] args) {
int n = 7;
for (int i = 1; i <= n; i++) {
//雙層for循環是為了打印出三角形
for (int j = 1; j <= n-i; j++) {//每行前面的空格數
System.out.print(" ");
}
for (int j = 1; j <= i; j++) {
System.out.print(num(i,j)+" ");
}
System.out.println();//換行
}
}
public static int num(int x,int y){
if(y==1||y==x){
return 1;
}
return num(x-1,y-1)+num(x-1,y);//每一個數等于肩上兩個數之和
}
3.7.題目:漢諾塔
接下來就到了遞歸的經典案例漢諾塔問題,本文就不對漢諾塔游戲規則進行講解,如果以前沒接觸過漢諾塔,建議先玩玩漢諾塔游戲,總結一下游戲規律。
現在要把X柱上所有圓盤移動到Z
當移動3個圓盤
當移動6個圓盤
所以可以推出,當n個從x柱,經由y柱中轉,移動到z柱(解出n層漢諾塔時),有:
當n=0時,
不用做任何操作
當n>0時,
首先,將n-1個盤子從x借助z移動到y
然后,將1個盤子從x移動到z
最后,將在中間y上的n-1個盤子借助x移動到z
為了解出n層漢諾塔,需要先使用n-1層漢諾塔的解法。
static int t=0;//最少移動次數
public static void main(String[] args) {
hanio(3,"x","y","z");
System.out.println(t);
}
static void hanio(int n ,String src,String mid,String dest){
if(n==1){
System.out.println(src+"-->"+dest);//移動過程
t++;
}else{
hanio(n-1,src,dest,mid);//將n-1個盤子從x借助z移動到y
hanio(1,src,"",dest);//因為中間柱子沒用到,所以可以填""或者填mid,然后將最大的盤子從x直接移動到z
hanio(n-1,mid,src,dest);//將在中間y柱上的n-1個盤子借助x移動到z
}
}