递归算法
在函数或子过程的内部,直接或者间接地调用自己的算法。
特点:
- (1) 递归就是在过程或函数里调用自身。
- (2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。
- (3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。
- (4) 在 递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成 栈溢出等。所以一般不提倡用递归算法设计程序。
递归算法一般用于解决三类问题:
- (1)数据的定义是按递归定义的。(Fibonacci函数)
- (2)问题解法按递归算法实现。(回溯)
- (3)数据的结构形式是按递归定义的。(树的遍历,图的搜索)
递归设计经验:
- 找重复(子问题)
- 找重复中的变化量–>参数
- 找参数变化趋势 --> 设计出口
解递归的常见思路:
- 切蛋糕思维,如求阶乘f(n) = n * f(n-1),数组就和,翻转字符串。
- 化不开的,看有没有递推公式?有没有等价转化? 如:斐波那契数列f(n) = f(n-1) + f(n-2),求最大公约数:f(m,n) = f(n, m%n)。
递归算法C语言实例
利用递归实现1到100以内的求和;
#include<stdio.h>
int main()
{
int Sum(int n);
printf("sum=%d\n",Sum(100));
return 0;
}
int Sum(int n)
{
int sum=0;
if(n==0)
return 0;
else
return sum=n+Sum(n-1);
}
利用递归求阶乘:
#include<stdio.h>
int main()
{
int Fac(int n);
printf("f=%d\n",Fac(5));
return 0;
}
int Fac(int n)
{
int f=0;
if(n==1)
return f=1;
else
return f=n*Fac(n-1);
}
利用递归求数组中最大数:
#include<stdio.h>
int main()
{
int Max(int a[],int n);
int a[5]={4,75,23,300,53};
printf("最大数是%d\n",Max(a,5));
return 0;
}
int Max(int a[],int n)
{
if(n==1)
return a[0];
else
{
if(a[n-1]>Max(a,n-1))
return a[n-1];
else
return Max(a,n-1);
}
}
递归算法java语言实例:
斐波那契序列:
斐波那契数列问题,等价于两个子问题:
- 求前一项
- 求前两项
- 两项求和
public class 斐波那契序列 {
public static void main(String[] args) {
for(int i = 1; i < 10; i++) {
System.out.print(fibonacci(i) + " ");
}
}
public static int fibonacci(int n) {
if(n == 1 || n == 2)
return 1;
return fibonacci(n-1) + fibonacci(n-2);
}
}
运行结果:
1 1 2 3 5 8 13 21 34
最大公约数:
public class 最大公约数 {
public static void main(String[] args) {
System.out.println(gcd(24,30));
}
public static int gcd(int m, int n) {
if(n == 0) return m;
return gcd(n, m%n);
}
}
程序运行结果:
6
插入排序改递归:
public class 递归_插入排序 {
public static void main(String[] args) {
int[] arr = {9, 18, 2, 3, 6 ,4, 0};
insertSort(arr, arr.length-1);
for(int i = 0; i < arr.length; i++)
System.out.print(arr[i] + " ");
}
// 对数组0-倒数第一个排序
// 等价于:
// 对数组0-倒数第二个元素排序,
// 然后把最后一个元素插入到这个有序的部分中
public static void insertSort(int[] arr, int k) {
if(k == 0)
return;
//对前k-1个元素排序
insertSort(arr, k-1);
//把位置k的元素插入到前面的部分
int x = arr[k];
int index = k-1;
while(index >= 0 && x < arr[index]) {
arr[index+1] = arr[index];
index--;
}
arr[index+1] = x;
}
}
程序运行结果:
0 2 3 4 6 9 18
汉诺塔问题:
将1-N从A移动到B,C作为辅助。等价于:
- 1~N-1移动到C,A作为源,B作为辅助
- 把N从A移动到B
- 把1~N-1 从C移动到B,A为辅助
public class 汉诺塔 {
public static void main(String[] args) {
printHanoiTower(3, "A", "B", "C");
}
/**
* 将1-N从A移动到B,C作为辅助。等价于:
* 1. 1~N-1移动到C,A作为源,B作为辅助
* 2. 把N从A移动到B
* 3. 把1~N-1 从C移动到B,A为辅助
* @param N N个盘子
* @param from 放盘子的初始柱子
* @param to 目标柱子
* @param help 辅助柱子
*/
public static void printHanoiTower(int N, String from, String to, String help) {
if(N == 1) {
System.out.println(N + ": " + from + "-->" + to);
return;
}
// 先把前N-1个盘子挪到辅助空间上去
printHanoiTower(N-1, from, help, to);
// N可以顺利到达目标
System.out.println(N + ": " + from + "-->" + to);
// 让N-1个盘子从辅助空间回到"原位置上"
printHanoiTower(N-1, help, to, from);
}
}
程序运行结果:
1: A-->B
2: A-->C
1: B-->C
3: A-->B
1: C-->A
二分查找递归解法:
全范围二分查找,等价于三个子问题:
- 左边找(递归)
- 中间比
- 右边找(递归)
注意:左边查找和右边查找只选其一
public class 递归_二分查找 {
public static void main(String[] args) {
int[] arr = {1, 3, 9, 13, 19, 23, 25};
System.out.println(binarySearch(arr, 0, arr.length-1, 13));
}
public static int binarySearch(int[] arr, int low, int high, int key){
if(low > high)
return -1;
int mid = low + ((high-low)>>1);//(low+high)>>1
int midVal = arr[mid];
if(midVal < key) //向左找
return binarySearch(arr, mid + 1, high, key);
else if(midVal > key) //向右找
return binarySearch(arr, low, mid - 1, key);
else
return mid; //key found
}
}
程序运行结果:
3
递归算法的性能分析:
斐波那契数列递归实现改进:
通过记录每次f(n)的值来防止大量重复计算,大大增加了性能。
public static int fib(int n, int[] tmp) {
if(tmp[n] != 0) {
return tmp[n];
}
if(n == 1 || n == 2)
return 1;
tmp[n] = fib(n-1, tmp) + fib(n-2, tmp);
return fib(n-1, tmp) + fib(n-2, tmp);
}
改进前和改进后性能对比:
public class 斐波那契序列 {
static int count = 0;
static int count1 = 0;
public static void main(String[] args) {
for(int i = 1; i < 20; i++) {
System.out.print(fibonacci(i) + " ");
}
System.out.println();
System.out.println("运算次数:" + count);
int[] tmp = new int[20];
for(int i = 1; i < 20; i++) {
System.out.print(fib(i, tmp) + " ");
}
System.out.println();
System.out.println("运算次数:" + count1);
}
public static int fibonacci(int n) {
if(n == 1 || n == 2)
return 1;
count++;
//System.out.print("<" + fibonacci(n-1) + fibonacci(n-2)+ ">") ;
return fibonacci(n-1) + fibonacci(n-2);
}
public static int fib(int n, int[] tmp) {
if(tmp[n] != 0) {
return tmp[n];
}
if(n == 1 || n == 2)
return 1;
tmp[n] = fib(n-1, tmp) + fib(n-2, tmp);
//System.out.print("<" + tmp[n] + ">");
count1++;
return fib(n-1, tmp) + fib(n-2, tmp);
}
}
程序运行结果:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
运算次数:10926
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
运算次数:17
小白上楼梯:
小白正在上楼梯,楼梯有n阶台阶,小白一次可以上1阶,2阶或者3阶,实现一个方法,计算小白有多少种走完楼梯的方式。
package chapter2;
public class 小白上楼梯 {
public static void main(String[] args) {
long now = System.currentTimeMillis();
for(int i = 1; i < 30; i++) {
int[] tmp = new int[1000];
System.out.print(f(i, tmp)+ " ");
}
System.out.println();
long end = System.currentTimeMillis();
System.out.println(end-now + "ms");
long now1 = System.currentTimeMillis();
for(int i = 1; i < 30; i++) {
System.out.print(f1(i) + " ");
}
System.out.println();
long end1= System.currentTimeMillis();
System.out.println(end1-now1 + "ms");
}
public static int f(int n, int[] tmp) {
if(tmp[n] != 0) return tmp[n];
if(n == 1) return 1;
if(n == 2) return 2;
if(n == 3) return 4;
tmp[n] = f(n-1, tmp) + f(n-2, tmp) + f(n-3, tmp);
return f(n-1, tmp) + f(n-2, tmp) + f(n-3, tmp);
}
public static int f1(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
if(n == 3) return 4;
return f1(n-1) + f1(n-2) + f1(n-3);
}
}
程序运行结果:
1 2 4 7 13 24 44 81 149 274 504 927 1705 3136 5768 10609 19513 35890 66012 121415 223317 410744 755476 1389537 2555757 4700770 8646064 15902591 29249425
1ms
1 2 4 7 13 24 44 81 149 274 504 927 1705 3136 5768 10609 19513 35890 66012 121415 223317 410744 755476 1389537 2555757 4700770 8646064 15902591 29249425
98ms