如何向后读取单链表?

Posted

技术标签:

【中文标题】如何向后读取单链表?【英文标题】:How to read a singly linked list backwards? 【发布时间】:2010-11-10 03:28:00 【问题描述】:

我能想到的一种方法是反转列表然后阅读它。 但这涉及更改不好的列表。 或者我可以复制列表然后反转它,但这会使用额外的 O(n) 内存。 有没有更好的方法不使用额外的内存,不修改列表并在 O(n) 时间内运行

反向链表代码在c#中是这样的

Void Reverse (Node head)

    Node prev= null;
    Node current = head;
    Node nextNode = null;

        while (current!=null)
        
            nextNode = current.Next;
            current.Next = prev;
            prev=current;
            current = nextNode; 

        
        head = prev;

   

递归解是

void ReadBackWard (Node n)

    if (n==null)
        return;
    else
        ReadBackward(n.Next);

    Console.WriteLine(n.Data);


【问题讨论】:

递归是你的朋友 @Neil:你能推荐一些使用递归的伪代码 但是递归使用 O(n) 内存 只有在使用 O(n) 额外内存的情况下,我们才能在 O(n) 时间内解决这个问题。请参阅下面的答案....谢谢大家的帮助....真的很棒,你们摇滚!!!.... 尼尔:检查我的递归实现 【参考方案1】:

要使用 O(n) 内存和 O(n) 性能,请创建堆栈;在向前迭代时推动所有内容,然后弹出所有内容,产生结果。

要使用 O(n^2) 性能(但 O(1) 额外内存),每次都向前读取,向上读取最后一个节点之前的节点。

例子:

IEnumerable<T> Reverse (Node head) 
    Stack<Node> nodes = new Stack<Node>();
    while(head != null) 
        nodes.Push(head);
        head = head.Next;
    
    while(nodes.Count > 0) 
        yield return nodes.Pop().Value;
    

【讨论】:

这相当于创建列表的反向副本。 这是一个更好的解决方案,但它使用相同的 O(n) 内存,这与拥有一个列表副本并反转它并读取它是一样的 不一定。您只需要将指针压入堆栈,而不是整个项目。 这与递归基本相同。唯一的区别是显式堆栈与递归的隐式堆栈。 通过递归,您通常还需要推送表示调用位置的附加状态。使用显式堆栈通常更有效。【参考方案2】:

单链表的特点之一是它实际上是单链表。这是一条单向的街道,除非你把它变成别的东西(比如反向单链表、堆栈、双向链表......),否则没有办法克服它。一个人必须忠实于事物的本质。

如前所述;如果您需要双向遍历列表;你需要有一个双向链表。这就是双向链表的本质,它是双向的。

【讨论】:

+1 叹息。为什么构建 SO 的人所倡导的简单性却被如此忽视?【参考方案3】:

你真的应该使用双向链表。

如果这是不可能的,我认为您最好的选择是构建一个已反转的列表副本。

如果列表太长,其他选项,例如依赖递归(有效地将列表复制到堆栈)可能会导致您用完堆栈空间。

【讨论】:

见标签 - “采访问题” :) 我认为将列表更改为双向链表不如提出其他机制来解决问题。【参考方案4】:

