实现一个队列,其中push_rear()、pop_front()和get_min()都是常数时间操作。

84
我遇到了这个问题:实现一个队列,使得push_rear()、pop_front()和get_min()的时间复杂度都为常数级别。 我最初想到使用最小堆数据结构来实现get_min(),因为它的复杂度为O(1)。但是push_rear()和pop_front()的复杂度将会是O(log(n))。
有人知道如何实现这样的队列吗?需要同时实现O(1)的push()、pop()和min()?
我搜索了一下,想指出这个Algorithm Geeks的帖子,但似乎没有一种解决方案能够满足所有3个方法的常数时间规则:push()、pop()和min()。
谢谢大家的建议。

你是否可以接受摊销 O(1) 时间复杂度的限制,还是这些必须是最坏情况下的时间复杂度? - templatetypedef
好的,现在看一下Kdoto在下面的回答;我现在确定最坏情况下的边界可能不是可能的事情。所以也许谷歌员工必须寻找摊销O(1)。编辑:好的,正如templatetypedef在Kdoto的答案评论中指出的那样,证明是不正确的。注意到了。 - bits
不要那么肯定,我的证明是不正确的。然而,我认为并没有找到所有操作的O(1)时间复杂度,无论是摊还的还是非摊还的。而且我怀疑这是不可能的。 - Olhovsky
@Kdoto- 我认为是这样的,因为我想出了一种可能的实现方式。 :-) 你能检查一下我的逻辑是否正确吗? - templatetypedef
可能是[在O(1)时间内插入、删除、获取最大值的算法]的重复问题。(https://dev59.com/bnA75IYBdhLWcg3wK1wp) - sdcvvc
显示剩余3条评论
11个回答

110
你可以使用O(1)的pop()、push()和get_min()实现一个栈:只需将当前最小值与每个元素一起存储。例如,堆栈[4,2,5,1](顶部为1)变为[(4,4), (2,2), (5,2), (1,1)]
然后,使用两个栈来实现队列。向一个栈推送,从另一个栈弹出;如果在弹出期间第二个栈为空,则将所有元素从第一个栈移动到第二个栈中。
例如,对于一个pop请求,将第一个栈中的所有元素[(4,4), (2,2), (5,2), (1,1)]移动,第二个栈将是[(1,1), (5,1), (2,1), (4,1)]。现在从第二个栈返回顶部元素。
要找到队列的最小元素,请查看各个最小堆栈的最小两个元素,然后取这两个值中的最小值。(当然,如果其中一个栈为空,则需要一些额外的逻辑,但这不太难解决)。
它将具有O(1)的get_min()push()以及平摊O(1)的pop()

12
使用两个栈来实现队列,如何得到摊销复杂度为 O(1) 的出队操作? - templatetypedef
7
@模板 每个元素只能从一个堆栈移动到另一个堆栈一次。 - adamax
7
如果您将“当前最小值与元素”一起存储,并从队列中弹出最小值,那么如何在O(1)的时间内确定新的最小值? - Olhovsky
5
@adamax我不理解第三部分。你的最小值是如何工作的?正如你所看到的,这里有太多的评论。为什么不提供一个简单的例子来说明你的算法步骤呢?这将有助于理解你的算法。 - UmmaGumma
8
@adamax,我终于理解了你的解决方案。你应该在解释中加入,当我们将元素从第一个结构移动到第二个结构时,应重新计算第二个元素的值。顺便说一下,正如我在答案中展示的那样,这些操作都可以在O(1)而不是平摊O(1)中完成。 :) - UmmaGumma
显示剩余28条评论

29

好的 - 我认为我有一个答案,可以让你执行这些操作的时间复杂度均摊为 O(1),意味着任何一个操作都可能需要 O(n) 的时间,但是 n 个操作的序列只需每个操作 O(1) 时间。

