首页 > 编程语言 >数据结构与算法:递归算法

数据结构与算法:递归算法

时间:2024-01-28 17:32:05浏览次数:38  
标签:调用 return 函数 递归 fib 算法 printFun 数据结构

递归算法

数据结构与算法:递归算法_堆栈

什么是递归?

函数直接或间接调用自身的过程称为递归,相应的函数称为递归函数。使用递归算法,可以很容易地解决某些问题。此类问题的示例包括汉诺塔 (TOH)中序/先序/后序树遍历、图的 DFS 递归函数通过调用自身的副本并解决原始问题的较小子问题来解决特定问题。需要时可以生成更多的递归调用。重要的是要知道我们应该提供某种情况来终止这个递归过程。

所以我们可以说,每次函数调用自身时都会使用原始问题的简单版本。

为什么需要递归

递归是一项令人惊奇的技术,借助它我们可以减少代码的长度并使其更易于阅读和编写。与稍后将讨论的迭代技术相比,它具有某些优点。对于可以用其相似的子任务来定义的任务,递归是最好的解决方案之一。例如:数字的阶乘。

递归的性质

  • 使用不同的输入多次执行相同的操作。
  • 在每一步中,我们都会尝试较小的输入来使问题更小。
  • 需要基本条件来停止递归,否则会发生无限循环。

算法步骤

在函数中实现递归的算法步骤如下:

第1步: 定义基本情况:确定解决方案已知最简单情况。这是递归的停止条件,因为它防止函数无限地调用自身。

步骤2: 定义递归情况:用更小的子问题来定义问题。将问题分解为更小的子问题,并递归调用函数来解决每个子问题。

步骤3: 确保递归终止:确保递归函数最终到达基本情况,并且不会进入无限循环。

步骤4: 合并解决方案:合并子问题的解决方案来解决原问题。

数学解释

让我们考虑一个问题,程序员必须确定前 n 个自然数的和,有多种方法可以做到这一点,但最简单的方法是将从 1 到 n 的数字相加。所以这个函数看起来就像这样:

方法(1) : 简单地一一相加

f(n) = 1 + 2 + 3 +……..+ n

另一种数学方法可以表示这一点:

方法(2) – 递归添加

f(n) = 1 n=1

f(n) = n + f(n-1) n>1

方法(1)和方法(2)之间有一个简单的区别,那就是在**方法(2)**中,函数“ f() ”本身在函数内部被调用,因此这种现象被称为递归,并且函数包含递归被称为递归函数,最后,这是程序员手中的一个很好的工具,可以以更简单有效的方式编写一些问题。

递归函数如何存储在内存中?

递归使用更多内存,因为递归函数会在每次递归调用时将值添加到堆栈中,并将值保留在那里,直到调用完成。递归函数使用 LIFO(后进先出)结构,就像堆栈数据结构一样。

递归的基本条件是什么?

在递归程序中,提供了基本情况的解决方案,并用较小的问题来表达较大问题的解决方案。

int fact(int n)
{
    if (n < = 1) // base case
        return 1;
    else    
        return n*fact(n-1);    
}

在上面的示例中,定义了 n < = 1 的基本情况,并且可以通过将数字转换为较小的值来求解较大的值,直到达到基本情况。

如何使用递归解决特定问题?

这个想法是用一个或多个较小的问题来表示一个问题,并添加一个或多个停止递归的基本条件。例如,如果我们知道 (n-1) 的阶乘,我们就可以计算阶乘 n。阶乘的基本情况是 n = 0。当 n = 0 时,我们返回 1。

为什么递归会出现Stack Overflow错误?

如果未达到或未定义基本情况,则可能会出现堆栈溢出问题。让我们举个例子来理解这一点。

int fact(int n)
{
    if (n < = 1) // base case
        return 1;
    else    
        return n*fact(n-1);    
}

如果调用fact(10),它将调用fact(9)、fact(8)、fact(7)等,但数量永远不会达到100。因此,未达到基本情况。如果堆栈上的内存被这些函数耗尽,就会导致堆栈溢出错误。

直接递归和间接递归有什么区别?