如果内存不足,您可以反转列表,遍历它并再次反转它。或者,您可以制作一组指向节点的指针(或任何类似于 C# 中的指针)。

【讨论】:

【参考方案5】:

还有第三种解决方案,这次使用O(log(n)) 内存和O(n log(n)) 时间,因此在 Marc 的回答中占据了两种解决方案之间的中间地带。

它实际上是二叉树 [O(log(n))] 的逆序下降,除了在每一步都需要找到树的顶部 [O(n)]:

    将列表一分为二 递归到列表的后半部分 打印中点的值 递归到前半部分

这是Python中的解决方案(我不懂C#):

def findMidpoint(head, tail):
  pos, mid = head, head
  while pos is not tail and pos.next is not tail:
    pos, mid = pos.next.next, mid.next
  return mid

def printReversed(head, tail=None):
  if head is not tail:
    mid = findMidpoint(head, tail)
    printReversed(mid.next, tail)
    print mid.value,
    printReversed(head, mid)

这可以使用迭代而不是递归来重铸,但要以清晰为代价。

例如,对于一个百万条目的列表,三个解决方案的顺序为:

解决方案内存性能 ========================================== Marc #1 4MB 100 万次操作 矿山 80B 2000 万次操作 Marc #2 4B 1 万亿次操作

【讨论】:

@chrispy:具有n 节点的树需要O(n) 内存,而不是您提到的O(log n)。我是不是理解错了? @eSKay 代码在遍历列表好像有一个关联的树,实际上并不是在内存中创建树 @Lazer:忽略“树”这个词,并从分而治之的角度思考:如果您跟踪中间点,则可以同样有效地处理列表的后半部分作为上半场。在处理列表的第一个第二个时,如果您跟踪 3/4 点,您可以像处理第三个季度一样快地处理四个季度。然后在处理前半部分时,保留 1/4 点,这样您就可以像处理第一部分一样高效地处理第二部分。 漂亮的解决方案!【参考方案6】:
void reverse_print(node *head) 

    node *newHead = NULL, *cur = head;

    if(!head) return;

    // Reverse the link list O(n) time O(1) space
    while(cur)
        head = head->next;
        cur->next = newHead;
        newHead = cur;
        cur = head;
    

    // Print the list O(n) time O(1) space
    cur = newHead;
    while(cur) 
        printf(" %d", cur->val);
        cur = cur->next;
    

    // Reverse the link list again O(n) time O(1) space
    cur = newHead;
    while(cur)
        newHead = newHead->next;
        cur->next = head;
        head = cur;
        cur = newHead;
    
    // Total complexity O(n) time O(1) space

【讨论】:

最好的反向打印算法,节省时间和空间【参考方案7】:

假设你的单链表实现了 IEnumerable,你可以利用 LINQ 的反向扩展方法:

var backwards = singlyLinkedList.Reverse();

您需要在代码文件的顶部添加一个using System.Linq; 指令才能使用 LINQ 的扩展方法。

【讨论】:

... 这正是 OP 所建议的,但想要一个更好的解决方案。仅仅因为您不自己分配额外的内存并不意味着它不会发生。 Reverse 是延迟加载的,在请求项目时执行。和OP不一样。【参考方案8】:

创建堆栈并将所有元素推入堆栈的一种变体是使用递归(以及系统的内置堆栈),这可能不是生产代码的方式,但可以作为更好的(恕我直言)采访答案如下:

    这表明你了解递归 代码更少,看起来更优雅 天真的面试官可能不会意识到有空间开销(如果是这种情况,您可能要考虑是否要在那里工作)。

【讨论】:

【参考方案9】:

好吧,天真的解决方案是跟踪您当前所在的节点,然后从头开始迭代,直到找到该节点,始终保存您刚刚离开的节点。然后每次找到当前所在的节点时,生成刚刚离开的节点,将该节点保存为当前所在的节点,然后从头开始重新迭代。

这在性能方面当然会非常糟糕。

我相信一些更聪明的人有更好的解决方案。

伪代码(甚至有错误):

current node = nothing
while current node is not first node
    node = start
    while node is not current node
        previous node = node
        node = next node
    produce previous node
    set current node to previous node

【讨论】:

【参考方案10】:

这很乱但有效:

class SinglyLinkedList 
SinglyLinkedList next;
int pos;
SinglyLinkedList(int pos) 
    this.pos = pos;

SinglyLinkedList previous(SinglyLinkedList startNode) 
    if (startNode == this) return null;
    if (startNode.next == this) return startNode;
    else return previous(startNode.next);


static int count = 0;
static SinglyLinkedList list;
static SinglyLinkedList head;
static SinglyLinkedList tail;
public static void main (String [] args) 
    init();

    System.out.println("Head: " + head.pos);
    System.out.println("Tail: " + tail.pos);

    list = head;
    System.out.print("List forwards: ");
    while (list != null) 
        System.out.print(list.pos + ",");
        list = list.next;
    

    list = tail;
    System.out.print("\nList backwards: ");
    while (list.previous(head) != null) 
        System.out.print(list.pos + ",");
        list = list.previous(head);
    

static void init() 
    list = new SinglyLinkedList(0);
    head = list;
    while (count < 100) 
        list.next = new SinglyLinkedList(++count);
        list = list.next;
    
    tail = list;

【讨论】:

【参考方案11】:

如果在显式堆栈程序中,我们只为每个节点的数据创建一个堆栈(而不是创建&lt;Node&gt; 类型的堆栈,我们创建&lt;T&gt; 类型的堆栈)不是更好吗?因为那时我们不需要存储节点的任何其他信息。

IEnumerable<T> Reverse (Node<T> head) 
    Stack<T> nodes = new Stack<T>();
    while(head != null) 
        nodes.Push(head.data);
        head = head.Next;
    
    while(nodes.Count > 0) 
        yield return nodes.Pop();
    

【讨论】:

【参考方案12】:

你可以在 O(n^2) 中读取它——每次去最后一个节点读取并打印出前一个节点

【讨论】:

【参考方案13】:

有什么问题:

    public void printBackwards(LinkedList sl)    
        ListIterator<Element> litr = sl.listIterator(sl.size());
        Element temp;
        while(litr.previousIndex() >= 0)
            temp = litr.previous();
            System.out.println(temp);
        
    

O(n) 性能,O(1) 内存,简单如 do-re-mi!

【讨论】:

投了反对票; C#中的链表实现为双向链表,OP要求单链表。

以上是关于如何向后读取单链表?的主要内容,如果未能解决你的问题,请参考以下文章

单链表循环链表双向链表的比较

数据结构与算法-线性表之循环链表

三种方法实现反转单链表

反转单链表@Leetcode--链表

单链表快速排序

单链表快速排序