这个想法是将数据存储为 笛卡尔树。 这是一棵遵循最小堆属性(每个节点都不大于其子节点)并以一种使得其中序遍历返回添加顺序相同的节点的方式排序的二叉树。例如,下面是一个由序列 2 1 4 3 5 构成的笛卡尔树:

       1
     /   \
    2      3
          / \
         4   5

可以通过以下方法在O(1)摊销时间内将元素插入笛卡尔树中。查看树的右侧脊柱(由始终向右走形成的从根到最右叶子的路径)。从最右边的节点开始,沿着这条路径向上扫描,直到找到第一个小于要插入的节点的节点。
更改该节点,使其右子节点为这个新节点,然后将该节点的旧右子节点作为刚刚添加的节点的左子节点。例如,假设我们想将另一个2的副本插入以上树中。我们沿着右侧脊柱向上走过5和3,但在1下停止,因为1 < 2。然后我们改变树的样子如下:

       1
     /   \
    2      2
          /
         3
        / \
       4   5

注意中序遍历给出的顺序是 2 1 4 3 5 2,这是我们添加值的顺序。

这个算法的平摊复杂度为 O(1),因为我们可以创建一个势能函数,等于树右脊中节点的数量。插入一个节点所需的真实时间是 1 加上我们考虑的脊长为 k 的节点数(称之为 k)。一旦找到插入节点的位置,脊的大小缩小了 k - 1,因为我们访问的 k 个节点不再在右脊上,而新节点就在它们的位置上。这给出了平摊成本为 1 + k + (1 - k) = 2 = O(1),即每次插入的平摊复杂度为 O(1)。另一种思考方式是,一旦将一个节点移出右脊,它永远不会再成为右脊的一部分,因此我们永远不需要再次移动它。由于每个节点最多只能被移动一次,这意味着 n 次插入最多可以进行 n 次移动,因此总运行时间最多为 O(n),对于每个元素的平摊复杂度为 O(1)。

要执行弹出操作,我们只需从笛卡尔树中删除最左节点。如果这个节点是叶子节点,则我们已经完成。否则,该节点只能有一个孩子(右孩子),因此我们用其右孩子替换它。只要跟踪最左边的节点在哪里,这一步就需要 O(1) 的时间。但是,在删除最左节点并用其右孩子替换它后,我们可能不知道新的最左节点在哪里。为了解决这个问题,我们只需从刚刚移动到最左孩子的新节点开始沿树的左脊向下走。我声称这仍然在 O(1) 平摊时间内运行。要证明这一点,请注意在找到最左节点的任何一次遍历中,每个节点最多被访问一次。要了解这一点,请注意,一旦以这种方式访问了一个节点,唯一需要再次查看它的方法是将其从最左节点的孩子移动到最左节点。但是,所有被访问的节点都是最左节点的父节点,因此这种情况不会发生。因此,在此过程中每个节点最多被访问一次,弹出操作的复杂度为 O(1)。

我们可以在 O(1) 时间内找到最小值,因为笛卡尔树可以免费让我们访问树中最小的元素;它是树的根节点。

最后,要查看节点是否按插入顺序返回,请注意笛卡尔树始终将其元素存储在中序遍历访问它们的排序顺序中。由于我们总是在每一步中删除最左节点,而这是中序遍历的第一个元素,因此我们总是按照它们被插入的顺序获取节点。

简而言之,我们得到了 O(1) 平摊推和弹出,以及 O(1) 最坏情况下的查找最小值。

如果我能想出最坏情况下 O(1) 的实现,我一定会发布它。这是一个很好的问题;感谢您的发布!


