这篇关于Java递归的深度教程通过实例、类型和相关概念解释了什么是递归。 它还包括递归与迭代:

在早期的Java教程中,我们已经看到了迭代的方法,即我们声明一个循环,然后通过一次一个元素的迭代方式遍历一个数据结构。

我们也看到了条件流,我们再次保留一个循环变量并重复一段代码,直到循环变量满足条件。 当涉及到函数调用时,我们也探索了函数调用的迭代方法。

在本教程中,我们将讨论一种不同的编程方法,即递归方法。

什么是Java中的递归?

递归是一个函数或方法反复调用自己的过程。 这个被直接或间接反复调用的函数被称为 "递归函数"。

我们将看到各种例子来理解递归。 现在让我们看看递归的语法。

递归语法

任何实现递归的方法都有两个基本部分:

  1. 可以自我调用的方法调用,即递归。
  2. 一个将停止递归的前提条件。

请注意,任何递归方法都需要一个前提条件,因为如果我们不打破递归,那么它就会一直无限地运行下去,导致堆栈溢出。

递归的一般语法如下:

 methodName (T parameters...) { if (precondition == true) //precondition or base condition { return result; } return methodName (T parameters...); //recursive call } 

请注意,前提条件也被称为基础条件。 我们将在下一节详细讨论基础条件。

了解Java中的递归

在本节中,我们将尝试了解递归过程,看看它是如何发生的。 我们将学习基础条件、堆栈溢出,并看看如何用递归解决一个特定的问题以及其他此类细节。

递归基本条件

在编写递归程序时,我们应该首先提供基础案例的解决方案。 然后我们用小问题来表达大问题。

作为一个 例子、 我们可以用一个经典的问题来计算一个数的阶乘。 给定一个数n,我们必须找到一个n的阶乘,用n表示!

现在让我们来实现这个程序,用递归法计算n个阶乘(n!)。

 public class Main{ static int fact(int n) { if (n == 1) // base condition return 1; else return n*fact(n-1); } public static void main(String[] args) { int result = fact(10); System.out.println("10! = " + result); } } 

输出

在这个程序中,我们可以看到,条件(n<=1)是基础条件,当达到这个条件时,函数返回1。 函数的else部分是递归调用。 但每次递归方法被调用时,n都会被递减1。

因此,我们可以得出结论,最终n的值将变成1或小于1,在这一点上,方法将返回值1。 注意,n的值可以是任何东西,只要它满足基本条件。

使用递归解决问题

使用递归的基本思想是用较小的问题来表达较大的问题。 同时,我们需要增加一个或多个基础条件,这样我们才能从递归中走出来。

这在上面的阶乘例子中已经展示过了。 在这个程序中,我们用更小的数值来表达n阶乘(n!),并且有一个基础条件(n <=1),所以当n达到1时,我们可以退出递归方法。

递归中的堆栈溢出错误

我们知道,当任何方法或函数被调用时,函数的状态被存储在堆栈中,并在函数返回时被检索出来。 堆栈也被用于递归方法。

但在递归的情况下,如果我们没有定义基础条件,或者基础条件在某种程度上没有达到或执行,就可能出现问题。 如果出现这种情况,就可能出现堆栈溢出。

让我们考虑下面这个阶乘符号的例子。

这里我们给出了一个错误的基础条件,n==100。

 public class Main { static int fact(int n) { if (n == 100) // base condition resulting in stack overflow return 1; else return n*fact(n-1); } public static void main(String[] args) { int result = fact(10); System.out.println(" 10! = " + result); } } 

因此,当n> 100时,该方法将返回1,但递归不会停止。 n的值将无限期地递减,因为没有其他条件来停止它。 这将持续到堆栈溢出。

另一种情况是当n <100的值时,在这种情况下,该方法也不会执行基本条件,并导致堆栈溢出。

Java中的递归实例

在本节中,我们将使用递归来实现以下例子。

#1)使用递归的斐波那契数列

斐波那契数列由以下公式给出、

1,1,2,3,5,8,13,21,34,55,…

上述序列表明,当前元素是前两个元素的总和。 另外,斐波那契数列的第一个元素是1。

所以一般来说,如果n是当前数,那么它由(n-1)和(n-2)之和给出。 由于当前元素是用以前的元素表示的,我们可以用递归来表达这个问题。

以下是实现斐波那契数列的程序:

 public class Main { //method to calculate fibonacci series static int fibonacci(int n) { if (n <= 1) { return n; } return fibonacci(n-1) + fibonacci(n-2); } public static void main(String[] args) { int number = 10; //print first 10 numbers of fibonacci series System.out.println ("Fibonacci Series: First 10 numbers:"); for (int i = 1; i <= number; i++) { System.out.print( fibonacci(i) + " " );} } } 

输出

##2)使用递归检查一个数字是否是帕林多姆(Palindrome)。

回文是指当我们从左到右或从右到左阅读时,一个序列是相等的。

给定一个数字121,我们可以看到,当我们从左到右和从右到左读它时,它是相等的。 因此数字121是一个回文。

让我们来看看另一个数字,1242,当我们从左到右读它时,它是1242,当从右到左读时,它是2421。 因此这不是一个回文。

我们通过颠倒数字的位数来实现回文程序,并递归地将给定的数字与它的反转表示法进行比较。

下面的程序实现了检查回文的程序。

 import java.io.*; import java.util.*; public class Main { // check if num contains only one digit public static int oneDigit(int num) { if ((num>= 0) && (num <10)) return 1; else return 0; } //palindrome utility function public static int isPalindrome_util (int num, int revNum) throws Exception { // base condition; return if num=0 if (num == 0) { return revNum; } else { //call实用函数递归 revNum = isPalindrome_util(num / 10, revNum); } // 检查num和revNum的第一个数字是否相等 if (num % 10 == revNum % 10) { // 如果是,revNum将随num移动 return revNum / 10; } else { // exit throw new Exception(); } } // 使用palindrome实用函数检查给定数字是否是palindrome的方法 public static int isPalindrome(int num) throwsException { if (num <0) num = (-num); int revNum = (num); return isPalindrome_util(num, revNum); } public static void main(String args[] ) { int n = 1242; try { isPalindrome(n); System.out.println("是,给定数字 " + n + " 是一个回文。"); } catch (Exception e) { System.out.println("不,给定数字 " + n + " 不是一个回文。") ; } n = 1221; try { isPalindrome(n) ;System.out.println("是的,给定的数字 " + n + " 是一个回文。"); } catch (Exception e) { System.out.println("不,给定的数字 " + n + " 不是回文。"); } } } 

输出

#3) 反向字符串递归 Java

给定一个字符串 "Hello",我们必须把它倒过来,使结果字符串为 "olleH"。

这是用递归来完成的。 从字符串中的最后一个字符开始,我们递归地打印每个字符,直到字符串中的所有字符都用完。

下面的程序使用递归来逆转一个给定的字符串。

 class String_Reverse { //递归方法来反转给定的字符串 void reverseString(String str) { //基本条件;如果字符串是空的或有1个或更少的字符则返回 if ((str==null)} } class Main{ public static void main(String[] args) { String inputstr = "SoftwareTestingHelp"; System.out.println("The given string: " + inputstr); String_Reverse obj = new String_Reverse(); System.out.print("The reversed string: " ); obj.reverseString(inputstr); } } 

输出

#4)二进制搜索Java递归

二元搜索算法是一种著名的搜索算法。 在这种算法中,给定一个由n个元素组成的排序数组,我们在这个数组中搜索给定的关键元素。 一开始,我们通过找到数组的中间元素将数组分成两半。

然后根据关键的中,我们将搜索范围限制在数组的前半部分或后半部分。 这样,同样的过程重复进行,直到找到关键元素的位置。

我们将在这里用递归来实现这个算法。

 import java.util.*; class Binary_Search { // 递归二进制搜索 int binarySearch(int numArray[], int left, int right, int key) { if (right>= left) { // 计算数组的中间 int mid = left + (right - left) /2; // 如果键在中间,返回中间 if (numArray[mid] == key) return mid; // 如果键是key) return binarySearch(numArray, left, mid - 1, key) // 否则递归搜索,在right subarray return binarySearch(numArray, mid + 1, right, key); } //数组中没有元素,返回-1 return -1; } } class Main{ public static void main(String args[]) { Binary_Search ob = new Binary_Search(); //declare and print the array int numArray[] = { 4,6,12,16,22}; System.out.println(" The given array : " + Arrays.toString(numArray)); int len = numArray.length; //length of arrayint key = 16; //要搜索的键 int result = ob.binarySearch(numArray, 0, len - 1, key); if (result == -1) System.out.println("Element " + key + " not present"); else System.out.println("Element " + key + " found at index " + result); } } 

输出

#5)使用递归寻找数组中的最小值

使用递归法,我们也可以找到数组中的最小值。

下面是寻找数组中最小值的Java程序。

 import java.util.*; class Main { static int getMin(int numArray[], int i, int n) { //return first element if only one element or minimum of the array elements return (n == 1) ? numArray[i] : Math.min(numArray[i], getMin(numArray,i + 1 , n - 1)); } public static void main(String[] args) { int numArray[] = { 7,32,64,2, 10, 23 }; System.out.println(" Given Array : " + Arrays.toString(numArray)); int n =numArray.length; System.out.print("数组的最小元素:" + getMin(numArray, 0, n) + "n" ); } } 

输出

这些是递归的一些例子。 除了这些例子之外,软件中的很多其他问题都可以用递归技术来实现。

递归类型

根据对递归方法的调用时间,递归有两种类型。

它们是:

#1)尾部递归

当对递归方法的调用是递归方法内部执行的最后一条语句时,它被称为 "尾部递归"。

在尾部递归中,递归调用语句通常与方法的返回语句一起执行。

下面给出了尾部递归的一般语法:

 methodName ( T parameters...){ { if (base_condition == true) { return result; } return methodName ( T parameters ...) //tail recursion } 

##2)头部递归

头部递归是指任何不是尾部递归的递归方式。 因此,即使是一般的递归也是前方递归。

头部递归的语法如下:

 methodName (T parameters...){ if (some_condition == true) { return methodName (T parameters...); } return result; } 

Java中的递归与迭代

递归 迭代
递归是指一个方法反复调用自己,直到满足一个基本条件的过程。 迭代是一个过程,通过这个过程,一段代码被重复执行有限的次数或直到满足一个条件。
职能的申请。 适用于循环。
对于较小的代码大小来说,效果很好。 对于较大的代码尺寸来说,效果很好。
利用更多的内存,因为每个递归调用都被推到堆栈中。 使用的内存相对较少。
难以调试和维护 更容易调试和维护
如果没有指定基本条件或没有达到基本条件,会导致堆栈溢出。 可以无限地执行,但最终会因为任何内存错误而停止执行
时间的复杂性非常高。 时间复杂性相对较低。

常见问题

问题#1) 递归在Java中是如何工作的?

答案是: 在递归中,递归函数反复调用自己,直到满足一个基础条件。 被调用函数的内存被推到调用函数内存的顶部的堆栈中。 对于每个函数的调用,局部变量被单独拷贝。

Q #2) 为什么要使用递归?

答案是: 递归是用来解决那些可以分解成更小的问题的,整个问题可以用更小的问题来表达。

递归也用于那些过于复杂而无法用迭代方法解决的问题。 除了时间复杂度不是问题的问题外,使用递归。

Q #3) 递归的好处是什么?

答案是:

递归的好处包括:

  1. 递归减少了函数的冗余调用。
  2. 与迭代法相比,递归使我们能够轻松解决问题。

问题#4)递归和迭代哪个更好?

答案是: 递归会重复调用,直到达到基函数为止。 因此有一个内存开销,因为每个函数调用的内存被推到堆栈中。

另一方面,迭代没有太多的内存开销。 递归执行比迭代方法慢。 递归减少了代码的大小,而迭代方法使代码变大。

Q #5) 递归比迭代的优势是什么?

答案是:

  • 递归使代码更加清晰和简短。
  • 对于像河内塔、树形遍历等问题,递归比迭代方法更好。
  • 由于每个函数调用都有内存被推到堆栈上,因此递归使用的内存更多。
  • 递归的性能比迭代的方法要慢。

总结

递归是软件中一个非常重要的概念,不管是哪种编程语言。 递归主要用于解决数据结构问题,如河内塔、树形遍历、链表等。虽然它需要更多的内存,但递归使代码更简单、更清晰。

我们在本教程中探讨了关于递归的所有内容,并实现了许多编程实例,以便更好地理解这一概念。

滚动到顶部