使用递归以恒定空间和线性时间向后打印链接列表

时间:2013-09-04 15:12:41

标签: c++ c recursion tail-call-optimization

在我看来,应该可以使用递归和尾调用优化在恒定空间和线性时间内向后打印循环链表。但是,由于在进行递归调用后尝试打印当前元素,我遇到了困难。通过检查反汇编,我看到函数被调用而不是跳转到。如果我将其更改为向前打印而不是向后打印,则可以正确消除函数调用。

我看过this相关问题,但我特别感兴趣的是使用递归和TCO解决它。

我正在使用的代码:

#include <stdio.h>

struct node {
    int data;
    struct node *next;
};


void bar(struct node *elem, struct node *sentinel)
{
    if (elem->next == sentinel) {
        printf("%d\n", elem->data);
        return;
    }
    bar(elem->next, sentinel), printf("%d\n", elem->data);
}

int main(void)
{
    struct node e1, e2;
    e1.data = 1;
    e2.data = 2;
    e1.next = &e2;
    e2.next = &e1;
    bar(&e1, &e1);
    return 0;
}

并使用

进行编译
    $ g++ -g -O3 -Wa,-alh test.cpp -o test.o

更新:使用Joni的答案解决了一些循环列表的轻微修改

void bar(struct node *curr, struct node *prev, struct node *sentinel,
    int pass)
{
    if (pass == 1) printf("%d\n", curr->data);
    if (pass > 1) return;
    if ((pass == 1) && (curr == sentinel))
        return;

    /* reverse current node */
    struct node *next = curr->next;
    curr->next = prev;

    if (next != sentinel) {
        /* tail call with current pass */
        bar(next, curr, sentinel, pass);
    } else if ((pass == 1) && (next == sentinel)) {
        /* make sure to print the last element */
        bar(next, curr, sentinel, pass);
    } else {
        /* end of list reached, go over list in reverse */
        bar(curr, prev, sentinel, pass+1);
    }
}

2 个答案:

答案 0 :(得分:5)

更新:这个答案有误导性(请注意它!),只有在你无法修改数据结构时才会这样。

这是不可能的。递归和常数空间是这项任务中相互矛盾的要求。

我知道您希望使用TCO,但是您无法在递归调用之后执行的额外工作。

来自维基百科http://en.wikipedia.org/wiki/Tail_call

  

在计算机科学中,尾调用是发生的子程序调用   在另一个程序中作为最后的行动。

答案 1 :(得分:2)

要从尾部调用优化中受益,您必须重新组织代码。这是一种方法:

void bar(struct node *curr, struct node *prev, int pass)
{
    if (pass == 1) printf("%d\n", curr->data);
    if (pass > 1) return;

    /* reverse current node */
    struct node *next = curr->next;
    curr->next = prev;

    if (next) {
        /* tail call with current pass */
        bar(next, curr, pass);
    } else {
        /* end of list reached, go over list in reverse */
        bar(curr, NULL, pass+1);
    }
}

此函数假定列表的末尾由NULL发出信号。该列表遍历两遍:首先将其原位反转,然后再打印元素并再次反转。并且,据我所知,gcc -O3执行尾调用优化,因此算法在恒定空间中运行。

要调用此函数,请使用:

bar(&e1, NULL, 0);