在这篇文章中,我们将探讨堆栈及其属性和操作,然后我们将介绍递归并解释为什么堆栈用于递归,最后我们将讨论如何将递归转换为迭代方法。
目录。
- 堆栈简介
- 向堆栈中添加元素
- 从堆栈中删除元素
- 递归
- 递归的堆栈
- 将递归方法转换为迭代方法
简而言之,用于递归的数据结构是堆栈(LIFO)数据结构。
先决条件。
堆栈简介
堆栈是计算机科学中最基本的数据结构之一。之所以这样称呼它,是因为这正是最好的比喻。假设我们想一个一个地存储6个立方体,我们可能会把它们组织成一个整齐的堆栈,以有效地利用货架空间。插图如下。
上图显示,如果我们想在堆栈中添加额外的立方体,我们会把它添加到哪里?是在堆栈的顶部还是在底部?当然,我们应该把它加在顶部,因为把它加在底部会涉及到移动整个堆栈。
如果我们想分配一个立方体呢?同样,我们会从哪里拿?我们是抓取堆栈顶部的立方体,还是移动沉重堆栈上的其他立方体,从最底部取走立方体?我想我们中的大多数人都会在堆栈的顶部增加立方体,并从堆栈的顶部取走立方体。
上面提到的操作是计算机堆栈ADT所模拟的。堆栈的行为意味着最近添加的项目总是第一个被删除的,因此堆栈被称为 "后进/先出 "或 后进制数据类型。对于一个堆栈ADT来说,基本的功能被称为 ***推()***和 pop(). ***推()***将值添加到堆栈的顶部,并且 ***pop()***删除并返回堆栈顶部的值。
将元素添加到堆栈中
如果我们想在堆栈中添加一个元素,我们应该在顶部添加它,我们把这个操作命名为 push():
示例代码 -- 实现的例子 推送():
蟒蛇
class Stack:
def __init__(self, size):
''' Constructor
Parameters:
self -- the current object
size -- the initialize size of our stack
'''
self.data = [0] * size
self.end = 0
def push(self, item):
''' push -- adds something to the top of the stack
Parameters:
self -- the current object
item -- the item to add to the stack
Returns nothing
'''
if self.end >= len(self.data):
print("Full!")
return
self.data[self.end] = item
self.end += 1
从堆栈中删除元素
如果我们想从堆栈中删除一个元素,我们应该把它放在最上面,我们把这个操作命名为 pop():
示例代码 -- 实现的例子 pop():
循环
class Stack:
def __init__(self, size):
''' Constructor
Parameters:
self -- the current object
size -- the initialize size of our stack
'''
self.data = [0] * size
self.end = 0
def push(self, item):
''' push -- adds something to the top of the stack
Parameters:
self -- the current object
item -- the item to add to the stack
Returns nothing
'''
if self.end >= len(self.data):
print("Full!")
return
self.data[self.end] = item
self.end += 1
def pop(self):
''' pop -- removes something from the top of the stack
Parameters:
self -- the current object
Returns the top element after removing it from the stack
'''
if self.end <= 0:
print("Empty!")
return
self.end -= 1
return self.data[self.end]
递归
递归是一个许多人似乎都在纠结的概念。然而,究竟什么是递归?基本上,递归是多次连续应用一个规则或过程来实现一个大问题的答案。它通过从最微不足道的问题的基本答案开始,然后反复应用一个过程,每一次成功的应用都会产生同一问题的一个渐进的大版本的解决方案。
递归的想法是,我们有一个问题要解决,我们正在寻找一种方法来解决这个问题,证明最终答案是将一组顺序计算应用于解决同一问题的较小版本的结果。由于较小的问题是同一类型的,其解决方案又来自于应用于更小版本的问题的相同过程。
一般来说,递归包括两个阶段。
- 基本案例--琐碎/简单的案例,解决方案是 "显而易见的"
- 递归情况--"神奇 "的部分,我们将问题分割开来,做一些计算,然后在子部分调用函数
递归的堆栈
我们可以回顾一下Stack的数据结构。
- 一个项目的列表
- 推一个项目进入堆栈
- pop从堆栈中取出一个项目
- "后进先出"
上面的四个属性可以完美地实现递归的过程。
当计算机执行一种编程语言时,它们会提供一个包含当前范围内所有变量(和其他名称)的上下文。当一个函数被调用时,该上下文被保存下来,并且该函数的新上下文被""推送的"在旧的上下文之上。这就是 (呼叫栈和上下文是 堆栈框架.每个堆栈框架都有被调用函数中定义的所有变量(和函数)的条目。
Python
def sum(k):
if (k <= 1):
// base case
return(1)
else:
// recursive case
return(k + sum(k - 1))
假设我们想执行 sum(3):
当我们执行 *****sum(3)*****时,一个堆栈帧被保存,一个新的函数堆栈帧将被 "推 "到堆栈的顶部,系统堆栈的顶部正好是 基例,图示如下。
当基本情况被执行后,最上面的堆栈帧将被从系统堆栈中弹出。
在这之后,下一个栈顶帧也将被弹出。
随着这个过程的进行,计算机将最终执行原始程序并返回正确的答案。
将递归方法转换为迭代方法
考虑我们上面提到的递归 和的例子。
递归。
def sum(k):
if (k <= 1):
// base case
return(1)
else:
// recursive case
return(k + sum(k - 1))
我们可以清楚地看到,该函数的最后一个操作 和做的最后一个操作是递归地调用自己。对于递归的实现,如 和,我们称其为"尾部递归".
任何问题的递归解决方案都有一个迭代的替代方案,反之亦然。这一原则在尾部递归操作中也是如此。递归实现在正确的情况下实现了优雅的编码风格。然而,它们有一个可能的问题。 堆栈溢出.当有太多的递归调用时,堆栈溢出的现象就会发生,它填满了程序的堆栈。对于上面的 和函数,递归调用的数量正好等于 k,尽管在这种情况下堆栈溢出不会发生,但对于大数来说,我们可能会遇到这个问题。
我们如何将递归转换为迭代?事实上,从递归的实现来看,我们可以得出结论,整个过程可以描述为''从0开始,每一个正数k的最终结果递增k",这说明可以用一个循环来达到这个目的。从递归到迭代的转换如下。
迭代。
def sum(k):
res = 0
while k > 0:
res += k
k--
return res
从上面的代码中我们不难看出,通过将递归函数转换为迭代函数,我们很好地避免了堆栈溢出,这就是迭代的力量。
值得强调的是,迭代不仅是递归的替代品,它本身也是一种重要的设计技术。在现实生活中,递归和迭代都在解决问题中发挥着重要作用。
通过OpenGenus的这篇文章,你一定对用于递归的数据结构有了完整的了解。