12

我能想到的一种方法是反转列表然后阅读它。但这涉及更改不好的列表。
或者我可以复制列表然后反转它,但这会使用额外的 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);

}
4

13 回答 13

32

要使用 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;
    }
}
于 2009-07-12T19:42:04.773 回答
11

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

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

于 2009-07-12T19:51:15.113 回答
8

真的,您应该使用双向链表。

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

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

于 2009-07-12T19:41:35.803 回答
3

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

于 2009-07-12T19:43:54.783 回答
3

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

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

  1. 将列表一分为二
  2. 递归到列表的后半部分
  3. 打印中点的值
  4. 递归到上半场

这是 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 万亿次操作
于 2009-07-21T17:24:05.730 回答
3
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
}
于 2011-04-07T02:21:46.283 回答
1

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

var backwards = singlyLinkedList.Reverse();

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

于 2009-07-20T18:19:51.303 回答
1

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

  1. 它表明你了解递归
  2. 它的代码更少,看起来更优雅
  3. 一个天真的面试官可能没有意识到有空间开销(如果是这种情况,您可能要考虑是否要在那里工作)。
于 2009-07-20T18:23:56.677 回答
0

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

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

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

伪代码(甚至有错误):

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
于 2009-07-12T19:39:12.967 回答
0

这很混乱但有效:

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;
}

}

于 2009-07-20T18:08:19.610 回答
0

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

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();
    }
}
于 2016-12-22T11:37:22.467 回答
-1

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

于 2009-07-12T19:40:05.103 回答
-1

有什么问题:

    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!

于 2010-11-26T10:09:09.073 回答