我仍在考虑您的弹出操作是否真正达到了O(1)的平摊复杂度。当我确认后,我一定会点赞这个答案。如果还有其他人能够帮忙验证这个答案就更好了。 - Olhovsky
@Kdoto- 想一想,如果你想要获得O(1)的平摊出队时间,那么每个节点都需要存储一个父指针,因为这样当你移除一个叶子节点时,你可以在最坏情况下以O(1)的时间更新指向树中最左边的节点的指针。 - templatetypedef
我看到了你的实现 https://www.keithschwarz.com/interesting/code/?dir=min-queue // 添加和删除队列非常清晰,但是使用两个旧栈和新栈来查找最小值不太清楚?为了查找最小值,您使用另一个结构吗?能否请您举一个简单的例子来说明它的工作原理? - user5920478

25

好的,以下是一种解决方案。

首先,我们需要一些可以提供 push_back()、push_front()、pop_back() 和 pop_front() 的东西,时间复杂度为 O(1)。使用数组和 2 个迭代器实现很容易。第一个迭代器将指向前面,第二个将指向后面。让我们称这样的东西为 deque。

这是伪代码:

class MyQueue//Our data structure
{
    deque D;//We need 2 deque objects
    deque Min;

    push(element)//pushing element to MyQueue
    {
        D.push_back(element);
        while(Min.is_not_empty() and Min.back()>element)
             Min.pop_back();
        Min.push_back(element);
    }
    pop()//poping MyQueue
    {
         if(Min.front()==D.front() )
            Min.pop_front();
         D.pop_front();
    }

    min()
    {
         return Min.front();
    }
}

解释:

例如,让我们将数字 [12,5,10,7,11,19] 推入到我们的 MyQueue 中。

1) 推入 12

D [12]
Min[12]

2)向数组中添加元素5

D[12,5]
Min[5] //5>12 so 12 removed

3)将10推入

D[12,5,10]
Min[5,10]

4)推入7

D[12,5,10,7]
Min[5,7]

6)将11推入

D[12,5,10,7,11]
Min[5,7,11]

7) 将19推入数组

D[12,5,10,7,11,19]
Min[5,7,11,19]

现在让我们调用pop_front()

我们得到的结果是

 D[5,10,7,11,19]
 Min[5,7,11,19]

最小值为5

让我们再次调用pop_front()

解释:pop_front将从D中移除5,但它也会弹出Min的前面元素,因为它等于D的前面元素(5)。

 D[10,7,11,19]
 Min[7,11,19]

最小值为7. :)


看起来如果你按顺序输入2、3、1,get_min返回的是2而不是1。 - adamax
@adamax 哎呀 :). 你抓住我了。我改正了 push()。现在它是正确的,但不是在 0(1) 中运行。它像你的一样在平摊 O(1) 中工作 :). - UmmaGumma
3
@UmmaGumma,干得好!不过稍作修改,当12从堆栈中弹出时,它应该是5 < 12。 - seeker

2
使用一个双端队列(A)存储元素,使用另一个双端队列(B)存储最小值。
当x入队时,将其push_back到A中,并保持弹出B直到B的末尾小于x,然后将x push_back到B中。
当出队A时,pop_front A作为返回值,如果它等于B的front,则也pop_front B。
当获取A的最小值时,使用B的front作为返回值。
dequeue和getmin显然是O(1)。对于enqueue操作,请考虑n个元素的push_back。有n次push_back到A,n次push_back到B,以及最多n次pop_back到B,因为每个元素要么留在B中,要么从B中弹出一次。总共有O(3n)个操作,因此摊销成本也是O(1)。
最后,这个算法之所以有效,是因为当您将x入队到A时,如果B中存在比x大的元素,它们现在永远不会成为最小值,因为x将在队列A中停留的时间比B中的任何元素都长(队列是FIFO)。因此,在将x推入B之前,我们需要弹出B中比x大的元素(从后面)。
from collections import deque


class MinQueue(deque):
    def __init__(self):
        deque.__init__(self)
        self.minq = deque()

    def push_rear(self, x):
        self.append(x)
        while len(self.minq) > 0 and self.minq[-1] > x:
            self.minq.pop()
        self.minq.append(x)

    def pop_front(self):
        x = self.popleft()
        if self.minq[0] == x:
            self.minq.popleft()
        return(x)

    def get_min(self):
        return(self.minq[0])

