递归入门-数据结构和算法教程

什么是递归?

函数直接或间接调用自身的过程称为递归,相应的函数称为递归函数。使用递归算法,某些问题可以很容易地解决。这样的问题的示例是河内塔(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(LAST IN FIRST OUT)结构,就像堆栈数据结构一样。

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

当从main()调用任何函数时,内存在堆栈上分配给它。递归函数调用自身,被调用函数的内存分配在分配给调用函数的内存之上,并且为每个函数调用创建局部变量的不同副本。当达到基本情况时,函数将其值返回给调用它的函数,并且内存被释放,并且过程继续。

def printFun(test):

	if (test < 1):
		return
	else:

		print(test, end=" ")
		printFun(test-1) # statement 2
		print(test, end=" ")
		return

# Driver Code
test = 3
printFun(test)

输出:

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。内存堆栈如下图所示。
在这里插入图片描述

现在,让我们讨论几个可以用递归解决的实际问题,并了解它的基本工作原理。如需基本了解,请阅读以下文章。

问题1:写一个程序和递归关系,找到n的斐波那契级数,其中n>2。

数学方程:
n if n == 0, n == 1;      
fib(n) = fib(n-1) + fib(n-2) otherwise;

相互关系:
T(n) = T(n-1) + T(n-2) + O(1)

递归程序:
输入:n = 5 
输出:
斐波那契数列的5个数是:0 1 1 2 3
# Function for fibonacci
def fib(n):

	# Stop condition
	if (n == 0):
		return 0

	# Stop condition
	if (n == 1 or n == 2):
		return 1

	# Recursion function
	else:
		return (fib(n - 1) + fib(n - 2))

# Driver Code

# Initialize variable n.
n = 5;
print("斐波那契数列的5个数是:",end=" ")

# for loop to print the fibonacci series.
for i in range(0,n):
	print(fib(i),end=" ")

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

这里是输入5的递归树,它清晰地展示了如何将一个大问题分解成更小的问题。
fib(n)是一个Fibonacci函数。给定程序的时间复杂度可以取决于函数调用。

fib(n) -> level CBT (UB) -> 2^n-1 nodes -> 2^n function call -> 2^n*O(1) -> T(n) = O(2^n)

最好的情况:

T(n) = θ(2^n\2)

在这里插入图片描述

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

数学方程:
1 if n == 0 or n == 1;      
f(n) = n*f(n-1) if n> 1;

相互关系:
T(n) = 1 for n = 0
T(n) = 1 + T(n-1) for n > 0

输入:n = 5
输出:
5的阶乘是:120

# Factorial function
def f(n):

	# Stop condition
	if (n == 0 or n == 1):
		return 1;

	# Recursive condition
	else:
		return n * f(n - 1);


# Driver code
if __name__=='__main__':

	n = 5;
	print("factorial of",n,"is:",f(n))

时间复杂度:O(n)
空间复杂度:O(n)
在这里插入图片描述

实例:递归在实际问题中的真实的应用

递归是一种强大的技术,在计算机科学和编程中有许多应用。以下是递归的一些常见应用:

  • 树和图遍历:递归经常用于遍历和搜索数据结构,如树和图。递归算法可以用于以系统的方式探索树或图的所有节点或顶点。
  • 排序算法:递归算法也用于排序算法,如快速排序和归并排序。这些算法使用递归将数据划分为更小的子数组或子列表,对它们进行排序,然后将它们合并在一起。
  • 分治算法:许多使用分治方法的算法,如二分查找算法,使用递归将问题分解为更小的子问题。
  • 分形生成:可以使用递归算法来生成分形形状和图案。例如,曼德尔布罗特集是通过将递归公式重复应用于复数而生成的。
  • 回溯算法:回溯算法用于解决涉及做出一系列决策的问题,其中每个决策都取决于先前的决策。这些算法可以使用递归来实现,以探索所有可能的路径,并在未找到解决方案时进行回溯。
  • 记忆化:记忆是一种技术,涉及存储昂贵的函数调用的结果,并在再次发生相同的输入时返回缓存的结果。记忆可以使用递归函数来实现,以计算和缓存子问题的结果。

这些只是递归在计算机科学和编程中的许多应用中的几个例子。递归是一种通用且功能强大的工具,可用于解决许多不同类型的问题。

说明:递归的一个真实的例子

递归是一种涉及函数调用自身的编程技术。它可以成为解决复杂问题的强大工具,但也需要仔细实现,以避免无限循环和堆栈溢出。

def factorial(n):
	# Base case: if n is 0 or 1, return 1
	if n == 0 or n == 1:
		return 1

	# Recursive case: if n is greater than 1, call the function with n-1 and multiply by n
	else:
		return n * factorial(n-1)

# Call the factorial function and print the result
result = factorial(5)
print(result) # Output: 120

在这个例子中,我们定义了一个名为factorial的函数,它接受整数n作为输入。该函数使用递归来计算n的阶乘(即,直到n的所有正整数的乘积)。
阶乘函数首先检查n是0还是1,这是基本情况。如果n为0或1,则函数返回1,因为0!1!都是1。
如果n大于1,则函数进入递归情况。它以n-1作为参数调用自身,并将结果乘以n。计算n!通过递归计算(n-1)!.
需要注意的是,如果不小心使用,递归可能效率低下,并导致堆栈溢出。每个函数调用都会向调用堆栈添加一个新帧,如果递归太深,则会导致堆栈变得太大。此外,递归会使代码更难理解和调试,因为它需要考虑多个级别的函数调用。
然而,递归也可以是解决复杂问题的强大工具,特别是那些涉及将问题分解为更小的子问题的问题。如果使用正确,递归可以使代码更优雅,更容易阅读。

递归VS迭代

递归:

  • 当基本情况变为true时终止。
  • 用于函数。
  • 每个递归调用都需要堆栈内存中的额外空间。
  • 更小的代码大小。

迭代:

  • 当条件变为false时终止。
  • 用于循环。
  • 每次迭代都不需要任何额外的空间。

递归编程相对于迭代编程的缺点是什么?

注意,递归和迭代程序具有相同的问题解决能力,即,每个递归程序都可以迭代地编写,反之亦然。递归程序比迭代程序具有更大的空间需求,因为所有函数将保留在堆栈中,直到达到基本情况。由于函数调用和返回的开销,它也有更大的时间需求。
此外,由于代码的长度较小,代码难以理解,因此在编写代码时必须格外小心。如果没有正确检查递归调用,计算机可能会耗尽内存。

递归编程比迭代编程有什么优势?

递归为编写代码提供了一种干净而简单的方法。有些问题本质上是递归的,如树遍历,河内塔等。对于这样的问题,最好编写递归代码。我们也可以在堆栈数据结构的帮助下迭代地编写这样的代码。例如,参考无递归的中序树遍历,迭代河内塔。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据结构算法是计算机科学中非常重要的一部分,学习好它们对于程序员来说是非常有益的。以下是我推荐的一些快速入门数据结构算法的方法: 1. 学习基本概念和术语:在学习数据结构算法之前,我们需要了解基本概念和术语,例如数组、链表、栈、队列、图、树等等,同时也需要掌握一些基本的算法概念,例如排序、查找、递归等等。 2. 选择合适的学习资源:可以选择一些经典的教材或者在线课程来学习,例如《算法导论》、《数据结构算法分析》、LeetCode、Coursera等等,这些资源都可以帮助您快速入门数据结构算法。 3. 刷题:刷题是学习算法的最好方法之一,可以通过一些在线刷题平台,例如LeetCode、LintCode、牛客网等等来练习算法。 4. 参加编程竞赛:参加编程竞赛可以让您更快地掌握数据结构算法,例如ACM、ICPC等等,这些竞赛都需要掌握一定的算法数据结构知识。 5. 实践项目:在实际项目中,我们也会用到一些数据结构算法,例如搜索、排序、图像处理等等,因此在实践项目中掌握数据结构算法也是非常有益的。 总之,学习数据结构算法需要持续的练习和实践,通过不断的刷题和参加编程竞赛来增强自己的算法数据结构能力,同时也需要选择合适的学习资源来深入了解算法数据结构的原理和应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值