【问题标题】:Question regarding recursive stack while deleting middle element from stack关于从堆栈中删除中间元素时递归堆栈的问题
【发布时间】:2020-08-03 11:40:18
【问题描述】:

我有从堆栈中删除中间元素的运行代码,这不是问题。这是我的代码;

def insert(stack,temp,curr,n):
#print(curr,stack)
    if curr!=n//2:
       print(curr)
       stack.append(temp)
    #return
    return

def delete_middle(stack,n,curr):
    while len(stack)>0:
       temp=stack.pop()
       delete_middle(stack,n,curr+1)
       insert(stack,temp,curr,n)
    #print(stack)
       return stack

delete_middle([2,4,1,18,34],5,0)

我的代码运行良好。但我试图了解递归堆栈是如何工作的。例如,我们在5 次左右递归调用delete_middle()(与列表长度相同,stack)。每次指针变量,curr 都会更新为指向当前元素。如您所见,curr 的最后一个值为 5,但由于堆栈长度为 0,因此不再有递归调用。然后调用insert() 函数。

有趣的是,如果我查看currinsert() 中的值,我们会得到以下输出;

4
3
2
1
0

为什么我们在这里没有看到curr 的第一个值是 5?我将非常感谢这里的一些反馈。我可以对它的时间复杂度有一些想法吗?似乎是O(N),其中N 是堆栈的大小。但我对此有点生疏。将在这里欣赏一些见解。 谢谢