如果函数 fun 调用相同的函数 fun,则该函数被称为直接递归。如果函数 fun 调用另一个函数(例如 fun_new )并且 fun_new 直接或间接调用 fun ,则该函数被称为间接递归。直接递归和间接递归之间的区别如表 1 所示。

// 直接递归的示例
void directRecFun()
{
    // Some code....
    directRecFun();
    // Some code...
}

// An example of indirect recursion
void indirectRecFun1()
{
    // Some code...
    indirectRecFun2();
    // Some code...
}
void indirectRecFun2()
{
    // Some code...
    indirectRecFun1();
    // Some code...
}

递归中如何为不同的函数调用分配内存?

当从 main() 调用任何函数时,都会在堆栈上为其分配内存。递归函数调用自身,被调用函数的内存分配在分配给调用函数的内存之上,并且为每个函数调用创建不同的局部变量副本。当达到基本情况时,函数将其值返回给调用它的函数,并且内存被解除分配,并且该过程继续。 让我们通过一个简单的函数来举例说明递归是如何工作的。

PHP

<?php
// PHP程序演示 递归的工作原理
// 用于演示递归工作的函数
function printFun($test)
{
	if ($test < 1) {
		return;
  }	else {
		echo("$test ");
		printFun($test-1);
		
		echo("$test ");
		return;
	}
}

$test = 3;
printFun($test);

Javascript

<script>

// JavaScript程序演示递归的工作原理
function printFun(test)
	{
		if (test < 1) {
			return;
    }	else {
			console.log(test + " ");
			printFun(test - 1); // statement 2
			console.log(test + " ");
			return;
		}
	}

// Driver code
	let test = 3;
	printFun(test);

</script>

输出

3 2 1 1 2 3

时间复杂度: O(1) 辅助空间: O(1)

当从 main() 调用printFun(3)时,内存被分配给 printFun(3),局部变量 test 被初始化为 3,语句 1 到 4 被压入堆栈,如下图所示。它首先打印“3”。在语句 2 中,调用printFun(2),为 **printFun(2)**分配内存,并将局部变量 test 初始化为 2,并将语句 1 到 4 压入堆栈。

类似地,printFun(2)调用printFun(1)printFun(1)调用printFun(0)printFun(0)转到 if 语句,然后返回到printFun(1)。**printFun(1)的其余语句被执行并返回到printFun(2)**等等。在输出中,打印从 3 到 1 的值,然后打印 1 到 3。内存堆栈如下图所示。

数据结构与算法:递归算法_递归_02

使用递归解决的实际问题并了解其基本工作原理

问题 1: 编写一个递归关系程序来查找 n 的斐波那契数列,其中 n>2 。

数学方程:

如果 n == 0,n == 1;输出:0, 1
否则: fib(n) = fib(n-1) + fib(n-2)

递归关系: T(n) = T(n-1) + T(n-2) + O(1)

递归程序:

输入: n = 5 输出: 斐波那契数列 5 个数字为:0 1 1 2 3

package main

import (
	"fmt"
	"testing"
)

func fib(n int) int {
	if n == 0 {
		return 0
	} else if n == 1 || n == 2 {
		return 1
	}

	return fib(n-1) + fib(n-2)
}

func Test_fib(t *testing.T) {
	var n = 5
	for i, _ := range make([]int, n) {
		fmt.Println(fib(i))
	}
}

输出: 斐波那契数列 5 个数字是: 0 1 1 2 3

时间复杂度: O(2n ) 辅助空间: O(n)

这是输入 5 的递归树,它清楚地显示了如何将大问题解决为小问题。 fib(n) 是斐波那契函数。给定程序的时间复杂度取决于函数调用。

对于最好的情况: T(n) = θ(2^n\2)

数据结构与算法:递归算法_堆栈_03

**问题 2:**编写一个程序和递归关系来查找 n 的阶乘,其中 n>2 。

数学方程:

如果 n == 0 或 n == 1,则为 1;
f(n) = n*f(n-1) 如果 n> 1;

递归关系:

T(n) = 1(n = 0) T(n) = 1 + T(n-1)(n > 0)