1
你可以使用LinkedList来维护队列。
LinkedList中的每个元素将是某种类型。
class LinkedListElement
{
   LinkedListElement next;
   int currentMin;
}

你可以有两个指针,一个指向开始,另一个指向结束。

如果你要将元素添加到队列的开头,请检查开始指针和要插入的节点。如果要插入的节点currentmin小于start currentmin,那么node to insert currentmin是最小值。否则,用 start currentmin 更新当前最小值。

对enque执行相同操作。


1

JavaScript 实现

(感谢 adamax's solution 的灵感;我基于其实现了一个类似的实现。向下滚动到底部查看完全注释的代码或者阅读下面的一般步骤。请注意,这个方法在O(1)常数时间内找到最大值而不是最小值--很容易改变):

总体思路是在构建MaxQueue时创建两个栈(我使用链表作为底层Stack数据结构--未包含在代码中;但只要实现了O(1)插入/删除的任何Stack都可以)。一个我们将主要从中popdqStack),另一个我们将主要push到(eqStack)。


插入:最坏情况O(1)

对于enqueue操作,如果MaxQueue为空,我们将会在元组中存储当前最大值和该值,并将其与值一同压入dqStack中(由于它是MaxQueue中唯一的值,因此这两个值相同);例如:
const m = new MaxQueue();

m.enqueue(6);

/*
the dqStack now looks like:
[6, 6] - [value, max]
*/

如果MaxQueue不为空,我们只将value推入eqStack
m.enqueue(7);
m.enqueue(8);

/*
dqStack:         eqStack: 8
         [6, 6]           7 - just the value
*/

然后,在元组中更新最大值。
/*
dqStack:         eqStack: 8
         [6, 8]           7
*/


删除操作:平摊时间复杂度为O(1)

对于 dequeue 操作,我们将从 dqStackpop 并返回元组中的值。

m.dequeue();
> 6

// equivalent to:
/*
const tuple = m.dqStack.pop() // [6, 8]
tuple[0];
> 6
*/

如果 dqStack 为空,将 eqStack 中的所有值移动到 dqStack 中,例如:
// if we build a MaxQueue
const maxQ = new MaxQueue(3, 5, 2, 4, 1);

/*
the stacks will look like:

dqStack:         eqStack: 1
                          4
                          2
         [3, 5]           5
*/

当每个值被移动时,我们会检查它是否比迄今为止的最大值大,并将其存储在每个元组中:
maxQ.dequeue(); // pops from dqStack (now empty), so move all from eqStack to dqStack
> 3

// as dequeue moves one value over, it checks if it's greater than the ***previous max*** and stores the max at tuple[1], i.e., [data, max]:
/*
dqStack: [5, 5] => 5 > 4 - update                          eqStack:
         [2, 4] => 2 < 4 - no update                         
         [4, 4] => 4 > 1 - update                            
         [1, 1] => 1st value moved over so max is itself            empty
*/

因为每个值最多只移动到dqStack一次,我们可以说dequeue的平摊时间复杂度为O(1)。
寻找最大值:最坏情况下为O(1)
然后,在任何时刻,我们都可以调用getMax以在O(1)的常数时间内检索当前最大值。只要MaxQueue不为空,最大值就可以轻松地从dqStack中的下一个元组中取出。
maxQ.getMax();
> 5

// equivalent to calling peek on the dqStack and pulling out the maximum value:
/*
const peekedTuple = maxQ.dqStack.peek(); // [5, 5]
peekedTuple[1];
> 5
*/


代码

