【问题标题】:How can I find the largest item in a linked list recursively given the head node?给定头节点,如何递归地找到链表中的最大项?
【发布时间】:2010-02-21 06:07:49
【问题描述】:
int findLargest (ListNode *p)
// --------------------------------------------------------------------------
// Preconditions: list head pointer is passed as a parameter.
// Postconditions: returns the largest value in the linked list.
// --------------------------------------------------------------------------
{
    if (p->item != NULL)
    {
        int largest = p->item;
        if (largest > p->next->item)
            ...
    }

    ...
}

是否可以编写这个递归函数,只传递一个指针作为参数?如果不添加更多参数,我无法弄清楚如何做到这一点。有任何想法吗?我只使用顺序搜索。没什么特别的。

这是需要的类 List 的一部分:

    struct ListNode 
    {
        ListItemType item;  // A data item on the list.
        ListNode    *next;  // Pointer to next node
    }; // end ListNode

    ListNode   *head; // Pointer to linked list of items.

我主要担心问题的可行性。可以只用指针作为参数吗?

【问题讨论】:

  • 请给出List的定义...

标签: c++ recursion linked-list


【解决方案1】:

虽然 C 不需要尾递归优化,但如果您可以将其转换为尾递归(并且您可以在此处无需大量工作),那么当应用该优化时,您可以保持可读性,递归的清晰度和简洁性与最佳的非递归解决方案相同性能(时间和空间)。

我稍微修改了函数的条件,使其可以在没有节点的空列表上工作(其中 p 为空),并且在这种情况下将返回空。这是尾递归的,并且确实需要另一个参数:

ListNode* findLargestRecurse(ListNode* p, ListNode* largest) {
  // this is an implementation detail, and would not be called directly
  // requires that largest is not null, but p may be null
  if (!p) return largest;
  if (p->item > largest->item) largest = p;
  return findLargestRecurse(p->next, largest);
}

// preconditions: list head pointer is passed as a parameter, and may be null
// postcondition: returns the node with the largest value, or null
ListNode* findLargest(ListNode* p) {
  if (!p) return 0; // mark A, see below
  return findLargestRecurse(p->next, p);
}

请注意,您使用主条目 findLargest 来设置实际递归 findLargestRecurse 的初始条件/上下文。

但是,我会将尾递归编写为一个迭代的 while 循环,以减少对当前非常特定于编译器且在 C 中通常不熟悉的内容的依赖,这并不难做到:

// preconditions: list head pointer is passed as a parameter, and may be null
// postcondition: returns the node with the largest value, or null
ListNode* findLargest(ListNode* p) {
  ListNode* largest = 0;
  for (; p; p = p->next) {
    if (!largest || p->item > largest->item) {
      largest = p;
    }
  }
  return largest;
}

您可以像第一个 findLargest 一样检查基本情况(“标记 A”),提前将!largest 条件从循环中分离出来。

现在看这个,你可能想知道为什么我把递归版本称为更简洁:它真的不适合这个微不足道的例子。这部分是因为 C 旨在支持迭代(特别注意 for 循环),部分是因为我试图在递归中变得冗长而不是像往常那样将其压扁(这样你就可以更容易理解它),其余的是因为这只是一个简单的问题。

【讨论】:

  • 你的递归解决方案令人困惑——你实际上并不需要那个累加器,它也没有增加任何清晰度。 Matthieu 的答案也是尾递归的,不需要那个额外的参数。
  • @Konrad:Matthieu 的回答是不是尾递归的。请注意,它对递归调用的结果做了一些事情。
【解决方案2】:

我发现大多数递归问题都可以使用思考的框架/模板来解决:

  • 我现在手头有什么信息?
  • 如果我进行递归调用,我会得到什么信息?
  • 如何将这两者结合起来得出最终结果?
  • (另外,请务必清楚“基本情况”。)

