递归地反转链接列表,有人可以引导我通过吗?

时间:2013-06-17 23:07:17

标签: java recursion

public void reverse(Node curr) {
      if (isEmpty()) {
          return;
      }

      if (curr.next == null) {
          head = curr;
          return;
      }

      reverse(curr.next);
      curr.next.next = curr;
      curr.next = null;

  }

我正在努力学习递归..我最薄弱的一点,我只是不明白它是如何工作的......我看到它从第二个节点开始......然后在下一个节点旁边,当前,这是链接null ...我只是不明白。抱歉新问题。我会添加更多我理解的东西..但不幸的是,这真的是关于它..

5 个答案:

答案 0 :(得分:6)

我将完全忽略您的示例代码,因为我也不理解它。这不是思考IMO这个问题的最简单方法。让我们在概念上一起思考:

  1. 如果有人通过空列表会怎么样?你应该返回一个空列表
  2. 如果有人返回一个元素列表会怎么样?您应该返回
  3. 中传递的内容
  4. 如果有人返回两个或更多的列表会怎样?您获取列表中的 last 元素并将其放在前面,然后调用last + reverse(listWithLastRemoved)

  5. 让我们用两个列表来试试这个。它中有[a, b]个。你传入它并执行第3步。结果它确实:

    b + reverse([a])
    

    忽略左侧一秒钟。 reverse([a])将返回[a],因为它与第2步匹配。这可以评估为

    b + [a]
    

    这可以评估为

    [b, a]
    

    请注意这是伪代码。我正在使用+来暗示在列表的前面添加元素。


    让我们再试一次,但这次有3个元素,[a b c]

    [a b c] #apply step #3
    c + reverse([a b])  #apply step #3
    c + b + reverse([a]) #apply step #2
    c + b + [a] #add b to [a]
    c + [b a] #add c to [b a]
    [c b a]
    

    正如您所指出的,要获得最后一个元素,您必须“迭代”列表以找出最后的内容。听起来你必须使用for循环(这不会是递归的),但是也可以递归地完成最后一次循环。实际上,它的算法比reverse更简单,并且从一开始就是一个更好的练习。我将解释getLast的步骤:

    1. 如果有人通过空列表会怎么样?你应该返回null
    2. 如果有人返回一个元素列表会怎么样?你应该返回第一个元素
    3. 如果有人返回两个或更多的列表会怎样?您删除列表的第一个元素,然后将其余元素传递到getLast
    4. 让我们试试[a b c]

      [a b c] #apply step #3
      getLast([b c]) #apply step #3
      getLast([c]) #apply step #2
      c
      

      注意最后一步返回一个元素,而不是一个列表。

答案 1 :(得分:2)

这里有一些伪代码来说明解决方案

// method takes a list and returns the list reversed
// it will corrupt the existing list so do not use it
// after calling this method.
// works by finding the last node and building a list from there
public Node reverseList(Node list){
    // have we found the last node yet?
    if (list.isLast()){
        return list;
    } else {
      // reverse the list after the current node
      Node newList = reverseList(list.next());
      // add current node to end of new list
      addToLast(list,node);
      // return new list
      return newList;
    }

}

public void addToLast(Node list, Node newNode){
    // add newNode to the end of list.
}

答案 2 :(得分:1)

想到这一点的一种方法是用一个小例子。首先,两个边缘情况:

  1. 如果列表为空,则已经反转(平凡)
  2. 如果curr是单个节点,那么它就是它自己的逆转(再次,平凡);因此将当前列表头设置为curr
  3. 假设这些都没有,那么棘手的是弄清楚最后一段代码是如何工作的。据推测,整个过程都是通过调用reverse(head)启动的。让我们看看两元素列表会发生什么:

    head = curr(0)
      |
      V
    +---+  +---+
    | 0 |->| 1 |->null
    +---+  +---+
    

    由于每次递归调用都会创建一个新的curr,因此我使用索引注释curr以显示适用的递归级别。现在打电话给reverse(curr.next)。在该调用期间,方法开头的图片如下所示:

    head   curr(1)
      |      |
      V      V
    +---+  +---+
    | 0 |->| 1 |->null
    +---+  +---+
    

    这满足第二个边缘情况,因此该方法将图片更改为:

           head = curr(1)
             |
             V
    +---+  +---+
    | 0 |->| 1 |->null
    +---+  +---+
    

    (请注意,虽然curr是一个单元素列表,但该元素不是head。这是整个事情起作用的关键!)现在该方法返回,我们回到了第一次打电话图片现在看起来像这样:

    curr(0) head
      |      |
      V      V
    +---+  +---+
    | 0 |->| 1 |->null
    +---+  +---+
    

    第二行代码(在第三个块中)是curr.next.next = curr;。这会将图片更改为:

    curr(0) head
      |      |
      V      V
    +---+  +---+
    | 0 |->| 1 |--\
    +---+  +---+  |
      ^           |
      |           |
      \-----------/
    

    最后,它设置了curr.next = null;

    curr(0)       head
      |             |
      V             V
    +---+         +---+
    | 0 |->null   | 1 |--\
    +---+         +---+  |
      ^                  |
      |                  |
      \------------------/
    

    有点盯着它应该说服你,事实上,这个名单是相反的。

    可以使用相同的技术来查看这是如何反转更长的列表。考虑它的最简单方法是调用reverse(curr.next)将反转列表的尾部,接下来的两行将在curr上添加到列表的末尾。

答案 3 :(得分:1)

因为你要求进行一次演练:

public void reverse(Node curr) {
      if (isEmpty()) {
          return;
      }

如果已到达列表的末尾,则无需执行任何操作。

      if (curr.next == null) {
          head = curr;
          return;
      }

当参数是列表的最后一个节点(curr.next == null)时,我们将head设置为该节点,因为原始节点的最后一个节点是反向列表的第一个节点。

      reverse(curr.next);

否则,我们反转列表的尾部(当前节点之后的所有内容)。结果列表的最后一个节点是curr.next,因为这是我们在这里反转的列表的第一个节点。

      curr.next.next = curr;

我们将当前节点附加到反转列表的末尾。方便的是,我们仍然引用了它的最后一个节点,即curr.next

      curr.next = null;

我们将当前节点的next字段设置为null,因为它是反转列表的最后一个节点。

}

答案 4 :(得分:1)

起初这对我来说有点奇怪,但它确实很有道理:

public void reverse(Node curr) 
{
      if (isEmpty()) //obvious 
      {
          return;
      }

      if (curr.next == null) //once we get to the last element, assign it to head
      {
          head = curr;
          return;
      }

      //explained below
      reverse(curr.next); 
      curr.next.next = curr;
      curr.next = null;  
  }

让我们通过考虑我们在倒数第二个元素上的情况来分析最后三行。

我们致电reverse(curr.next),因为在该电话curr.next == null中,我们head = curr,在这种情况下curr是最后一个元素,然后返回。

现在,在curr.next.next = currcurr再次成为原始列表中倒数第二个元素。

curr.next是原始last元素和curr.next.next = curr将原始列表中的倒数第二个元素指定为原始列表中最后一个元素的下一个元素。

curr.next = null然后在列表next中生成null元素(第三个)。我们重复,因此向后增加列表(最后一个元素将是null)。

如果这仍然没有意义,请尝试抓住三个对象:笔,书,你有什么,用一个代表curr,一个代表curr.next,一个代表{{1} }} / curr.next.next。如果您跟踪调用并使用真实对象来帮助您跟踪事物,这可能会有所帮助。希望这可以解决问题。祝你好运。