class MaxQueue {
  constructor(...data) {
    // create a dequeue Stack from which we'll pop
    this.dqStack = new Stack();
    // create an enqueue Stack to which we'll push
    this.eqStack = new Stack();
    // if enqueueing data at construction, iterate through data and enqueue each
    if (data.length) for (const datum of data) this.enqueue(datum);
  }
  enqueue(data) { // O(1) constant insertion time
    // if the MaxQueue is empty,
    if (!this.peek()) {
      // push data to the dequeue Stack and indicate it's the max;
      this.dqStack.push([data, data]); // e.g., enqueue(8) ==> [data: 8, max: 8]
    } else {
      // otherwise, the MaxQueue is not empty; push data to enqueue Stack
      this.eqStack.push(data);
      // save a reference to the tuple that's next in line to be dequeued
      const next = this.dqStack.peek();
      // if the enqueueing data is > the max in that tuple, update it
      if (data > next[1]) next[1] = data;
    }
  }
  moveAllFromEqToDq() { // O(1) amortized as each value will move at most once
    // start max at -Infinity for comparison with the first value
    let max = -Infinity;
    // until enqueue Stack is empty,
    while (this.eqStack.peek()) {
      // pop from enqueue Stack and save its data
      const data = this.eqStack.pop();
      // if data is > max, set max to data
      if (data > max) max = data;
      // push to dequeue Stack and indicate the current max; e.g., [data: 7: max: 8]
      this.dqStack.push([data, max]);
    }
  }
  dequeue() { // O(1) amortized deletion due to calling moveAllFromEqToDq from time-to-time
    // if the MaxQueue is empty, return undefined
    if (!this.peek()) return;
    // pop from the dequeue Stack and save it's data
    const [data] = this.dqStack.pop();
    // if there's no data left in dequeue Stack, move all data from enqueue Stack
    if (!this.dqStack.peek()) this.moveAllFromEqToDq();
    // return the data
    return data;
  }
  peek() { // O(1) constant peek time
    // if the MaxQueue is empty, return undefined
    if (!this.dqStack.peek()) return;
    // peek at dequeue Stack and return its data
    return this.dqStack.peek()[0];
  }
  getMax() { // O(1) constant time to find maximum value
    // if the MaxQueue is empty, return undefined
    if (!this.peek()) return;
    // peek at dequeue Stack and return the current max
    return this.dqStack.peek()[1];
  }
}


1

如果您不介意存储一些额外的数据,那么存储最小值应该是非常简单的。如果新添加或删除的元素是最小值,push和pop可以更新该值,获取最小值只需获取变量的值即可。

这是假设get_min()不改变数据的情况下;如果您更喜欢像pop_min()这样的东西(即删除最小元素),您可以简单地存储指向实际元素及其前一个元素(如果有)的指针,并使用push_rear()和pop_front()相应地更新它们。

在评论后进行编辑:

显然,在这些操作中,如果最小值发生变化,则会导致O(n)的push和pop,因此严格来说不符合要求。


1
这样做会给你一个O(n)的弹出,因为你必须扫描所有元素来找到新的最小值? - templatetypedef
2
我认为get_min()实际上并没有弹出数据。但是pop_front()会弹出数据。假设前面的节点也是最小节点,那么它就被弹出了。现在我们如何在常数时间内维护最小属性呢? - bits
啊,说得好...尽管你是对的,@bits,只有在你推入新最小值或弹出当前最小值的情况下,它才是O(n)。如果必须是最坏情况下的O(1),我不知道是否可能,但我很想看到其他情况。 - Andy Mikula

0

我们知道push和pop是常数时间操作[精确地说是O(1)]。

但是当我们想到get_min()[即查找队列中当前最小的数字]时,通常首先想到的是每次请求最小元素时搜索整个队列。但这永远不会给出常数时间操作,这是问题的主要目标。

这通常在面试中经常被问到,所以你必须知道诀窍。

为了做到这一点,我们必须使用另外两个队列来跟踪最小元素,并且我们必须在队列上执行push和pop操作时修改这两个队列,以便在O(1)时间内获得最小元素。

