Task03:栈与递归

本文介绍了栈的基本概念,包括定义、操作和Python实现。接着详细阐述了递归的概念,以汉诺塔问题为例,展示了递归的使用。最后,提出了车辆重排序问题,探讨如何通过类似汉诺塔的策略来解决火车车厢的重新排列问题。

1. 栈的定义与操作

1.1 栈的定义

插入(入栈)和删除(出栈)操作只能在一端(栈顶)进行的线性表。即先进后出(First In Last Out)的线性表。
顺序表模拟入栈、出栈单链表模拟入栈、出栈

1.2 栈的操作

  • 入栈操作:将数据元素值插入栈顶。
  • 出栈操作:移除栈顶的数据元素。
  • 是否为空:判断栈中是否包含数据元素。
  • 得到栈深:获取栈中实际包含数据元素的个数。
  • 清空操作:移除栈中的所有数据元素。
  • 获取栈顶元素。

1.3 栈的实现(python)

class Stack:
  """模拟栈"""
  def __init__(self):
    self.items = []
     
  def isEmpty(self):
    return len(self.items)==0
   
  def push(self, item):
    self.items.append(item)
   
  def pop(self):
    return self.items.pop()
   
  def peek(self):
    if not self.isEmpty():
      return self.items[len(self.items)-1]
     
  def size(self):
    return len(self.items)
s=Stack()
print(s.isEmpty())
s.push(4)
s.push('dog')
print(s.peek())
s.push(True)
print(s.size())
print(s.isEmpty())
s.push(8.4)
print(s.pop())
print(s.pop())
print(s.size())

2. 递归

如果一个函数在内部调用自身本身,这个函数就是递归函数。

汉诺塔问题

我们可以先假设除 a 柱最下面的盘子之外,已经成功地将 a 柱上面的 63个盘子移到了 b 柱,这时我们只要再将最下面的盘子由 a 柱移动到 c 柱即可。

当我们将最大的盘子由 a 柱移到 c 柱后,b 柱上便是余下的 63 个盘子,a 柱为空。因此现在的目标就变成了将这 63 个盘子由 b 柱移到 c 柱。这个问题和原来的问题完全一样,只是由 a 柱换为了 b 柱,规模由 64 变为了 63。因此可以采用相同的方法,先将上面的 62 个盘子由 b 柱移到 a 柱,再将最下面的盘子移到 c 柱。

以此内推,再以 b 柱为缓冲,将 a 柱上面的 62 个圆盘最上面的 61 个圆盘移动到 b 柱,并将最后一块圆盘移到 c 柱。

我们已经发现规律,我们每次都是以 a 或 b 中一根柱子为缓冲,然后先将除了最下面的圆盘之外的其它圆盘移动到辅助柱子上,再将最底下的圆盘移到 c 柱子上,不断重复此过程。

这个反复移动圆盘的过程就是递归,例如我们每次想解决 n 个圆盘的移动问题,就要先解决(n-1)个盘子进行同样操作的问题。

于是可以编写一个函数,move(n, a, b, c)。可以这样理解:move(盘子数量, 起点, 缓冲, 终点)。
1. a 上只有一个盘子的情况,直接搬到 c,代码如下:

if n == 1:
    print(a, '-->', c)

2. a 上不止有一个盘子的情况:

首先,需要把 n-1 个盘子搬到 b 柱子缓冲。打印出的效果是:a --> b。

move(n - 1, a, c, b)

再把最大的盘子搬到 c 柱子,也是最大尺寸的一个。打印出:a–>c。

move(1, a, b, c)

最后,把剩下 b 柱的 n-1 个盘子搬到 c 上,此时缓冲变成了起点,起点变成了缓冲。

move(n - 1, b, a, c)

利用 Python 实现汉诺塔问题

i = 0


def move(n, a, b, c):
    global i
    if (n == 1):
        i += 1
        print('移动第 {0} 次 {1} --> {2}'.format(i, a, c))
        return
    move(n - 1, a, c, b)
    move(1, a, b, c)
    move(n - 1, b, a, c)


move(3, "a", "b", "c")  

# 移动第 1 次 a --> c
# 移动第 2 次 a --> b
# 移动第 3 次 c --> b
# 移动第 4 次 a --> c
# 移动第 5 次 b --> a
# 移动第 6 次 b --> c
# 移动第 7 次 a --> c

3.练习-车辆重排序

假设一列货运列车共有n节车厢,每节车厢将停放在不同的车站。假定n个车站的编号分别为1n,货运列车按照第n站至第1站的次序经过这些车站。车厢的编号与它们的目的地相同。为了便于从列车上卸掉相应的车厢,必须重新排列车厢,使各车厢从前至后按编号1n的次序排列。当所有的车厢都按照这种次序排列时,在每个车站只需卸掉最后一节车厢即可。

