`
liaobinxu
  • 浏览: 43292 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

递归算法

阅读更多
什么是递归?
   递归是一个重要的概念。我们在开发中排序方法以及定义和少秒线性数据结构的主干部分使用递归。递归运用在运筹学模型、博弈论以及图的研究中。

递归运用到什么方面?
   一个计算机文件系统由拥有的文件和其他目录(名为子目录)的根组成。到你需要复制一个文件夹的内容到你的移动硬盘的时候。程序首先会把根目录下面的文件复制到移动硬盘中,然后在继续前进到子目录。针对每一个子目录, 再次重复同样的处理过程: 复制文件并移动到子目录(也就是根下面的子目录)。最终,你会在子目录层次结构中一直向下前进到只存在文件位置,此时复制完成。 文件夹复制是一个递归过程, 这是因为它涉及的一个过程会产生一个具有相同动作的相同过程。在每一个步骤, 磁盘备份只完成复制文件的最终, 递归步骤会到达只复制文件的停止条件。
1下面介绍一个示例:
1.1求一个非负整数n的阶乘?
阶乘的公式:n!=n * n-1* n-2 * ...*2*1
这个迭代过程其实可以使用循环
	public static int factorial(int n) {
		if(n<=0)
			throw new IllegalArgumentException(" Recursion: factorial must be postive");
		int factValue = 1;
		while (n >= 1) {
			factValue *= n;
			n--;
		}
		return factValue;
	}

1.2我们使用递归方法来实现阶乘
	public static int factorial2(int n){
		if(n<=0)
			throw new IllegalArgumentException(" Recursion: factorial must be postive");
		if(n==0)
			return 1;
		return n* factorial2(n-1);
	}

1.3上面两个方法的测试用例
	/**
	 * 测试: Recursion的factorial()方法
	 */
	public void factroial(){
		int n=3;
		System.out.println(Recursion.factorial(n));
		
	}
	/**
	 * 测试: Recursion的factorial()方法
	 */
	public void factroialWithRecursion(){
		int n=3;
		System.out.println(Recursion.factorialWithRecursion(n));
		
	}

2.递归算法的描述或实现
2.1递归算法的要数:
(1)一个或多个能够确定实参直接求值的停止条件(stopping condition)
(2)一个或多个递归步骤(recursive step),在递归步骤中,要计算递归算法的当前值,需要重复调用具有实参的方法, 这些实参最终达到某个停止条件。
引用
递归算法中存在递归步骤和递归条件

2.2递归的实现
设计递归算法时,必须小心区分停止条件和递归步骤。使用if-else语法来实现这个区别
引用
使用if-else选择语句来标识停止条件(if部分)和递归步骤(else部分)

2.3递归的工作方式
现在我们跟踪factorial()方法的执行:
将factorial(n)视作一台为n-Fact的机器,它通过执行乘法n*(n-1)!来计算n!的值。为了操作这台机器,它必须与由后向前传递信息的其他一些列n-Fact机互联。 0-Fact是一个例外,它可以独立工作, 并且不需要另外一个台机器的帮组就可以返回一个结果1.
如图所示

步骤1:4-Fact启动3-Fact机, 等待来自3-Fact机的返回值3!:计算4*3!=24并返回给main
步骤2:3-Fact启动2-Fact机, 等待来自2-Fact机的返回值2!:计算3*2!=6并返回给4-Fact
步骤3:2-Fact启动1-Fact机, 等待来自1-Fact机的返回值1!:计算2*1!=2并返回给3-Fact
步骤4:1-Fact启动0-Fact机, 等待来自0-Fact机的返回值0!:计算1*1!=1并返回给2-Fact
步骤5:0-Fact确定0!=1 将1返回给1-Fact
3.递归的应用
递归中最强大是允许程序人员能够解决难于采用迭代处理进行设计和实现的问题。下面的示例是汉诺塔(Hanoi)问题。
3.1Hanoi塔
问题:Hanoi塔包括一堆尺寸不等的圆盘和三根分别为A、B、C的针, 起始设置是将n个圆盘放在A上。Hanoi的任务就是把针A上的圆盘移动C上,一次只能移动一个圆盘,并且要以相同顺序重新构建n个圆盘。在移动中, 尺寸大的圆盘不允许放在尺寸小的圆盘上。
分析:
设三根针分别为initNeedle,tempNeedle,endNeedle.
引用
阶段1, 使用tempNeedle作为临时存储器, 将n-1个圆盘从tempNeedle移动到endNeedle(递归步骤)

hanoi(n-1, tempNeedle, endNeedle, initNeedle);

引用
阶段2,将initNeedle上的一个圆盘移动到endNeedle(停止条件)。

System.out.println("move "+initNeedle+" to "+ endNeedle);

引用
阶段3,在阶段1中,使用initNeedle作为临时存储器,将n-1个圆盘从tempNeedle移动到endNeedle(递归步骤)

hanoi(n-1,tempNeedle,endNeedle,initNeedle)

递归步骤持续将堆叠得越来越少的圆盘从一根针移动到另一根针, 直到只剩下一根圆盘,此时递归步骤到达停止条件,解决的问题的方法就是简单地将这个圆盘移动到正确的针上。
下面是hanoi塔的实现:
	public static void hanoi(int n,String initNeedle, String endNeedle,String tempNeedle){
		if(n==1){
			System.out.println("Move " +initNeedle+" to "+endNeedle);
		}else {
			hanoi(n-1, initNeedle, tempNeedle, endNeedle);
			System.out.println("Move " +initNeedle+" to "+endNeedle);
			hanoi(n-1, tempNeedle, endNeedle, initNeedle);
		}
	}

* hanoi塔算法
* @param n 从大到小
* @param initNeedel  针A
* @param endNeedel   针B
* @param tempNeedle  针C
运行结果
引用

n=3,initNeeelde = A, endneedle=C, tempNeedle=B
Move A to C       //阶段一
Move A to B
Move C to B
Move A to C       //阶段二
Move B to A       //阶段三
Move B to C
Move A to C       

4递归的评价
递归的方法的价值取决与问题。对于hannoi塔问题递归就是最好的选择,但是正对fibonacci数列就不一定了
4.1fibonacci数列
fib(n)= 0(n=0)或=1(n=1)或=fib(n-1)+fib(n-2)(n>=2)
使用递归方法实现fibonacci数列
引用
fib()方法是fibonacci()的简写

	public static int fibonacci(int n){
		if (n<=1) {
			return n;
		}else {
			return fibonacci(n-1)+fibonacci(n-2);
		}
	}

我们以fibonacci(5)为例

fib()产生多次迭代,造成巨大的人冗余。fib(5)的值需要两次fib(3)和五次fib(1)。调用次数numCall(n)=2*fib(n+1)-1;
对于5来说numbCall(5)=2*fib(6)-1=2*8-1=15
对于35来说,numCall(35)=2*fib(36)-1=2*149303352-1=29,860,703
引用
fib(n)产生巨大的冗余调用

4.2使用迭代的方式实现fibonacci数列fibIter()
	public static int fibInter(int n){
		int oneback=1,twoback=0,current=0;
		for (int i = 2; i <= n; i++) {
			current=oneback+twoback;
			twoback=oneback;
			oneback=current;
		}
		return current;
	}

迭代算法fibIter()是一个线性算法

4.3对比Fibonacci数列的两种方法
测试用例
	public void fibonacci(){
		Timing time=new Timing();
		time.start();
		Recursion.fibonacci(35);
		System.out.println(time.stop());
		
		time.start();
		Recursion.fibInter(35);
		time.stop();
		System.out.println(time.stop());
	}

引用
输出
0.141
0.0

5递归的使用方法
outline:



package ds.util;

/**
 * 通过将一个问题分割为使用相同的算法来解决若干子问题, 某个算法可以解决这个问题, 此时就会应用术语“递归(recursive)”。对于阶乘算法来说,
 * 分割过程首先计算(n-1)!来解决确定的n!的值, 然后以同样方式继续进行计算。 在递归的算法执行期间, 分割过程不能无限地继续下去。
 * 分割过程必须终止于一个或多个能够直接解决的简单。我们将这些简单问题算法称为停止条件(Stopping condition),
 * 其原因在于它们不需要进一步分割就能被解决。
 * 
 * 递归算法设计要素: (1)一个或多个能够确定实参直接求值的停止条件(stopping condition)。 (2)一个或多个递归步骤(recursive
 * step),在递归步骤中,要计算递归方法的当前值, 需要重复调用具有实参的方法, 这些实参最终会达成某个停止条件
 * 
 * 递归算法的实现: 设计递归算法时,必须小心区分停止条件和递归步骤。 编程人员使用if-else语句来实现这个区别。其中if部分处理停止条件,
 * 而else部分处理递归步骤。
 * 
 * @author Administrator
 * 
 */
public class Recursion {

	/**
	 * 求一个整整数的阶乘,使用循环的方式,这也是我们最容易接受的方式
	 * 
	 * @param n
	 * @return
	 */
	public static int factorial(int n) {
		if (n <= 0)
			throw new IllegalArgumentException(
					" Recursion: factorial must be postive");
		int factValue = 1;
		while (n >= 1) {
			factValue *= n;
			n--;
		}
		return factValue;
	}

	/**
	 * 使用递归来求n的阶乘 n*(n-1)*(...)*2*1
	 * 
	 * @param n
	 * @return
	 */
	public static int factorialWithRecursion(int n) {
		if (n <= 0)
			throw new IllegalArgumentException(
					" Recursion: factorial must be postive");
		if (n == 0)
			return 1;
		return n * factorialWithRecursion(n - 1);
	}

	/**
	 * hanoi塔算法
	 * 
	 * @param n
	 *            从大到小
	 * @param initNeedel
	 *            针A
	 * @param endNeedel
	 *            针B
	 * @param tempNeedle
	 *            针C
	 */
	public static void hanoi(int n, String initNeedle, String endNeedle,
			String tempNeedle) {
		if (n == 1) {
			System.out.println("Move " + initNeedle + " to " + endNeedle);
		} else {
			hanoi(n - 1, initNeedle, tempNeedle, endNeedle);
			System.out.println("Move " + initNeedle + " to " + endNeedle);
			hanoi(n - 1, tempNeedle, endNeedle, initNeedle);
		}
	}

	/**
	 * fibonacci数列:fib(n)=0(n=0)或1(n=1)或fib(n-1)+fib(n-2)(n>=2)
	 * 
	 * @param n
	 * @return
	 */
	public static int fibonacci(int n) {
		if (n <= 1) {
			return n;
		} else {
			return fibonacci(n - 1) + fibonacci(n - 2);
		}
	}

	/**
	 * 使用迭代来实现fabionacci数组的算法
	 * 
	 * @param n
	 * @return
	 */
	public static int fibInter(int n) {
		int oneback = 1, twoback = 0, current = 0;
		for (int i = 2; i <= n; i++) {
			current = oneback + twoback;
			twoback = oneback;
			oneback = current;
		}
		return current;
	}

	/**
	 * 使用递归求n,n-1,...,2,1的和
	 * 
	 * @param n
	 * @return
	 */
	public static int sumToN(int n) {
		if (n <= 0)
			throw new IllegalArgumentException(
					" Recursion: factorial must be postive");
		if (n == 1)
			return 1;
		return sumToN(n - 1) + n;
	}

	/**
	 * isPal()方法判断字符串str是否为同一个简单的回文.简单的回是一个完全由字符"a"-"z"组成的、并且正读反读都是相同的串
	 * 
	 * @param str
	 * @param startIndex
	 * @param endIndex
	 * @return
	 */
	public static boolean isPal(String str, int startIndex, int endIndex) {
		if (startIndex >= endIndex)
			return true;
		if (str.charAt(startIndex) != str.charAt(endIndex))
			return false;
		return isPal(str, ++startIndex, --endIndex);
	}

	/**
	 * 递归方法baseString()提供以2到16之间的任何值为基数的非负数值为基数的非负数表示方法, 该方法接受一个十进制数n和一个基数b(其中2<=b<=16),并且返回一个给出对应的表示法串。
	 * 
	 * @param n
	 * @param b
	 * @return
	 */
	public static String baseString(int n, int b) {
		String str = "", digitChar = "0123456789abcef";
		if (n == 0)// 停止条件
			return "";
		else {
			str = baseString(n / b, b);// 利用n/b作为减小条件
			return str + digitChar.charAt(n % b);// 递归步骤
		}
	}

	/**
	 * 上面的方法实现十进制从2到16之间的任何值为基数进制转换,现在要实现一个十进制数转换一个指定长度length的二进制字符串
	 * 
	 * @param n
	 * @param length
	 * @return
	 */
	public static String base2String(int n, int length) {
		if (n > (Math.log(length) / Math.log(2)))
			throw new IllegalArgumentException(
					" n must be litter than log(length)");
		if (n == 0) {// 停止条件1
			if (length > 0)// 停止条件2
				return base2String(0, length - 1) + "0";// 递归步骤2
			return "";
		} else
			return base2String(n / 2, length - 1) + (n % 2);// 递归步骤1
	}

	/**
	 * 在Arrays和GenericUtil里实现了的二分查询binSearch(),现在我们要使用递归的思想来实现它们:
	 * 该方法接受一个数组arr(用索引first和last来描述一个索引)和一个target值,并且扫描查找匹配的列表. 这个方法返回匹配的索引,
	 * 或者在没有匹配的情况下返回-1. 使用分治侧率实现一个递归形式的二分查询搜索算法.
	 * 
	 * @param <T>
	 *            泛型类型T
	 * @param arr
	 *            反省类型数组
	 * @param first
	 *            数组arr的头索引
	 * @param last
	 *            数组arr的尾索引
	 * @param target
	 *            搜索的目标值
	 * @return
	 */
	public static <T extends Comparable<? super T>> int binSearch(T[] arr,
			int first, int last, T target) {
		int mid = (first + last) / 2;
		if (first < last) { // 运行条件1
			if (arr[mid].compareTo(target) > 0) { // 停止条件1
				return binSearch(arr, first, mid, target); // 递归步骤1
			} else if (arr[mid].compareTo(target) < 0) { // 停止条件2
				return binSearch(arr, mid + 1, last, target); // 递归步骤2
			} else {
				return mid; // 返回步骤1
			}
		} else if (first == last) { // 运行条件2
			if (arr[mid].compareTo(target) == 0) { // 停止条件1
				return mid; // 返回步骤1
			} else {
				return -1; // 返回步骤2
			}
		} else { // 运行条件3
			return -1; // 返回步骤2
		}
	}

	/**
	 * 方法numToNames()方法将一个正数转换一个串,例如n=372,那么numberToNames()方法返回串“three seven
	 * two”。
	 * 
	 * @return
	 */
	public static String numToNames(int n) {
		if (n < 10) {
			return numToName(n);
		} else {
			return numToNames(n / 10) + " " + numToName(n % 10);
		}
	}
	

	private static String numToName(int n) {
		String value = "";
		switch (n) {
		case 0:
			value = "zero";
			break;
		case 1:
			value = "one";
			break;
		case 2:
			value = "two";
			break;
		case 3:
			value = "three";
			break;
		case 4:
			value = "four";
			break;
		case 5:
			value = "five";
			break;
		case 6:
			value = "six";
			break;
		case 7:
			value = "seven";
			break;
		case 8:
			value = "eight";
			break;
		case 9:
			value = "nine";
			break;
		default:
			break;
		}
		return value;
	}
}


测试用例


package test.ds.util;

import ds.time.Timing;
import ds.util.GenericUtil;
import ds.util.Recursion;
import junit.framework.TestCase;

public class RecursionTestCase extends TestCase {

	/**
	 * 测试: Recursion的factorial()方法
	 */
	public void factroial() {
		int n = 3;
		System.out.println(Recursion.factorial(n));

	}

	/**
	 * 测试: Recursion的factorial()方法
	 */
	public void factroialWithRecursion() {
		int n = 3;
		System.out.println(Recursion.factorialWithRecursion(n));

	}

	/**
	 * 测试: Recursion的isPal()方法
	 */
	public void isPal() {
		String str = "amanaplanacanalpanama";
		System.out.println(Recursion.isPal(str, 0, str.length() - 1));//判断串str是否是回字
		
		String str1 = "gohangasalamiimalasagnahog";
		System.out.println(Recursion.isPal(str1, 0, str1.length() - 1));
	}
	/**
	 *
	 * 测试: Recursion的baseString()方法
	 */
	public void baseString(){
		System.out.println(Recursion.baseString(95, 8));//十进制转n机制
		System.out.println(Recursion.baseString(450, 16));
	}
	
	/**
	 *
	 * 测试: Recursion的base2String()方法
	 */
	public void base2String(){
		System.out.println(Recursion.base2String(11,5));
	}
	
	/**
	 * 测试: 测试Rescursion的binSearch()方法 
	 */
	public void binSearch(){
		String[] arr=ArraysUtil.getRandomStrings(10,5);//产生大小为10长度为5的字符串
		GenericUtil.selectionSort(arr);//保证arr必须是按升序排列
		
		ArraysUtil.printArrays(arr);

		System.out.println(Recursion.binSearch(arr, 6,9, arr[5]));//二分搜索
	}
	/**
	 * 测试hanoi()塔
	 */
	public void hanoi(){
		System.out.println("n=3,initNeeelde = A, endneedle=C, tempNeedle=B");
		Recursion.hanoi(3,"A","C","B");
	}
	/**
	 * 测试fibonacci数列
	 */
	public void fibonacci(){
		Timing time=new Timing();
		time.start();
		Recursion.fibonacci(35);
		System.out.println(time.stop());
		
		time.start();
		Recursion.fibInter(35);
		time.stop();
		System.out.println(time.stop());
	}
	public void numToNames(){
		System.out.println(Recursion.numToNames(372));
	}
}

  • 大小: 8.8 KB
  • 大小: 19.1 KB
  • 大小: 23 KB
  • 大小: 15.9 KB
7
4
分享到:
评论
2 楼 liaobinxu 2009-10-18  
shaopei3344 写道
递归不是会引起栈溢出吗。
说说递归在现实中的例子

不会溢栈吧, 递归从面向过程语言C,还是在面向对象语言java,都在广泛使用,只有在一些算法的时间复杂度不同而已,
比如递归解决hanoi比较好, 在斐波那契数列就不好,因为存在重复调用的问题,这里有一个关于斐波那契数列递归算法Fibonacci数列的一种经典递归实现
1 楼 shaopei3344 2009-10-14  
递归不是会引起栈溢出吗。
说说递归在现实中的例子

相关推荐

    VC对磁盘文件遍历搜索的递归算法和非递归算法

    在VC++(Visual C++)环境中,有多种方法可以实现这一目标,其中最常见的是递归算法和非递归算法。这两种方法各有优缺点,适用于不同的场景。 **递归算法**: 递归算法是一种基于函数自身调用解决问题的方法。在...

    5!递归算法和非递归算法

    递归算法和非递归算法 在计算机科学与编程领域中,递归算法与非递归算法是两种非常重要的计算方法。本文将详细介绍这两种算法的特点、应用场景以及如何实现它们,特别针对初学者及面试准备者。 #### 递归算法 ...

    acm递归算法总结竞赛

    在ACM(国际大学生程序设计竞赛)中,递归算法是一种常见的解决问题的方法,它通过函数自身调用自身来实现问题的解决。递归的核心在于找到基本情况(base case),即可以直接求解的问题,以及每次递归调用时问题规模...

    .net 递归算法 .net 递归算法.net 递归算法

    在.NET编程环境中,递归算法是一种强大的工具,它允许函数或方法调用自身来解决复杂问题。递归的核心思想是将大问题分解为相同或相似的小问题,直到问题变得足够简单,可以直接得出答案。这种解决问题的方式在数据...

    abap简单递归算法

    ### ABAP简单递归算法解析 #### 一、引言 ABAP(Advanced Business Application Programming)是一种用于SAP系统的编程语言。它不仅支持传统的过程化编程,还支持面向对象编程和Web开发。本文将深入探讨一个ABAP中...

    递归算法与循环算法的分析

    递归算法与循环算法的分析 递归算法是指在程序设计中,在调用一个函数的过程中又出现直接或间接调用其函数本身的现象。递归算法的优点是编写容易,结构清晰,可读性强,但是其缺点是计算速度慢,时间花费较长,效率...

    汉诺塔问题的非递归算法

    非递归算法解决了这个问题,提供了一个不同于传统递归解决方案的方法。对于n个圆盘的情况,非递归算法的核心在于通过交替执行两个步骤来移动圆盘: 1. **步骤1**:根据圆盘数量的奇偶性,按顺时针方向将圆盘1从当前...

    Hanoi塔问题的一种非递归算法

    ### Hanoi塔问题的一种非递归算法:深入解析与实现 #### 一、引言 Hanoi塔问题作为计算机科学领域内经典的递归问题之一,因其简洁性和挑战性而广受关注。通常,Hanoi塔问题的解决方案多采用递归算法,尽管其逻辑...

    递归算法ppt让你快速上手

    "递归算法ppt让你快速上手" 递归算法是计算机科学中的一种重要算法思想,它可以解决许多复杂的问题。下面是递归算法的知识点总结: 1. 递归的定义:若一个对象部分地包含它自己,或用它自己给自己定义,则称这个...

    合并排序递归和非递归算法

    3. **可读性和实现难度**:递归算法通常代码简洁,易于理解,而非递归算法可能需要更复杂的逻辑来模拟递归行为。 总的来说,选择哪种实现方式取决于具体场景,如内存限制、性能要求以及代码的可读性等。递归版本的...

    中序遍历二叉树非递归算法

    在IT领域,尤其是在数据结构与算法的学习中,中序遍历二叉树的非递归算法是一个关键且实用的知识点。通常,我们首先通过递归来实现二叉树的遍历,但递归方法可能因深度过大导致栈溢出,因此掌握非递归版本的遍历算法...

    程序设计中递归算法

    ### 递归算法在程序设计中的应用 #### 一、递归的概念与本质 递归是一种重要的编程思想,在计算机科学和数学领域都有广泛的应用。它指的是一个过程或函数直接或间接地调用自身来解决问题的方法。递归的核心在于将...

    快速排序算法设计与分析总结 二叉树与树的转换前序、后序的递归、非递归算法,层次序的非递归算法的实现

    快速排序算法设计与分析总结 二叉树与树的转换前序、后序的递归、非递归算法,层次序的非递归算法的实现 二叉树与树的转换前序、后序的递归、非递归算法,层次序的非递归算法的实现,应包含建树的实现 实现树与...

    折半查找的递归算法

    ### 折半查找的递归算法 #### 一、引言 折半查找(也称为二分查找)是一种高效的查找算法,适用于有序数组。通过不断将查找区间对半分割,可以快速定位目标值的位置,时间复杂度为O(log n),其中n是数组长度。本文...

    递归算法专题ppt

    ### 递归算法专题知识点详解 #### 一、递归算法原理 递归算法是一种将问题分解成子问题的方法,其中子问题与原问题性质相同但规模较小。递归算法的关键在于识别出能够通过递归解决的问题,并找到递归的基本情况...

    利用递归算法求阶乘(VB6.0源代码)利用递归算法求阶乘

    在这个主题中,我们将深入探讨如何使用递归算法在VB6.0(Visual Basic 6.0)中计算阶乘。VB6.0是Microsoft开发的一款经典可视化编程环境,用于创建Windows应用程序。 阶乘是一个数学概念,表示一个正整数n的所有...

    递归算法的详解,各种常见递归算法

    递归算法是一种强大的编程技术,它通过函数或过程在解决问题时调用自身来解决更小规模的相同问题。递归的基本概念在于一个函数在定义中包含对自身的引用,或者问题的解决方案依赖于较小规模问题的解决方案。在程序...

    程序2_delphi_milemut_递归算法_

    在编程领域,递归算法是一种强大的工具,尤其在处理树形结构或遍历目录时显得尤为重要。本项目“程序2_delphi_milemut_递归算法”是使用Delphi编程语言实现的一个应用,旨在通过递归方法列出指定目录“C:\Windows\...

    Python基于递归算法实现的走迷宫问题

    ### Python基于递归算法实现的走迷宫问题详解 #### 一、递归算法简介 在探讨如何使用Python实现走迷宫问题之前,我们先来了解一下递归算法的基本概念及其应用场景。 **递归**是一种非常重要的算法思想,在计算机...

Global site tag (gtag.js) - Google Analytics