以下是基于上述方法的自描述伪代码。

    Queue q, minq1, minq2;
    isMinq1Current=true;   
    void push(int a)
    {
      q.push(a);
      if(isMinq1Current)
      {
        if(minq1.empty) minq1.push(a);
        else
        {
          while(!minq1.empty && minq1.top < =a) minq2.push(minq1.pop());
          minq2.push(a);
          while(!minq1.empty) minq1.pop();
          isMinq1Current=false;
        }
      }
      else
      {
        //mirror if(isMinq1Current) branch. 
      }
    }
     
    int pop()
    { 
      int a = q.pop();
      if(isMinq1Current)
      {
        if(a==minq1.top) minq1.pop();
      }
      else
      {
        //mirror if(isMinq1Current) branch.    
      }
    return a;
    }

0
#include <iostream>
#include <queue>
#include <deque>
using namespace std;

queue<int> main_queue;
deque<int> min_queue;

void clearQueue(deque<int> &q)
{
  while(q.empty() == false) q.pop_front();
}

void PushRear(int elem)
{
  main_queue.push(elem);

  if(min_queue.empty() == false && elem < min_queue.front())
  {
      clearQueue(min_queue);
  }

  while(min_queue.empty() == false && elem < min_queue.back())
  {
      min_queue.pop_back();
  }

  min_queue.push_back(elem);
}

void PopFront() 
{
  int elem = main_queue.front();
  main_queue.pop();

  if (elem == min_queue.front())
  {
       min_queue.pop_front();
  }
}

int GetMin() 
{ 
  return min_queue.front(); 
}

int main()
{
  PushRear(1);
  PushRear(-1);
  PushRear(2);

  cout<<GetMin()<<endl;
  PopFront();
  PopFront();
  cout<<GetMin()<<endl;

  return 0;
}

在没有伴随明确说明代码正确性的情况下发布代码是不好的。 - Richard
那段代码非常容易理解。如果你需要解释,为什么不问一下呢?而不是直接点踩呢? - TheMan
我最喜欢StackOverflow的一个优点是这里答案的高质量。当我访问其他网站时,似乎每个发布的人都只是抛出一堆“自说明代码”,就像你的那样。不可避免地,其中一些是错误的,每个答案都需要时间来理解和验证。一个好的答案可以帮助你通过验证过程,并预先回答你可能会有的问题。很难记得回来检查这些事情,所以我更喜欢给予负面评价,然后中立或者点赞。 - Richard
据我所知,这与江来一个月前已经给出源代码并描述的算法相同。 - j_random_hacker

0

这个解决方案包含2个队列:
1. main_q - 存储输入的数字。
2. min_q - 根据某些规则存储最小数字,这些规则将在函数MainQ.enqueue(x), MainQ.dequeue(), MainQ.get_min()中描述。

以下是Python代码。队列使用List实现。
主要思想在于MainQ.enqueue(x), MainQ.dequeue(), MainQ.get_min()函数中。
一个关键假设是清空队列需要o(0)时间。
测试在末尾提供。

import numbers

class EmptyQueueException(Exception):
    pass

class BaseQ():
    def __init__(self):
        self.l = list()

    def enqueue(self, x):
        assert isinstance(x, numbers.Number)
        self.l.append(x)

    def dequeue(self):
        return self.l.pop(0)

    def peek_first(self):
        return self.l[0]

    def peek_last(self):
        return self.l[len(self.l)-1]

    def empty(self):
        return self.l==None or len(self.l)==0

    def clear(self):
        self.l=[]