我们在一个转轨站里完成车厢的重排工作,在转轨站中有一个入轨、一个出轨和k个缓冲铁轨(位于入轨和出轨之间)。图(a)给出一个转轨站,其中有k个(k=3)缓冲铁轨H1H2H3。开始时,n节车厢的货车从入轨处进入转轨站,转轨结束时各车厢从右到左按照编号1n的次序离开转轨站(通过出轨处)。在图(a)中,n=9,车厢从后至前的初始次序为5,8,1,7,4,2,9,6,3。图(b)给出了按所要求的次序重新排列后的结果。

具有三个缓冲区铁轨的转轨站

编写算法实现火车车厢的重排,模拟具有n节车厢的火车“入轨”和“出轨”过程。

def output(stacks, n):
    global minVal, minStack
    stacks[minStack].pop()
    print('移动车厢 %d 从缓冲铁轨 %d 到出轨。' % (minVal, minStack))
    minVal = n + 2
    minStack = -1
    for index, stack in enumerate(stacks):
        if((not stack.isempty()) and (stack.top() < minVal)):
            minVal = stack.top()
            minStack = index

def inputStack(i, stacks, n):
    global minVal, minStack
    beskStack = -1  # 最小车厢索引值所在的缓冲铁轨编号
    bestTop = n + 1  # 缓冲铁轨中的最小车厢编号
    for index, stack in enumerate(stacks):
        if not stack.isempty():  # 若缓冲铁轨不为空
            # 若缓冲铁轨的栈顶元素大于要放入缓冲铁轨的元素,并且其栈顶元素小于当前缓冲铁轨中的最小编号
            a = stack.top()
            # print('stack.top()的类型是', a)
            if (a > i and bestTop > a):
                bestTop = stack.top()
                beskStack = index
        else:  # 若缓冲铁轨为空
            if beskStack == -1:
                beskStack = index
                break
    if beskStack == -1:
        return False
    stacks[beskStack].push(i)
    print('移动车厢 %d 从入轨到缓冲铁轨 %d。' % (i, beskStack))
    if i < minVal:
        minVal = i
        minStack = beskStack
    return True

def rail_road(list, k):
    global minVal, minStack
    stacks = []
    for i in range(k):
        stack = stack1.ArrayStack()
        stacks.append(stack)
    nowNeed = 1
    n = len(list)
    minVal = n + 1
    minStack = -1
    for i in list:
        if i == nowNeed:
            print('移动车厢 %d 从入轨到出轨。' % i)
            nowNeed += 1
            # print("minVal", minVal)
            while (minVal == nowNeed):
                output(stacks, n)  # 在缓冲栈中查找是否有需求值
                nowNeed += 1
        else:
            if(inputStack(i, stacks, n) == False):
                return False
    return True

if __name__ == "__main__":
    list = [3, 6, 9, 2, 4, 7, 1, 8, 5]
    k = 3
    minVal = len(list) + 1
    minStack = -1
    result = rail_road(list, k)
    while(result == False):
        print('需要更多的缓冲轨道,请输入需要添加的缓冲轨道数量。')
        k = k + int(input())
        result = rail_road(list, k)