在这种情况下,答案是

  • 当前节点中的值
  • 此节点之后的列表“后缀”中的最大元素
  • 嗯,这个你自己想办法
  • (空列表应该返回什么?作业有说吗?)

玩得开心。 :)

【讨论】:

  • 对于空列表它没有说。也许我应该设置一个前提条件是至少有一个项目或者抛出一个异常。
  • 如果这是例如一个非负整数列表,最简单的方法是为空列表返回一个负值(这将使“组合”步骤更容易编码)。
【解决方案3】:

我看到一些代码贴出来,忍不住添加我自己的......因为我真的认为它可以做得更简单:)

我想item 是数字类型。

#include <algorithm> // std::max
#include <limits>    // std::numeric_limits

ListItemType findLargest(const ListNode* p)
{
  if (p == 0)
    return std::numeric_limits<ListItemType>::min();
  else
    return std::max(p->item, findLargest(p->next));
}

如您所见,简单得多,我冒昧地添加了const,因为我们当然不必修改列表本身!

【讨论】:

  • 记住你是如何被告知要避免在语法课上出现连续句子的,因为它们太啰嗦了,你无法确切地知道发生了什么,而三元组可能会很好地将它们全部放在一起行,但谁在乎行,使用条件运算符确实不会有任何改进,因为这很好。
  • 我是第二个罗杰。除了bool i; std::cout &lt;&lt; "foo is " &lt;&lt; (i ? " not": "") &lt;&lt; "a muppet";,我很少使用三元运算符。目标不是尽量减少行数(这是一个平均值),而是尽量减少阅读和分析(维护)所花费的时间。
【解决方案4】:

这绝对是可行的,虽然我同意递归不是解决这个问题的最佳方案。在这种情况下,非递归代码会更容易阅读(递归)、更快(函数调用的开销)和更高的内存效率(显然更多的堆栈帧)。

每个递归调用都返回它的值或列表其余部分的值中的较大者。

int findLargest (ListNode *p) {
    int current = p->item;
    int next;

    if (p->next == NULL) {
        //The value at this node is obviously larger than a non-existent value
        return current;
    } else {
        //Recur to find the highest value from the rest of the LinkedList
        next = findLargest(p->next);
    }

    //Return the highest value between this node and the end of the list
    if (current > next) {
        return current;
    } else {
        return next;
    }
}

next 项为空时,递归停止。

【讨论】:

  • 我不同意您对所有帐户的递归解决方案的描述。对于这个固有的递归问题,递归无疑是最惯用的、直接的解决方案。它也非常可读(可以说比迭代变体要好得多)。任何现代 C++ 编译器都会完全删除递归函数调用,因此没有开销——因此递归解决方案在时间和内存方面也是高效。另一方面,您的递归解决方案实际上具有您提到的所有缺点。
  • 考虑到这是一个家庭作业问题,我写我的文章是为了尽可能地可读。您对这个相同算法的单行实现如何实现不同的运行时特性?我从未学习过 CS(编译器),也从未专业地编写过 C/C++ 代码。
  • 你的代码的问题是它没有很好地显示递归结构。递归总是由两部分组成,基本情况和递归。浏览您的代码并不清楚它们在哪里。而我的实现直接对应于问题的数学公式(不是解决方案:问题!)。你的代码的冗长掩盖了底层算法,而不是澄清它。尤其是 cmets,不添加任何解释(第一个除外)。至于性能,请考虑 Roger 的第一个实现,它是尾递归(=> 维基百科)。
  • 此外,您最初的评论(“我同意递归不是解决此问题的最佳解决方案”)表明您的代码无法直接表示问题的结构,因为递归解决方案 在这里是合适的:一个好的递归解决方案只需要解释问题来解决它(再次查看我的答案,它带来了数学问题的表述和递归实现面对面)。
【解决方案5】:

Java 版本

return max(head, head.value);
int max(Node node, int currentMax)
{
    if(node==null)
        return currentMax;
    if(node.value>currentMax)
        return max(node.next, node.value);
    else
        return max(node.next, currentMax);
}