class MainQ(BaseQ):
    def __init__(self, min_q):
        super().__init__()
        self.min_q = min_q

    def enqueue(self, x):
        super().enqueue(x)
        if self.min_q.empty():
            self.min_q.enqueue(x)
        elif x > self.min_q.peek_last():
            self.min_q.enqueue(x)
        else: # x <= self.min_q.peek_last():
            self.min_q.clear()
            self.min_q.enqueue(x)

    def dequeue(self):
        if self.empty():
            raise EmptyQueueException("Queue is empty")
        x = super().dequeue()
        if x == self.min_q.peek_first():
            self.min_q.dequeue()
        return x

    def get_min(self):
        if self.empty():
            raise EmptyQueueException("Queue is empty, NO minimum")
        return self.min_q.peek_first()

INPUT_NUMS = (("+", 5), ("+", 10), ("+", 3), ("+", 6), ("+", 1), ("+", 2), ("+", 4), ("+", -4), ("+", 100), ("+", -40),
              ("-",None), ("-",None), ("-",None), ("+",-400), ("+",90), ("-",None),
              ("-",None), ("-",None), ("-",None), ("-",None), ("-",None), ("-",None), ("-",None), ("-",None))

if __name__ == '__main__':
    min_q = BaseQ()
    main_q = MainQ(min_q)

    try:
        for operator, i in INPUT_NUMS:
            if operator=="+":
                main_q.enqueue(i)
                print("Added {} ; Min is: {}".format(i,main_q.get_min()))
                print("main_q = {}".format(main_q.l))
                print("min_q = {}".format(main_q.min_q.l))
                print("==========")
            else:
                x = main_q.dequeue()
                print("Removed {} ; Min is: {}".format(x,main_q.get_min()))
                print("main_q = {}".format(main_q.l))
                print("min_q = {}".format(main_q.min_q.l))
                print("==========")
    except Exception as e:
        print("exception: {}".format(e))

以上测试的输出为:
"C:\Program Files\Python35\python.exe" C:/dev/python/py3_pocs/proj1/priority_queue.py
Added 5 ; Min is: 5
main_q = [5]
min_q = [5]
==========
Added 10 ; Min is: 5
main_q = [5, 10]
min_q = [5, 10]
==========
Added 3 ; Min is: 3
main_q = [5, 10, 3]
min_q = [3]
==========
Added 6 ; Min is: 3
main_q = [5, 10, 3, 6]
min_q = [3, 6]
==========
Added 1 ; Min is: 1
main_q = [5, 10, 3, 6, 1]
min_q = [1]
==========
Added 2 ; Min is: 1
main_q = [5, 10, 3, 6, 1, 2]
min_q = [1, 2]
==========
Added 4 ; Min is: 1
main_q = [5, 10, 3, 6, 1, 2, 4]
min_q = [1, 2, 4]
==========
Added -4 ; Min is: -4
main_q = [5, 10, 3, 6, 1, 2, 4, -4]
min_q = [-4]
==========
Added 100 ; Min is: -4
main_q = [5, 10, 3, 6, 1, 2, 4, -4, 100]
min_q = [-4, 100]
==========
Added -40 ; Min is: -40
main_q = [5, 10, 3, 6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Removed 5 ; Min is: -40
main_q = [10, 3, 6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Removed 10 ; Min is: -40
main_q = [3, 6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Removed 3 ; Min is: -40
main_q = [6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Added -400 ; Min is: -400
main_q = [6, 1, 2, 4, -4, 100, -40, -400]
min_q = [-400]
==========
Added 90 ; Min is: -400
main_q = [6, 1, 2, 4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 6 ; Min is: -400
main_q = [1, 2, 4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 1 ; Min is: -400
main_q = [2, 4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 2 ; Min is: -400
main_q = [4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 4 ; Min is: -400
main_q = [-4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed -4 ; Min is: -400
main_q = [100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 100 ; Min is: -400
main_q = [-40, -400, 90]
min_q = [-400, 90]
==========
Removed -40 ; Min is: -400
main_q = [-400, 90]
min_q = [-400, 90]
==========
Removed -400 ; Min is: 90
main_q = [90]
min_q = [90]
==========
exception: Queue is empty, NO minimum

Process finished with exit code 0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接