<think>嗯,用户的问题是关于如何用手动实现递归函数的非递归版本。我需要仔细分析递归的工作原理,然后思考如何用来模拟这个过程。 首先,递归函数在调用时会使用系统来保存每次调用的参数、局部变量和返回地址。所以,如果我要手动用实现迭代版本,就需要显式地维护这个结构,存储必要的信息。比如,每次递归调用前,系统会保存当前的状态,然后处理子问题。在迭代版本中,我需要手动将这些状态压入中,然后循环处理,直到为空。 接下来,我应该考虑具体的步骤。例如,阶乘函数的递归实现比较简单,可以作为一个例子。递归的阶乘函数是n*factorial(n-1),基线条件是n=0或1时返回1。对应的迭代版本需要将每次递归调用的参数n压入中,然后按相反的顺序处理,因为是后进先出的结构。不过,阶乘其实可以通过简单的循环来实现,不需要用,所以可能需要找一个更复杂的例子,比如二叉树的遍历,比如中序遍历,这时候用来模拟递归会更典型。 不过用户可能希望有一个通用的方法,所以应该先总结一般步骤,再举例说明。步骤可能包括:确定递归函数的参数和返回状态,将初始参数压,循环处理中的任务,分解子问题并将新任务压。同时,需要处理递归中的不同阶段,比如在分治算法中,可能需要先处理左子树再处理右子树,这时候中需要记录当前处理到了哪个阶段。 比如,斐波那契数列的递归实现是fib(n) = fib(n-1) + fib(n-2),但直接用模拟的话可能会有重复计算,所以需要优化,或者使用记忆化。不过这可能比较复杂,作为例子可能不太合适。或许还是以分治类的例子,比如汉诺塔问题,或者树的遍历更好。 然后,我需要考虑如何将递归调用转换为操作。例如,在二叉树的中序遍历递归版本中,函数会先遍历左子树,然后访问节点,再遍历右子树。用实现的迭代版本需要显式地压入节点,并记录是否已经处理过左子树。这可能需要使用一个状态变量,或者通过压顺序来控制。 不过用户的问题可能更关注一般方法,而不仅仅是某个具体例子。所以,我应该先概括步骤,再给出一个具体的例子,比如阶乘或者二叉树遍历,以展示如何应用这些步骤。 另外,需要注意递归函数可能有多个递归调用点,这时候中需要保存多个状态,可能需要将任务分解为多个阶段。例如,在遍历左子树之后,需要处理当前节点,再处理右子树。这时候可以将每个任务标记为不同阶段,比如“待处理左子树”、“待处理当前节点”、“待处理右子树”,并在中保存这些状态。 总结起来,步骤可能包括: 1. 确定递归函数中的参数和每次调用需要保存的状态。 2. 使用来保存这些状态,每个元素代表一个待处理的任务。 3. 初始化,将初始任务压入中。 4. 循环处理顶任务,直到为空。 5. 在循环中,根据当前任务的状态,分解为子任务,并按相反顺序压入中(因为是LIFO),以保持正确的处理顺序。 然后,举例说明,比如阶乘函数的非递归实现,或者二叉树的中序遍历的非递归实现。同时,需要指出递归和迭代版本的效率差异,比如的开销和可能的优化方法。 在编写回答时,还需要引用用户提供的参考资料,比如递归和迭代的时间复杂度理论相同,但实际应用中递归效率可能更低[^3]。同时,可能涉及函数调用堆的开销问题[^3]。 最后,生成相关问题,可能涉及其他递归转迭代的方法,优化策略,或者具体应用场景。</think>### 通过实现递归函数的非递归版本方法 递归的本质是通过系统调用保存函数执行状态(参数、返回地址、局部变量等),而手动用实现递归的迭代版本,核心是**显式模拟这一过程**。以下是具体步骤和示例: --- #### **步骤说明** 1. **定义结构** 每个元素需保存递归函数的参数、中间状态(如当前处理阶段)及必要局部变量。 2. **初始化** 将初始参数压入中,模拟首次递归调用。 3. **循环处理顶任务** - 弹出顶元素,根据状态执行对应操作(如分解问题、处理子任务、合并结果)。 - 将子任务按**逆序**压入(因是LIFO结构),确保执行顺序递归一致。 4. **终止条件** 当为空时,循环结束,返回最终结果。 --- #### **示例:阶乘函数(递归→迭代)** **递归版本** ```python def factorial(n): if n <= 1: return 1 return n * factorial(n-1) ``` **迭代版本(显式用)** ```python def factorial_iter(n): stack = [] result = 1 # 初始任务:计算n的阶乘,标记为“未处理乘法” stack.append({"param": n, "processed": False}) while stack: task = stack.pop() param = task["param"] is_processed = task["processed"] if not is_processed: if param <= 1: result *= 1 # 基线条件 else: # 分解任务:先处理乘法阶段(按逆序压) stack.append({"param": param, "processed": True}) # 标记当前param待乘 stack.append({"param": param-1, "processed": False}) # 递归子任务 else: result *= param # 合并结果 return result ``` **关键点** - 用`processed`标记区分“递归展开阶段”和“结果合并阶段”[^2]。 - 子任务压顺序需递归调用顺序相反(如先压入`param-1`再处理乘法)。 --- #### **复杂案例:二叉树中序遍历(非递归实现)** ```python class Node: def __init__(self, val): self.val = val self.left = None self.right = None def inorder_traversal(root): stack = [] result = [] current = root while stack or current: # 模拟递归左子树展开 while current: stack.append(current) current = current.left # 到达最左端后回溯 current = stack.pop() result.append(current.val) # 转向右子树 current = current.right return result ``` **说明** - 保存未处理的节点,模拟递归调用左子树的顺序。 - 每次弹出顶节点时,访问其值,再处理右子树[^4]。 --- #### **效率分析** - **时间复杂度**:递归版本一致(如阶乘为$O(n)$,中序遍历为$O(n)$)。 - **空间复杂度**:显式的空间占用递归深度相同,但避免了函数调用开销。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值