【问题标题】:Reversing a singly linked list without using any pointers在不使用任何指针的情况下反转单链表
【发布时间】:2013-07-23 20:59:09
【问题描述】:

当我说不使用任何指针时,我的意思是我们仍然使用'next'指针字段来遍历列表,但在反转链表时不会更改它们。

据我所知,似乎有办法做到这一点:

  • 一种方法是在不更改指针本身的情况下反转节点中的数据。
  • 第二种方法是创建一个与原始链表相反的新链表。

如果有人能帮助我了解如何继续第一种方法,我将不胜感激。也欢迎其他方法。

【问题讨论】:

标签: c pointers linked-list


【解决方案1】:

今天在工作中,我一直在试图理解这个问题。我发现您的限制有些莫名其妙,因此“您尝试过魔法吗?”评论。最终我克服了障碍......

这可能有助于可视化问题。让我们从 Julio Moreno 的代码中的想法开始,稍微简化一下:遍历列表并将每个节点数据与尾部数据交换。

A B C D E
E B C D A
E A C D B
E A B D C
E A B C D

E D B C A
E D A C B
E D A B C

E D C B A

(在工作中我断定这个过程不会成功,但现在我有更多时间可以看到它了)。如果我们再仔细看看他的函数,我们可以看到除此之外,它还可以递归地工作。我并不热衷于可视化从 for 循环调用的递归函数。这个过程显然不会因效率而赢得任何奖励。

那么让我们看看我们可以做什么如果我们不想限制自己不修改节点位置:

A B C D E
  B C D E A
    C D E B A
      D E C B A
        E D C B A

这里我们取尾节点 E,并记住它。我们现在将节点 A 插入到 E 之后,然后将 B 插入到 E 之后但在 A 之前,逐步遍历整个列表,立即插入 E 之后,直到 E 成为列表的第一个节点(头)。它有效,但我们不允许这样做。

让我们假装更进一步,假设它是一个双向链表,我们维护两个指针,一个在列表的开头,一个在结尾,我们交换两者的数据,然后递增一个和分别递减另一个。

A B C D E
E B C D A
E D C B A

已经完成了!

那么我们如何使用单链表来做到这一点呢?我们需要知道什么?我们如何在退步的同时又前进?

让我们从如何通过遍历整个列表来获取最后一个节点开始。

A B C D E F G H

然后交换它们:

H B C D E F G A

然后如果我们记得我们交换了数据的两个节点,我们可以从 B 开始并逐步前进,直到 node->next 指向现在保存数据 A 的节点。

B C D E F G

并交换它们:

G C D E F B
  F D E C
    E D 

但是,我仍然对重复遍历列表的想法感到不舒服 - 即使步进的范围在每次迭代过程中都会缩小。如果我们有一个 LIFO(后进先出)或其他已知的堆栈怎么办?

A B C D E F G H
  B C D E F G H ... A
    C D E F G H ... B
      D E F G H ... C
        E F G H ... D
          F G H ... E
            G H ... F
              H ... G...F...E...D...C...B...A

但这是辅助数据存储,我们不允许这样做,但不难看出如何使用递归函数调用和链表来实现 LIFO。那么我们如何通过递归函数调用和链表来前进和后退呢?我们不需要额外的参数吗?当我们到达列表的末尾时,我们仍然需要知道它是如何开始的。

A B C D E F G H
A,B             ^ return 0
  A,C           ^ return 0
    A,D         ^ return 0
      A,E       ^ swap D E done, return 0
        A,F     ^ swap C F return D
          A,G   ^ swap B G return C
            A,H ^ swap A H return B

我还没有实际测试过这一点来证明它,所以它可能是错误的。我现在就去测试它,如果需要可以发布代码。希望我不必编辑这篇文章就说它不起作用;-)

编辑:可以确认它有效。

static lnode* list_private_reverse(lnode* list, lnode* node)
{
    lnode* next = node->next;

    if (next)
    {
        lnode* swap = list_private_reverse(list, next);

        if (swap)
        {
            int c = swap->c;

            swap->c = node->c;
            node->c = c;

            if (swap->next == node || swap->next->next == node)
                return 0;

            return swap->next;
        }
        return 0;
    }
    else
    {
        int c = node->c;
        node->c = list->c;
        list->c = c;
    }

    return list->next;
}

lnode* list_reverse(lnode* list)
{
    list_private_reverse(list, list);
    return list;
}

list_private_reverse 的调用次数与列表中的元素数量相同。

【讨论】:

  • 我无法得到更好的解释。万分感谢! :)
【解决方案2】:

这个问题与任何其他反转问题没有太大区别。一种解决方案是遍历列表,并将每个数据项放入一个数组中。然后,再次遍历列表,但从数组的末尾开始,然后用数组中的值以相反的顺序覆盖列表数据。

for (n in list) arr[i++] = n->data
for (n in list) n->data = arr[--i]