递归程序:输入: n = 5 输出: 5 的阶乘为:120

Golang

func f(n int) int {
	if n == 0 {
		return 0
	}
	if n == 1 {
		return 1
	}

	return n * f(n-1)
}

func Test_t(t *testing.T) {
	var n = 5
	rest := f(n)
	fmt.Println(rest)
}

标签:调用,return,函数,递归,fib,算法,printFun,数据结构
From: https://blog.51cto.com/demo007x/9453603

相关文章

  • 生成一个满二叉数算法
    1、树结构类publicclassTreeNode<T>{Tval;TreeNode<T>parent;TreeNode<T>right;TreeNode<T>left;publicTreeNode(){}publicTreeNode(Tval){this.val=val;this.parent=nul......
  • 一些在刷js算法时常用的方法(1)
    Array.fromArray.from()静态方法从可迭代或类数组对象创建一个新的浅拷贝的数组实例String、Array、TypedArray、Map、Set以及Intl.Segments(en-US)都是内置的可迭代对象console.log(Array.from('foo'));//输出:Array["f","","o","o"]可以将字符串拆成数组,同时将......
  • 【学习笔记】部分树上算法(概念篇)
    本文包括:轻重链剖分(done)线段树合并(done)tobeupd:长链剖分DSUontree(树上启发式合并)点分治边分治LCT有待更新本文非例题代码大多未经过编译,谨慎使用本文本来只有重剖长剖dsu,但是发现不会写,另外几个甚至更简单就带歪了.jpgpart1轻重链剖分树剖是一类算法的总......
  • 单源最短路径算法之bellman-ford
    单源最短路径算法之\(bellman-ford\)以边为研究对象单起点多终允许有负边权\(bellman-ford\)的工作原理假设\(n\)个点\(m\)条有向边的图,求\(s\)为起点的最短路条以\(s\)出发的最短路,最多包含\(n\)个点,\(n-1\)条边对于一条边\((x,y,w)\),\(y\)可能被\(x\)......
  • 算法模板 v1.4.1.20240128
    算法模板v1.1.1.20240115:之前的历史版本已经不可寻,创建了第一份算法模板。v1.2.1.20240116:删除“编译”-“手动开栈”与“编译”-“手动开O优化”;将“编译”-“CF模板”中的第20行代码cin>>T;注释;删除“读写”及其目录下的内容;删除“图论”-“欧拉图”-“混合图”;删除“图论”-......
  • 贪心算法之两端贪
    这类题目一般是当前元素的位置既受前一个元素的影响又受后一个元素的影响。题目一定是要确定一边之后,再确定另一边,例如比较每一个元素的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。当确定一边后,就单独看排完序的数组,因为这时候只需考虑一边,因此容易找规律。典型题目:1......
  • 文心一言 VS 讯飞星火 VS chatgpt (188)-- 算法导论14.1 5题
    五、用go语言,给定n个元素的顺序统计树中的一个元素x和一个自然数i,如何在O(lgn)的时间内确定工在该树线性序中的第i个后继?文心一言,代码正常运行:在顺序统计树(也称为平衡二叉搜索树)中,要找到一个元素x的第i个后继,我们可以使用以下步骤:从根节点开始,使用递归或迭代方式沿......
  • 递归搜索文件
    1publicstaticvoidmain(String[]args){2searchFile(newFile("F:/"),"logFile-data.log");3}45privatestaticvoidsearchFile(Filefile,StringfileName){6//判断file是否是目录7if(file!=......
  • day25 代码随想录算法训练营 17. 电话号码的字母组合
    题目:17.电话号码的字母组合我的感悟:一时间没理解没关系,只要不放弃,就会成长!!!理解难点:index是独立集合的起点,需要理解它。有些东西就是时间的积累代码难点:代码示例:classSolution:def__init__(self):self.letterMap=["",#0......
  • 代码随想录算法训练营第四天| 24. 两两交换链表中的节点 19.删除链表的倒数第N个节
    24.两两交换链表中的节点给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。题目链接:24.两两交换链表中的节点-力扣(LeetCode)建议画图,会更清晰一些。同时注意交换问题要用两个临时节点。class......