如何向后阅读单链表?

时间:2009-07-12 19:35:03

标签: c# singly-linked-list

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

}

13 个答案:

答案 0 :(得分:31)

要使用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;
    }
}

答案 1 :(得分:10)

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

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

答案 2 :(得分:8)

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

如果无法做到这一点,我认为您最好的选择是构建一个已被撤消的列表副本。

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

答案 3 :(得分:3)

如果内存不足,您可以反向列表,迭代它并再次反转。或者,您可以创建一堆指向节点的指针(或者像C#中的指针一样)。

答案 4 :(得分: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
}

答案 5 :(得分:2)

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

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

  1. 将列表拆分为两个
  2. 进入列表的后半部分
  3. 在中点打印值
  4. 进入上半场
  5. 这是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)
    

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

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

    Solution   Memory       Performance
    =========================================
     Marc #1     4MB    1 million operations
      Mine       80B    20 million operations
     Marc #2      4B    1 trillion operations
    

答案 6 :(得分:1)

假设您的单链表实现IEnumerable&lt; T&gt;,您可以使用LINQ的反向扩展方法:

var backwards = singlyLinkedList.Reverse();

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

答案 7 :(得分:1)

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

  1. 它显示你grok recursion
  2. 代码少,看起来更优雅
  3. 天真的面试官可能没有意识到存在空间开销(如果是这种情况,您可能需要考虑是否要在那里工作)。

答案 8 :(得分: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

答案 9 :(得分: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;
}

}

答案 10 :(得分:0)

如果在Explicit Stack程序中,我们只为每个节点的数据创建一个堆栈(而不是创建类型为<Node>的堆栈,我们创建类型为<T>的堆栈)不会#n;甚至更好?因为我们不需要存储Node的任何其他信息。

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

答案 11 :(得分:-1)

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

答案 12 :(得分:-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)记忆和简单就像做了一样!