C 使用递归从列表的最后一个元素中查找第n个元素

C 使用递归从列表的最后一个元素中查找第n个元素,c,haskell,recursion,tail-recursion,C,Haskell,Recursion,Tail Recursion,例如,我们可以使用静态变量在C中解决这个问题,如下面的代码段() 我想看看我是否能用尾部调用递归解决这个问题, 没有静态变量/全局变量 我试图学习Haskell想知道如何以一种纯功能的方式实现它,而不使用Haskell的length和 类似于x!!((长度x)-K) 首先,我们要问,如何在C语言中实现它,使用递归,没有静态/全局变量,只是为了了解一些情况 如有任何建议/建议,将不胜感激 谢谢。典型的迭代策略使用两个指针,在开始移动另一个指针之前运行一个指针到n-1。 使用递归,我们可以通过添加第

例如,我们可以使用静态变量在C中解决这个问题,如下面的代码段()

  • 我想看看我是否能用尾部调用递归解决这个问题, 没有静态变量/全局变量

  • 我试图学习Haskell想知道如何以一种纯功能的方式实现它,而不使用Haskell的
    length
    类似于
    x!!((长度x)-K)

  • 首先,我们要问,如何在C语言中实现它,使用递归,没有静态/全局变量,只是为了了解一些情况

    如有任何建议/建议,将不胜感激


    谢谢。

    典型的迭代策略使用两个指针,在开始移动另一个指针之前运行一个指针到
    n-1
    。 使用递归,我们可以通过添加第三个参数,使用堆栈从列表的末尾向上计数。为了保持用法干净,我们可以创建一个静态帮助函数(从这个意义上讲,它意味着只在编译单元中可见,这与具有函数作用域的静态变量是完全不同的概念)

    或者,我们实际上可以在没有额外参数的情况下进行计数,但我认为这不太清楚

    static node *nth_last_helper(node* curr, unsigned int *n) {
      node *t;
      if (!curr) return NULL;
      t = nth_last_helper(curr->next, n);
      if (t) return t;
      if (1 == (*n)--) return curr;
      return NULL;
    }
    
    node* nth_last(node* curr, unsigned int n) {
      return nth_last_helper(curr, &n);
    }
    
    请注意,我使用了无符号整数来避免为列表中的“负第n个最后一个”值选择语义


    然而,这两种方法都不是尾部递归的。为了实现这一点,您可以更直接地将迭代解决方案转换为递归解决方案,如中所示。

    链接页面说明了如何使用双指解决方案解决问题;令人惊讶的是,他们并没有简单地编写递归版本,这将是简单而清晰的。(根据我的经验,如果有一个同样有效的简单明了的版本,你就不能通过提供复杂而晦涩的代码来成功面试。但我想有些面试官会看重晦涩的代码。)

    因此,双指解决方案是基于这样一个观察结果,即如果我们有两个指针进入列表(两个手指),它们总是相隔
    n
    元素,因为我们总是将它们串联在一起,那么当前导指到达列表的末端时,尾随指将指向我们想要的元素。这是一个简单的尾部递归:

    Node* tandem_advance(Node* leading, Node* trailing) {
      return leading ? tandem_advance(leading->next, trailing->next)
                     : trailing;
    }
    
    Node* advance_n(Node* head, int n) {
      return n ? advance_n(head->next, n - 1)
               : head;
    }
    
    对于初始情况,我们需要从列表开始的N个元素作为前导指。另一个简单的尾部递归:

    Node* tandem_advance(Node* leading, Node* trailing) {
      return leading ? tandem_advance(leading->next, trailing->next)
                     : trailing;
    }
    
    Node* advance_n(Node* head, int n) {
      return n ? advance_n(head->next, n - 1)
               : head;
    }
    
    那么我们只需要把这两者结合起来:

    Node* nth_from_end(Node* head, int n) {
      return tandem_advance(advance_n(head, n + 1), head);
    }
    

    (我们最初按
    n+1
    前进,这样从末尾开始的第0个节点将是最后一个节点。我没有检查所需的语义;可能是
    n
    将是正确的。)

    在Haskell中,两个手指的解决方案似乎是显而易见的方式。如果请求的元素不存在,此版本将以各种方式出错。我将把它留给您作为一个练习来解决这个问题(提示:编写
    drop
    last
    的版本,它们返回
    可能
    值,然后将计算与
    >=
    一起串起来)。请注意,这会将列表的最后一个元素设置为从末尾算起的第0个元素

    nthFromLast::Int->[a]->a
    nthFromLast n xs=最后一个$zipWith常量xs(删除n xs)
    
    如果您想手动执行一些递归,没有任何_信号指示会提供更好的性能

    ——a和b的类型不同,这就清楚了
    --我们从哪个列表中获取值。
    lzc::[a]->[b]->a
    lzc[]\=错误“空列表”
    lzc(x:xs)[]=x
    lzc(x:xs)(y:ys)=lzc-xs-ys
    nthFromLast n xs=lzc xs(下降n xs)
    

    我们不必费心手工写出
    drop
    ,因为库中相当简单的版本是最好的。与此答案中的第一个解决方案或“反向,然后索引”方法不同,使用
    lzc
    的实现只需要分配恒定的内存量。

    我假设您的代码丢失了

    getNthFromLast(list *node_ptr, int n) {
    
    就在上面。(!!)

    递归版本在其调用堆栈帧中跟踪
    节点_ptr
    s,因此本质上是非尾部递归的。此外,它继续展开堆栈(返回调用帧堆栈),同时增加
    i
    ,并仍然检查其是否等于
    n
    ,即使在它从上一个节点找到其目标n节点之后;因此,这是没有效率的

    这将是一个迭代版本,确实可以编码为尾部递归,在前进的道路上做事情,因此可以在达到目标后立即停止。为此,我们从起点开始打开n长度间隙,而不是在到达终点之后。我们不是像递归版本那样向后计数,而是向前计数。这与前面提到的双指针方法相同

    在伪代码中

    end = head; 
    repeat n: (end = end->next); 
    return tailrec(head,end)->payload;
    
    在哪里

    tailrec(p,q) { 
        if(NULL==q): return p; 
        else: tailrec(p->next, q->next);
    }
    
    这是基于1的,假设
    n
    因为Haskell是懒惰的,所以这应该足够有效


    如果您不想使用
    ,您必须自己重新定义它,但这很愚蠢。

    嘿,Downvoter,请解释一下。我认为这个问题没有什么明显的错误,“这是第n个元素”。“这”指的是什么?我假设您正在
    getNthFromLast
    中设置
    I
    。但是它本身是如何给你第n个元素的呢?@Alan,这是本例中的“节点”。我没有打印出整个解决方案,因为它可以像这里这样轻松实现。例如:由于没有提供完整准确的信息,您的问题最终变得不清楚。为什么需要
    i
    是静态的?为什么即使对于非递归情况,也需要任何全局变量?这毫无意义
    getNthFromLast
    可以返回指向找到的节点的指针。无论实现是迭代的还是递归的,都不需要全局/静态的。那么你到底想问什么呢?谢谢大家的回复和评论。下次我一定要把问题弄清楚。我本来打算建议
    nthFromLast lst n = reverse lst !! n