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