【讨论】:

    【解决方案6】:

    如果您只想返回最大值,那么是的,您几乎已经编写好了。

    int FindLargest(ListNode* node){
      if (node != NULL){
        int downTheListLargest = FindLargest(node->next);
        if (downTheListLargest > node->item){
          return downTheListLargest;
        }
        return node->item;
      }
      return //?? some max negative value
    }
    

    如果要返回指向最大节点的指针,则参数需要是双指针 (**),或者函数需要返回指针。

    ListNode* FindLargest(ListNode* node){
      if (node == NULL){
        return NULL;
      }
      ListNode* downTheListLargestNode = FindLargest(node->next);
      if (downTheListLargestNode && downTheListLargestNode->item > node->item){
        return downTheListLargestNode;
      }
      return node;
    }
    

    确实没有理由递归地执行此操作。我认为这只是一个学习递归的练习,但我觉得这是一个糟糕的学习示例。

    【讨论】:

    • 这不起作用(它会递归直到 node==NULL,然后 downTheListLargest 被取消引用并崩溃)。这也被标记为家庭作业。
    • 好的 - 修复了列表结束问题。我在家庭作业问题上看到了两种思想流派 1) 无论如何都要回答它,所以堆栈溢出已经回答了这个问题 - 或者 - 2) 尝试教育学生而不是分发答案......它应该是哪个?
    【解决方案7】:

    这是另一个惯用的递归解决方案,类似于 Matthieu 的解决方案。与他的解决方案不同,这需要一个空列表——可以说,从一个空列表中取出最小的项目并不是一个有意义的操作:

    // Precondition: list is non-empty.
    int find_largest(ListNode const* n) {
        assert(n != 0 && "find_largest requires non-empty list.");
        return n->next == 0 ? n->item
                            : max(n->item, find_largest(n->next));
    }
    

    这个读起来很像一个数学定义,使用“案例”符号:

                 { item(i),                   if i is the last node
    largest(i) = {
                 { max{item(i), largest(i+1)} else.
    

    【讨论】:

    • 被我的代表数 10 倍(甚至更多)的人引用我有点自豪至少,一个断言)。
    • @Matthieu:你当然是对的。我没有加入断言,因为 C++ 的内置 assert 宏是一个非常弱的机制,我在实践中使用了其他技术。
    • 对空列表进行操作可能不是有意义的操作,但从数学上讲,将其作为 已定义 操作是很方便的,而不是在调用之前必须不断检查的先决条件.
    • 另一方面,我个人认为前提条件的注释对于示例代码来说非常好(所以完全同意你的观点),但我有时会在示例代码中使用断言(甚至 C 的有限断言)作为更好技术的占位符。
    • 我是个挑剔的人 :) 我还发现 assert 真的很弱,并在实际代码中使用定制的宏来做更多的事情(行和文件,抛出异常而不是中止,携带信息,...)
    【解决方案8】:

    不需要递归,你的例子不是递归(它必须调用自己)。

    可以只用一个指针作为参数来做到这一点。

    提示:将 p 分配给 p->next 以在列表中前进。

    【讨论】:

      【解决方案9】:

      始终将递归问题分为两个步骤:停止条件和“问题的其余部分”。首先考虑停止条件。在链表中,它通常是空节点。但是在您的情况下,请考虑给定节点为空时会发生什么。你会返回什么?事实是无论如何你都必须返回最大值,当列表中没有元素时,有 no 最大元素。在这种情况下,也许你可以假设一个列表因此必须至少有一个元素。

      那么停止条件是什么?停止条件是列表中有一个元素时;在这种情况下,最大值是该节点的值。

      下一步是递归步骤。假设您有一个链接到列表的元素。并注意我如何描述链表:链接到链表的节点。最大值是该节点的值,如果它大于列表的最大值,否则为列表的最大值。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-09-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-19
        • 1970-01-01
        • 2013-11-13
        相关资源
        最近更新 更多