【问题讨论】:

    标签: python-3.x recursion stack


    【解决方案1】:

    编辑:

    插入函数在递归函数使用空堆栈后才会执行。

    START DELETE 0 [only one while loop executed]
    START DELETE 1 [only one while loop executed]
    START DELETE 2 [only one while loop executed]
    START DELETE 3 [only one while loop executed]
    START DELETE 4 [only one while loop executed]
    START DELETE 5 [NO while loop executed because list is empty]
    -------------------------
    START INSERT iteration 4
    END INSERT iteration 4
    END DELETE 4
    -------------------------
    START INSERT iteration 3
    END INSERT iteration 3
    END DELETE 3
    -------------------------
    START INSERT iteration 2
    END INSERT iteration 2
    END DELETE 2
    -------------------------
    START INSERT iteration 1
    END INSERT iteration 1
    END DELETE 1
    -------------------------
    START INSERT iteration 0
    END INSERT iteration 0
    END DELETE 0
    

    所以,我们首先运行插入函数 (4),因为当最后一个 delete_middle(5) 被执行时,列表堆栈是空的,并且它没有按照所述运行插入函数:

    while len(stack)>0:
       ...........
       insert(stack,temp,curr,n)
    

    delete_middlen=5 中的上述内容将不会被执行,因为len(stack)==0

    中间是如何工作的:

    #first executed  delete_middle([2,4,1,18,34],5,0)
    def delete_middle(stack,n,curr):                            iteration 1
        while len(stack)>0:                     initial stack   [2,4,1,18,34]   
           temp=stack.pop()               stack after removal   [2,4,1,18]
           .....
           delete_middle(stack,n,curr+1) -> becomes: delete_middle([2,4,1,18],5,1) iteration 2
                                              while len(stack)>0:                     initial stack   [2,4,1,18]   
                                              temp=stack.pop()                  stack after removal   [2,4,1]
                                              .....
                                              delete_middle(stack,n,curr+1) > becomes: delete_middle([2,4,1],5,2) and so on until stack = [] (empty)
                                              .....
          (!) During these iterations insert(stack,temp,curr,n) is not executed because:
    

    插入如何运行:

      delete_middle([2,4,1,18,34],5,0)
        # Iteration 1
        #delete_middle(stack,     n, curr+1)
        delete_middle([2,4,1,18], 5,   1   )
        insert not executed!
          # Iteration 2
          delete_middle([2,4,1],5,2)
          insert not executed!
            # Iteration 3
            delete_middle([2,4],5,3)
            insert not executed!
              # Iteration 4
              delete_middle([2],5,4)
              insert not executed!
                # Iteration 5
                delete_middle([],5,5) # returns nothing because it does not enter the while loop
                insert not executed!
        Only from now -> each insert will be executed from inside delete_middle!
                insert 4
                  insert 3
                    insert 2
                      insert 1
    

    关于时间复杂度:

    Time complexity = time taken to build the final list;
                    = (1 pop + 1 insert) * n
                    = O(n)
    

    从最坏情况的角度来看,您可以说复杂度为 O(n),因为将列表大小减少 1 个元素涉及将 1 个元素附加到列表中,这是因为您只有 每级一次递归。(而..返回缩进)

    虽然 pop 是 O(1) 并且列表每次都会减少 (O(log n)),但当每个 middle_delete 被调用时,您的算法的运行时间会随着输入数据的大小而增加执行 (n-1)总共附加;附加整个剩余列表。

    Some starting point.


    结束编辑

    关于您的代码,是的,它可以工作,但如果我可以的话,会有一些限制:

    1. 目标列表的长度必须是奇数
    2. 需要一些额外的不必要的*标准 (curr,n) [不必要的见 最后一部分]
    3. 函数未“完全正确”执行

    解释:

    1. 自己试试

    2. 回答结束(请耐心等待:))

    3. 例如:

    在您的代码中:

    while len(stack)>0:
       .....
       return stack
    

    基本上,您似乎返回了 while 循环的第一个值。 示例:

    c = 0
    def func(d):
      while d < 5:
        d+=1
        return d
    
    print(func(c))
    

    递归效果是由None 在while 循环中产生的func(insert) 的巧妙使用触发的。这就像拉手断而不是踏板。 :)

    关于您的问题

    如下所示,(中间函数执行+插入函数)为每个元素。

    delete middle iterion 0
    --------------------------
    temp 34 0
    delete middle iterion 1
    --------------------------
    temp 18 1
    delete middle iterion 2
    --------------------------
    temp 1 2
    delete middle iterion 3
    --------------------------
    temp 4 3
    delete middle iterion 4
    --------------------------
    temp 2 4
    delete middle iterion 5
    --------------------------
    ======================
    insert iteration 4
    curr:4, stack:[], temp:2,n:5
    ======================
    insert iteration 3
    curr:3, stack:[2], temp:4,n:5
    ======================
    insert iteration 2
    curr:2, stack:[2, 4], temp:1,n:5
    ======================
    insert iteration 1
    curr:1, stack:[2, 4], temp:18,n:5
    ======================
    insert iteration 0
    curr:0, stack:[2, 4, 18], temp:34,n:5
    [2, 4, 18, 34]
    

    为什么我们在这里看不到 curr 的第一个值是 5?

    如果你从 0 开始 (curr),你会看到从 4->0 的迭代,而当初始 curr 为 1 时,迭代将是 5->1;因此将触发 5 (N) 个删除函数调用。

    1. 我用一个简单的逻辑稍微重构了你的代码:

    如果您有一个目标列表 [1,2,..,n-1,n] 并且您想使用递归函数删除中间部分,那么这种方法可能会吸引您:

    让我们从 0 计数器开始count = 0 对于列表中的每个元素,计数器将增加 1 count+=1

    现在,要从每一侧(左、右)消除一个元素以到达中间,我们可以说:如果 count 为 0 或 count 甚至从列表中删除最后一项,如果不是则删除第一项列表每次迭代都会丢失一项。

    if count == 0 and count % 2 == 0:
     target_list.pop(-1)
    else:
     target_list.pop(0)
    

    如果列表有偶数个项目怎么办?然后我们也会包含这个场景:if len(target) == 2

    我还建议使用一个小技巧,即函数参数的默认值仅在定义函数时计算一次。

    这是什么意思?检查Common Mistake #1 的示例。

    将上面的所有内容都包含到最终的 func (O(N)) 中:

    def eliminate_middle(target, count=0, beg_l=[],end_l=[]):
      print(count)                                    # O(N)
      if len(target) == 1:                            # if target has only one element
        return beg_l+sorted(end_l)
    
      else:
    
        if count == 0 or count % 2 == 0:
    
          if len(target) == 2:                        # case len(target) is Even 
            return beg_l+sorted(end_l)                #eliminates the two middle elements
    
          end_l.append(target.pop(-1))                #target loses last element
          return eliminate_middle(target,count+1)
    
        else:
    
          beg_l.append(target.pop(0))                 # target loses first element
          return eliminate_middle(target, count+1)
    
    print(eliminate_middle([2,4,1,18,34]))
    

    【讨论】:

    • 感谢您花时间详细解释。如果可以的话,我有几个问题;首先,我同意定义while循环的方式,它应该在第一次调用插入函数后立即返回。我可以知道为什么 insert 在这里被调用 4 次。它被调用了 4 次是一件好事,但我真的很想了解为什么会发生这种情况?其次,我仍然不确定我的算法的时间复杂度。你能建议吗?
    • @jayant 添加了从编辑到结束编辑的新部分。如果有不清楚的地方请告诉我。
    • 感谢您的详细解释。现在知道了,insert() 是如何被调用的。在旁注中,我正在查看您建议的代码。我担心的是,正如问题中所述,我正在处理堆栈,因此只允许使用 pop()append()。这就是我使用list 数据结构但只有pop()append() 操作的原因。在这种情况下,可以按照上面代码中的建议使用pop(0)。此外,我确实尝试输入长度均匀的输入,它似乎工作正常,即当 curr 等于 n//2 时删除元素。
    • 不客气!很高兴听到!关于偶数部分——你确定吗? delete_middle([2,4,1,2,18,34],6,0) 结果 2, 4, 2, 18, 34,例如delete_middle([2,4,1,2,18,34],5,0)) 结果2, 4, 1, 18, 34,这就是为什么(仅在我看来)删除这两个1,2 可能会更好,但最终你是决定如何最好的人。如果您选择答案,也将不胜感激。干杯!
    • 明白你的意思。我现在可以看到了。但根据要求,一个元素就可以了。感谢您的详细帮助。我今天学到了有用的概念。有没有一种计算时间复杂度的方法来检查一个集合是否包含在另一个集合中?我在这里有一个问题; stackoverflow.com/questions/61151175/…
    猜你喜欢
    • 1970-01-01
    • 2012-11-13
    • 1970-01-01
    • 2014-11-09
    • 2013-11-07
    • 2021-05-27
    • 2019-05-20
    • 1970-01-01
    • 2013-08-27
    相关资源
    最近更新 更多