如果您不允许存储任何内容,那么递归也将被淘汰,因为它充当辅助数组来存储您的反向列表。那么一个愚蠢的解决方案是实现对列表的随机访问接口:

Node * nth_list_item (Node *list, int n);

返回列表的nth 项。然后,您使用此接口反转列表,允许像数组一样访问它(当然会有时间损失)。反转不再需要 O(n) 时间,但现在是 O(n2)。


如果递归没问题,那么为了满足“不存储元素”要求的精神,您需要递归遍历列表,然后在展开时反转列表。这可以通过允许递归调用的另一个参数来提供在递归调用从末尾展开时遍历列表开头所需的指针来实现。此实现在每次递归调用时不使用额外的局部变量,仅使用参数中提供的变量。

void swap_left (node *a, node *b, int tmp) {
    a->data = b->data;
    b->data = tmp;
}

void reverse_recursively (node *cur, node **tail) {
    if (cur) {
        reverse_recursively(cur->next, tail);
        if (*tail) {
            swap_left(cur, *tail, cur->data);
            if (cur != *tail) *tail = (*tail)->next;
            if (cur == *tail) *tail = 0;
        }
    }
}

void reverse (node *list) {
    reverse_recursively(list, &list);
}

如果允许我们在使用递归时侵犯“不存储元素”要求的精神,那么就有一个更直接(但更占用空间)的解决方案。基本上,可以在递归遍历它的同时创建反向链表的副本。当到达它的末尾时,可以再次遍历列表,从反向副本中复制元素。

#define make_node(n, d) (node){ n, d }

void reverse_recursively (node *list, node *cur, node copy) {
    if (!cur) {
        for (cur = © cur; cur = cur->next, list = list->next) {
            list->data = cur->data;
        }
        return;
    }
    reverse_recursively(list, cur->next, make_node(&copy, cur->data));
}

void reverse (node *list) {
    if (list == 0) return;
    reverse_recursively(list, list->next, make_node(0, list->data));
}

【讨论】:

  • 是的,这是一个解决方案。我们可以在不存储元素的情况下做到这一点吗?
  • 允许递归。但是将元素存储在数组中不是。 (我知道我说得很具体。只是我正在准备面试。):)
  • @Jack:递归只是一种迭代机制,具有隐式的辅助存储区域(每次递归函数调用时的本地激活记录)。尝试实现递归解决方案。如果它不起作用并且您不知道为什么,请发布该问题而不是这个问题。
  • 在没有局部变量的递归函数上做得很好......但是没有添加额外的函数调用和参数只是真正将局部变量隐藏为参数?
  • @JamesMorris:是的,但正是 OP 提出了“递归正常,存储元素不正常”的要求。所以,我试图理解这一点。
【解决方案3】:

这样会起作用,第一个函数实际上并没有切换(抱歉名称令人困惑),它的工作原理很像插入算法,它获取列表的最后一个元素并将其插入“当前”位置。我严重怀疑这个算法的效率:

    typedef struct node node;
    struct node {
        node *next;
        void *value;
    };
    typedef node linked_list;

    node *switch_with_end(node *c)
    {
        if (c->next) {
            node *t = switch_with_end(c->next);
            void *temp = t->value;
            t->value = c->value;
            c->value = temp;
        }
        return c;
    }

    void reverse_list(linked_list *l)
    {
        node *c;
        for (c = l; c->next; c = c->next)
            switch_with_end(c);
    }

【讨论】:

    【解决方案4】:

    @ames Morris——解释得非常好。但是你的代码和我的代码有什么区别......

    最多使用 2 个指针 ...

    Node* reverseLL (Node *curr, Node *prev)
    {
      Node *nxt = NULL;
      if (curr)
      {
        nxt = curr->next;
        curr->next = prev;
        curr = reverseLL (nxt, curr);
      }
      else
        return prev;
    }
    
    void reverseList (Node **head)
    {
      Node *curr = *head;
      Node *prev = NULL;
      curr = reverseLL (curr, prev);
      *head = curr;
    }
    

    【讨论】:

      【解决方案5】:

      实现一个堆栈并使用它来反转链表。堆栈是先进后出数据结构 (FILO)。在第一次迭代时将链表的内容压入堆栈,然后在第二次迭代时将它们弹回链表中。

      一旦你实现了一个堆栈,它就能很好地解决大部分的反转问题。

      当然,您会使用更多空间,这意味着它不是原位的。

      【讨论】:

        【解决方案6】:
         //Reversal of linked list without using pointers.
        void reverseList(ListNode* head) {
            if(head == NULL || head->next == NULL)
                return head;
            reverseList(head->next);
            //save the current value
            int val = head->val;
            ListNode *temp = head;
            // shift all the values to the left
            while(temp->next != NULL)
            {
                temp->val = temp->next->val;
                temp = temp->next;
            }
            // assign the save value at the end of the list
            temp->val = val;
            return;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-04-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-25
          • 2010-12-20
          相关资源
          最近更新 更多