微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如果一个Queue有一个链表,但是这个链表只有一个head的引用,那么入队和出队的运行时间是多少?

如何解决如果一个Queue有一个链表,但是这个链表只有一个head的引用,那么入队和出队的运行时间是多少?

如果一个队列是用链表实现的,但链表只有一个 对 head 的引用,enqueue 的运行时间是多少? 出队的运行时间?

解决方法

让我们从 Wikipedia's article on Queue 的引用开始:

FIFO 队列有几种有效的实现方式。一种有效的实现是可以在 O(1) 时间内执行入队和出队操作。

  • 链接列表

    • [...]
    • 常规单向链表只有在一端具有高效的插入和删除功能。 [...]

这真的是本质。由于入队和出队操作会影响列表两端的列表,而我们只有这两端之一的引用,我们将不得不“走”到另一端才能在那里执行其他操作。

我们可以选择链表的哪一端作为项目入队(添加)的一端,而另一端作为项目出队(提取)的一端。链表的 head 可以是——这是一个设计选择。

假设enqueue会在链表的末尾添加一个条目(在链表的当前尾部之后),并且dequeue 将返回头条目,并从列表中删除该节点。那么代码会是这样的——我以 Python 为例:

class Node:
    def __init__(self,value):
        self.value = value
        self.next = None

class Queue:
    def __init__(self):
        self.head = None

    def enqueue(self,value):
        if self.head is None:
            self.head = Node(value)
        else:
            node = self.head
            while node.next is not None:
                node = node.next
            node.next = Node(value)
    
    def dequeue(self):
        if self.head is None:
            raise ValueError("Queue is empty")
        else:
            value = self.head.value
            self.head = self.head.next
            return value 

所以对于enqueue,我们需要遍历列表,从head节点开始,找到列表的尾部在哪里。然后我们为给定的值创建一个节点并将它链接到该尾节点之后,使列表更长一个条目。由于我们需要遍历整个列表才能执行加法,因此时间复杂度为 O(n)。

对于dequeue,我们立即得到了需要获取值的节点:它是head节点。所以这里不需要遍历列表。我们只需更改 head 引用,以便它引用列表中的下一个节点,并从中删除第一个节点。此操作的时间复杂度为 O(1)。

现在,我们还可以采取替代设计决策,其中链表将向另一个方向增长,enqueue 将在当前 head 节点之前添加一个新节点。 dequeue 方法然后必须获取尾节点中的值并从列表中删除该尾节点。如您所见,现在 enqueue 操作的时间复杂度为 O(1),而 dequeue 的时间复杂度为 O(n)。

所以无论哪种方式,两者中都会有一个的时间复杂度为 O(n),而另一个的时间复杂度为 O(1)。

,

实际上可以构建一个由链表支持的队列,该队列只存储指向列表头部的指针,但入队和出队都需要时间 O(1)。这个想法是使用一个循环链接的双向链表,使得列表的第一个元素向后指向最后一个元素,列表的最后一个元素向前指向第一个元素。例如:

head
 |    +---+     +---+     +---+     +---+
 +--> | 1 | <-> | 2 | <-> | 3 | <-> | 4 |
      +---+     +---+     +---+     +---+
        ^                             ^
        |                             |
        +-----------------------------+     

要在 O(1) 时间内执行出队,我们只需删除并返回第一个链表单元格。正如我们所做的那样,我们在它之前和之后重新连接单元格以相互指向,如下所示:

head -------------+
                  |
                  v
                +---+     +---+     +---+
                | 2 | <-> | 3 | <-> | 4 |
                +---+     +---+     +---+
                  ^                   ^
                  |                   |
                  +-------------------+    

要在 O(1) 时间内执行入队,请在第一个元素和最后一个元素之间插入一个新值,如下所示:

head -------------+
                  |
                  v
                +---+     +---+     +---+
                | 2 | <-> | 3 | <-> | 4 |
                +---+     +---+     +---+
                  ^                   ^
                  |       +---+       |
                  +-----> | 5 | <-----+   
                          +---+

我会将实际的指针杂耍细节留给您来整理,但它们非常简单,可以说比常规链表队列更容易,因为要检查的边界条件更少。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。