Triển khai một hàng đợi trong đó push_rear (), pop_front () và get_min () là tất cả các hoạt động thời gian không đổi


76

Tôi đã gặp câu hỏi này: Triển khai một hàng đợi trong đó push_rear (), pop_front () và get_min () đều là các hoạt động thời gian không đổi.

Ban đầu tôi nghĩ đến việc sử dụng cấu trúc dữ liệu min-heap có độ phức tạp O (1) cho get_min (). Nhưng push_rear () và pop_front () sẽ là O (log (n)).

Có ai biết cách tốt nhất để thực hiện một hàng đợi như vậy có O (1) push (), pop () và min () không?

Tôi đã tìm kiếm thông tin về điều này và muốn chỉ ra chủ đề Người đam mê thuật toán này . Nhưng có vẻ như không có giải pháp nào tuân theo quy tắc thời gian không đổi cho cả 3 phương thức: push (), pop () và min ().

Cảm ơn tất cả những lời đề nghị.


Bạn có ổn với giới hạn thời gian O (1) được phân bổ cho tất cả những điều này không, hay những điều này phải là giới hạn thời gian trong trường hợp xấu nhất?
templatetypedef

Tôi không chắc, đó là một câu hỏi phỏng vấn của Google, ban đầu tôi thấy nó tại careercup.com/question?id=7263132 .... Có vẻ như câu hỏi có nghĩa là giới hạn thời gian trong trường hợp xấu nhất. Nó dường như không thể?
bit

@ bits- Không, nó chắc chắn có vẻ khả thi, và tôi đang quay đầu với nó ngay bây giờ. :-) Tôi đang xem xét việc sử dụng cây Descartes để làm điều này - điều này cung cấp cho bạn việc chèn phân bổ O (1) và tra cứu O (1), và tôi gần như cũng có tác dụng xóa phân bổ O (1). Nhưng, nếu bạn đang tìm kiếm các giới hạn trong trường hợp xấu nhất, tôi sẽ thay đổi cách tiếp cận của mình.
templatetypedef

ok, bây giờ hãy xem câu trả lời của Kdoto bên dưới; Bây giờ tôi chắc chắn rằng giới hạn trong trường hợp xấu nhất có thể không phải là điều khả thi. Vì vậy, có lẽ nhân viên của Google phải đang tìm Phân bổ O (1). EDIT: ok, như templatetypedef đã chỉ ra trong các bình luận về câu trả lời của Kdoto, bằng chứng không đúng. Ghi chú.
bit

Đừng chắc chắn như vậy, bằng chứng của tôi không đúng. Tuy nhiên, tôi không nghĩ rằng O (1) đã được tìm thấy cho tất cả các nghiệp vụ, được phân bổ hay không. Và tôi nghi ngờ rằng điều đó là không thể.
Olhovsky

Câu trả lời:


99

Bạn có thể triển khai một ngăn xếp với O (1) pop (), push () và get_min (): chỉ cần lưu trữ mức tối thiểu hiện tại cùng với mỗi phần tử. Vì vậy, ví dụ, ngăn xếp [4,2,5,1](1 ở trên cùng) trở thành [(4,4), (2,2), (5,2), (1,1)].

Sau đó, bạn có thể sử dụng hai ngăn xếp để triển khai hàng đợi . Đẩy tới một ngăn xếp, bật từ ngăn xếp khác; nếu ngăn xếp thứ hai trống trong cửa sổ bật lên, hãy di chuyển tất cả các phần tử từ ngăn xếp thứ nhất sang ngăn xếp thứ hai.

Ví dụ: đối với một popyêu cầu, di chuyển tất cả các phần tử từ ngăn xếp đầu tiên [(4,4), (2,2), (5,2), (1,1)], ngăn xếp thứ hai sẽ là [(1,1), (5,1), (2,1), (4,1)]. và bây giờ trả về phần tử hàng đầu từ ngăn xếp thứ hai.

Để tìm phần tử nhỏ nhất của hàng đợi, hãy xem hai phần tử nhỏ nhất của từng ngăn xếp tối thiểu riêng lẻ, sau đó lấy giá trị nhỏ nhất của hai giá trị đó. (Tất nhiên, có một số logic bổ sung ở đây là trường hợp một trong các ngăn xếp trống, nhưng điều đó không quá khó để giải quyết).

Nó sẽ có O (1) get_min()push()và khấu hao O (1) pop().


10
Làm thế nào để sử dụng hai ngăn xếp để triển khai hàng đợi cung cấp cho bạn cửa sổ bật O (1) được khấu hao?
templatetypedef

7
@template Mỗi phần tử chỉ có thể được di chuyển từ ngăn xếp này sang ngăn xếp khác một lần.
adamax

6
Nếu bạn lưu trữ "mức tối thiểu hiện tại cùng với các phần tử", và bạn bật mức tối thiểu từ hàng đợi, làm thế nào bạn biết mức tối thiểu mới là bao nhiêu, trong thời gian O (1)?
Olhovsky

4
@adamax Tôi không thể hiểu phần thứ 3. Mức tối thiểu của bạn đang hoạt động như thế nào. Như bạn thấy có quá nhiều bình luận ở đây. Tại sao không cung cấp một ví dụ nhỏ, với các bước thuật toán của bạn. Nó sẽ giúp hiểu thuật toán của bạn.
UmmaGumma,

7
@adamax Cuối cùng tôi cũng hiểu giải pháp của bạn. Bạn nên thêm vào giải thích của mình, rằng chúng ta nên tính toán lại giá trị của các phần tử thứ hai, khi chúng tôi chuyển các phần tử từ cấu trúc thứ nhất sang cấu trúc thứ hai. Nhân tiện như tôi trình bày trong câu trả lời của tôi Có thể thực hiện tất cả các nghiệp vụ này trong o (1) và không phân bổ theo O (1). :)
UmmaGumma,

28

Được rồi - Tôi nghĩ rằng tôi có một câu trả lời cung cấp cho bạn tất cả các hoạt động này theo phân bổ O (1), có nghĩa là bất kỳ hoạt động nào có thể mất đến O (n), nhưng bất kỳ chuỗi nào gồm n hoạt động nào cũng mất O (1) thời gian cho mỗi hoạt động .

Ý tưởng là lưu trữ dữ liệu của bạn dưới dạng cây Descartes . Đây là một cây nhị phân tuân theo thuộc tính min-heap (mỗi nút không lớn hơn các nút con của nó) và được sắp xếp theo cách sao cho việc truyền tải các nút nhỏ hơn sẽ trả lại cho bạn các nút theo cùng thứ tự mà chúng đã được thêm vào. Ví dụ, đây là một cây Descartes cho chuỗi 2 1 4 3 5:

       1
     /   \
    2      3
          / \
         4   5

Có thể chèn một phần tử vào cây Đề-các theo thời gian khấu hao O (1) bằng quy trình sau. Nhìn vào gai bên phải của cây (con đường từ gốc đến lá ngoài cùng bên phải hình thành bằng cách luôn đi về bên phải). Bắt đầu từ nút ngoài cùng bên phải, quét lên trên dọc theo đường dẫn này cho đến khi bạn tìm thấy nút đầu tiên nhỏ hơn nút bạn đang chèn.
Thay đổi nút đó để nút con bên phải của nó là nút mới này, sau đó đặt nút con bên phải cũ của nút đó thành nút con bên trái của nút bạn vừa thêm. Ví dụ, giả sử rằng chúng ta muốn chèn một bản sao khác của 2 vào cây trên. Chúng tôi đi lên cột sống bên phải qua cột 5 và cột 3, nhưng dừng lại dưới cột 1 vì 1 <2. Sau đó chúng tôi thay đổi cây trông như thế này:

       1
     /   \
    2      2
          /
         3
        / \
       4   5

Lưu ý rằng một đường truyền nhỏ hơn cho 2 1 4 3 5 2, là trình tự mà chúng tôi đã thêm các giá trị.

Điều này chạy theo phân bổ O (1) vì chúng ta có thể tạo ra một hàm tiềm năng bằng số nút trong cột sống bên phải của cây. Thời gian thực cần thiết để chèn một nút là 1 cộng với số nút trong cột sống mà chúng ta xem xét (gọi là k). Khi chúng tôi tìm thấy vị trí để chèn nút, kích thước của cột sống sẽ co lại theo độ dài k - 1, vì mỗi nút trong số k nút mà chúng tôi đã truy cập không còn ở cột sống bên phải nữa, và nút mới nằm ở vị trí của nó. Điều này tạo ra chi phí phân bổ là 1 + k + (1 - k) = 2 = O (1), đối với phụ lục O (1) được phân bổ. Như một cách nghĩ khác về điều này, một khi một nút đã được di chuyển khỏi cột sống bên phải, nó sẽ không bao giờ là một phần của cột sống bên phải nữa, và vì vậy chúng ta sẽ không bao giờ phải di chuyển nó nữa. Vì mỗi nút trong số n nút có thể được di chuyển nhiều nhất một lần, điều này có nghĩa là n phần chèn có thể thực hiện nhiều nhất n lần di chuyển,

Để thực hiện bước dequeue, chúng ta chỉ cần xóa nút ngoài cùng bên trái khỏi cây Đề-các. Nếu nút này là một lá, chúng ta đã hoàn tất. Nếu không, nút chỉ có thể có một nút con (nút con bên phải) và do đó chúng ta thay thế nút bằng nút con bên phải của nó. Với điều kiện là chúng ta theo dõi vị trí của nút ngoài cùng bên trái, bước này mất O (1) thời gian. Tuy nhiên, sau khi loại bỏ nút ngoài cùng bên trái và thay thế nó bằng nút con bên phải của nó, chúng ta có thể không biết nút ngoài cùng bên trái mới ở đâu. Để khắc phục điều này, chúng ta chỉ cần đi xuống cột sống bên trái của cây bắt đầu từ nút mới mà chúng ta vừa di chuyển đến nút con ngoài cùng bên trái. Tôi khẳng định rằng điều này vẫn chạy trong thời gian phân bổ O (1). Để thấy điều này, tôi tuyên bố rằng một nút được truy cập nhiều nhất một lần trong bất kỳ lần nào trong số các lần chuyển để tìm nút ngoài cùng bên trái. Để xem điều này, hãy lưu ý rằng khi một nút đã được truy cập theo cách này, cách duy nhất mà chúng ta có thể cần xem lại nó là nếu nó được chuyển từ một nút con của nút ngoài cùng bên trái sang nút ngoài cùng bên trái. Nhưng tất cả các nút được truy cập đều là cha của nút ngoài cùng bên trái, vì vậy điều này không thể xảy ra. Do đó, mỗi nút được truy cập nhiều nhất một lần trong quá trình này và cửa sổ bật lên chạy ở O (1).

Chúng ta có thể thực hiện tìm-min trong O (1) vì cây Descartes cho phép chúng ta truy cập miễn phí phần tử nhỏ nhất của cây; nó là rễ của cây.

Cuối cùng, để thấy rằng các nút quay trở lại theo đúng thứ tự mà chúng đã được chèn vào, hãy lưu ý rằng cây Đề-các-ta luôn lưu trữ các phần tử của nó để một trình duyệt nhỏ hơn truy cập chúng theo thứ tự được sắp xếp. Vì chúng tôi luôn loại bỏ nút ngoài cùng bên trái ở mỗi bước và đây là phần tử đầu tiên của trình duyệt inorder, chúng tôi luôn lấy lại các nút theo thứ tự mà chúng đã được chèn vào.

Nói tóm lại, chúng tôi nhận được O (1) lần đẩy và cửa sổ phân bổ, và O (1) mức tối thiểu trong trường hợp xấu nhất.

Nếu tôi có thể đưa ra cách triển khai O (1) trong trường hợp xấu nhất, tôi chắc chắn sẽ đăng nó. Đây là một vấn đề lớn; cảm ơn vì đã đăng nó!


Tôi vẫn đang xem xét liệu cửa sổ pop của bạn có thực sự được khấu hao O (1) hay không. Tôi chắc chắn sẽ tán thành câu trả lời này khi tôi xác nhận điều này. Sẽ thật tuyệt nếu ai đó cũng giúp xác minh câu trả lời này.
Olhovsky

@ Kdoto- Hãy nghĩ lại, bạn cần mỗi nút lưu trữ một con trỏ cha nếu bạn muốn nhận được phân bổ khấu hao O (1), vì theo cách đó khi bạn loại bỏ một lá, bạn có thể cập nhật con trỏ vào nút ngoài cùng bên trái trong cây trong trường hợp xấu nhất O (1).
templatetypedef

Tôi thấy sự triển khai của bạn keithschwarz.com/interesting/code/?dir=min-queue // add and delete from queue rất rõ ràng nhưng thấy min không rõ ràng với hai ngăn xếp cũ và mới? để tìm min bạn sử dụng cấu trúc khác? bạn vui lòng cho một ví dụ nhỏ về cách nó hoạt động?
Mio Unio

23

Ok, đây là một giải pháp.

Đầu tiên, chúng tôi cần một số công cụ cung cấp push_back (), push_front (), pop_back () và pop_front () trong 0 (1). Thật dễ dàng để thực hiện với mảng và 2 trình vòng lặp. Trình lặp đầu tiên sẽ trỏ tới trước, thứ hai ra sau. Hãy gọi những thứ đó là deque.

Đây là mã giả:

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

Giải trình:

Ví dụ, hãy đẩy các số [12,5,10,7,11,19] và MyQueue của chúng ta

1) đẩy 12

D [12]
Min[12]

2) đẩy 5

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

3) đẩy 10

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

4) đẩy 7

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

6) đẩy 11

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

7) đẩy 19

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

Bây giờ chúng ta hãy gọi pop_front ()

chúng tôi có

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

Tối thiểu là 5

Hãy gọi lại pop_front ()

Giải thích: pop_front sẽ loại bỏ 5 khỏi D, nhưng nó cũng sẽ bật lên phần tử phía trước của Min, vì nó bằng với phần tử phía trước của D (5).

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

Và tối thiểu là 7. :)


Dường như nếu bạn đẩy 2, 3, 1 thì get_min returns 2 thay vì 1.
adamax

@adamax Rất tiếc :). Bạn đã có tôi. Tôi đã sửa lỗi push (). Bây giờ nó đang hoạt động chính xác, nhưng không phải ở 0 (1). Nó đang hoạt động theo phân bổ O (1) như của bạn :).
UmmaGumma,

2
@ UmmaGumma, làm tốt lắm! Tuy nhiên, điều chỉnh nhỏ, 5 <12 khi 12 của nó được bật ra khỏi ngăn xếp.
người tìm kiếm

3

Sử dụng một deque (A) để lưu trữ các phần tử và một deque khác (B) để lưu trữ các giá trị tối thiểu.

Khi x được xếp hàng, push_back nó về A và giữ pop_backing B cho đến khi phần sau của B nhỏ hơn x, sau đó push_back x về B.

khi định giá lại A, pop_front A làm giá trị trả về và nếu nó bằng với mặt trước của B, thì pop_front B cũng vậy.

khi nhận được giá trị nhỏ nhất của A, sử dụng phía trước của B làm giá trị trả về.

dequeue và getmin rõ ràng là O (1). Đối với hoạt động hàng đợi, hãy xem xét push_back của n phần tử. Có n push_back đến A, n push_back đến B và nhiều nhất n pop_back của B vì mỗi phần tử sẽ ở lại B hoặc được bật ra một lần từ B. Trên tất cả có O (3n) hoạt động và do đó chi phí phân bổ là O (1) cũng như đối với enqueue.

Cuối cùng, lý do mà thuật toán này hoạt động là khi bạn xếp x đến A, nếu có các phần tử trong B lớn hơn x, chúng sẽ không bao giờ là cực tiểu ngay bây giờ vì x sẽ ở trong hàng đợi A lâu hơn bất kỳ phần tử nào trong B (một hàng đợi là FIFO). Do đó, chúng ta cần bật ra các phần tử trong B (từ phía sau) lớn hơn x trước khi chúng ta đẩy x vào B.

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

Nếu bạn không ngại lưu trữ một chút dữ liệu bổ sung, thì việc lưu trữ giá trị tối thiểu sẽ rất nhỏ. Đẩy và bật có thể cập nhật giá trị nếu phần tử mới hoặc phần tử bị loại bỏ là giá trị nhỏ nhất và việc trả về giá trị nhỏ nhất cũng đơn giản như nhận giá trị của biến.

Điều này giả sử rằng get_min () không thay đổi dữ liệu; nếu bạn muốn có một cái gì đó như pop_min () (tức là loại bỏ phần tử tối thiểu), bạn có thể chỉ cần lưu trữ một con trỏ đến phần tử thực và phần tử đứng trước nó (nếu có) và cập nhật chúng cho phù hợp với push_rear () và pop_front () cũng.

Chỉnh sửa sau khi nhận xét:

Rõ ràng điều này dẫn đến việc đẩy và bật O (n) trong trường hợp các thay đổi tối thiểu trên các hoạt động đó, và do đó không hoàn toàn đáp ứng các yêu cầu.


1
Điều này không cung cấp cho bạn một cửa sổ bật O (n), vì bạn phải quét tất cả các phần tử để tìm min mới?
templatetypedef

2
Tôi nghĩ rằng get_min () không thực sự bật dữ liệu. Nhưng pop_front () không bật dữ liệu. Giả sử nút phía trước cũng là nút tối thiểu, vì vậy nó đã xuất hiện. Bây giờ làm thế nào chúng ta có thể duy trì thuộc tính min trong thời gian không đổi?
bit

À, cuộc gọi tốt ... mặc dù bạn nói đúng, @bits, nó chỉ là O (n) trong trường hợp bạn đẩy mức tối thiểu mới hoặc bật mức tối thiểu hiện tại của mình. Nếu nó phải là trường hợp xấu nhất O (1), tôi không biết điều đó có thể xảy ra, nhưng tôi rất muốn thấy khác.
Andy Mikula,

1

Giải pháp cho câu hỏi này, bao gồm cả mã, có thể được tìm thấy tại đây: http://discuss.joelonsoftware.com/default.asp?interview.11.742223.32


Các liên kết đến các trang bên ngoài là vô ích. Điều gì sẽ xảy ra nếu liên kết bị phá vỡ? Ngoài ra: trang bạn liên kết cũng chỉ là một cuộc thảo luận dài. Một câu trả lời tốt sẽ nắm bắt được các yếu tố quan trọng của cuộc thảo luận đó và loại bỏ những điều sơ sài.
Richard

1

Bạn có thể thực sự sử dụng LinkedList để duy trì Hàng đợi.

Mỗi phần tử trong LinkedList sẽ thuộc Loại

class LinkedListElement
{
   LinkedListElement next;
   int currentMin;
}

Bạn có thể có hai con trỏ Một con trỏ đến Bắt đầu và con trỏ khác đến Kết thúc.

Nếu bạn thêm một phần tử vào đầu Hàng đợi. Kiểm tra con trỏ Bắt đầu và nút cần chèn. Nếu nút để chèn currentmin nhỏ hơn nút bắt đầu currentmin để chèn currentmin là mức tối thiểu. Cập nhật currentmin khác với bắt đầu currentmin.

Lặp lại tương tự cho enque.


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

Sẽ không tốt nếu đăng mã mà không có lời giải thích kèm theo, rõ ràng về lý do tại sao mã đúng.
Richard

Mã đó rất tự giải thích. Nếu bạn muốn giải thích, bạn có thể hỏi thay vì bỏ phiếu xuống, làm ơn?
TheMan

Một trong những phẩm chất tôi thích nhất về StackOverflow là chất lượng cao của các câu trả lời ở đây. Khi tôi truy cập các trang web khác, có vẻ như tất cả những người đăng bài chỉ đang tung ra những "mã tự giải thích", giống như của bạn. Không thể tránh khỏi, một số trong số này là sai và mỗi điều cần có thời gian để hiểu và xác minh. Một câu trả lời hay sẽ giúp bạn vượt qua quá trình xác minh và trả lời trước các câu hỏi bạn có thể có. Thật khó nhớ để quay lại và kiểm tra những điều này, vì vậy tôi thích bỏ phiếu xuống và sau đó vô hiệu hóa hoặc bỏ phiếu lên.
Richard

AFAICT, đây là thuật toán giống như thuật toán đã được cung cấp dưới dạng mã nguồn và được jianglai mô tả hơn một tháng trước đó.
j_random_hacker

0

Giải pháp này chứa 2 hàng đợi:
1. main_q - lưu trữ các số đầu vào.
2. min_q - lưu trữ các số min theo các quy tắc nhất định mà chúng ta sẽ mô tả (xuất hiện trong các hàm MainQ.enqueue (x), MainQ.dequeue (), MainQ.get_min ()).

Đây là mã bằng Python. Hàng đợi được triển khai bằng cách sử dụng Danh sách.
Ý tưởng chính nằm trong các hàm MainQ.enqueue (x), MainQ.dequeue (), MainQ.get_min ().
Một giả định quan trọng là việc làm trống hàng đợi lấy o (0).
Một bài kiểm tra được cung cấp ở cuối.

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))

Kết quả của bài kiểm tra trên là:

"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

0

Triển khai Java

import java.io.*;
import java.util.*;

public class queueMin {
    static class stack {

        private Node<Integer> head;

        public void push(int data) {
            Node<Integer> newNode = new Node<Integer>(data);
            if(null == head) {
                head = newNode;
            } else {
                Node<Integer> prev = head;
                head = newNode;
                head.setNext(prev);
            }
        }

        public int pop() {
            int data = -1;
            if(null == head){
                System.out.println("Error Nothing to pop");
            } else {
                data = head.getData();
                head = head.getNext();
            }

            return data;
        }

        public int peek(){
            if(null == head){
                System.out.println("Error Nothing to pop");
                return -1;
            } else {
                return head.getData();
            }
        }

        public boolean isEmpty(){
            return null == head;
        }
    }

    static class stackMin extends stack {
        private stack s2;

        public stackMin(){
            s2 = new stack();
        }

        public void push(int data){
            if(data <= getMin()){
                s2.push(data);
            }

            super.push(data);
        }

        public int pop(){
            int value = super.pop();
            if(value == getMin()) {
                s2.pop();
            }
            return value;
        }

        public int getMin(){
            if(s2.isEmpty()) {
                return Integer.MAX_VALUE;
            }
            return s2.peek();
        }
    }

     static class Queue {

        private stackMin s1, s2;

        public Queue(){
            s1 = new stackMin();
            s2 = new stackMin();
        }

        public  void enQueue(int data) {
            s1.push(data);
        }

        public  int deQueue() {
            if(s2.isEmpty()) {
                while(!s1.isEmpty()) {
                    s2.push(s1.pop());
                }
            }

            return s2.pop();
        }

        public int getMin(){
            return Math.min(s1.isEmpty() ? Integer.MAX_VALUE : s1.getMin(), s2.isEmpty() ? Integer.MAX_VALUE : s2.getMin());
        }

    }



   static class Node<T> {
        private T data;
        private T min;
        private Node<T> next;

        public Node(T data){
            this.data = data;
            this.next = null;
        }


        public void setNext(Node<T> next){
            this.next = next;
        }

        public T getData(){
            return this.data;
        }

        public Node<T> getNext(){
            return this.next;
        }

        public void setMin(T min){
            this.min = min;
        }

        public T getMin(){
            return this.min;
        }
    }

    public static void main(String args[]){
       try {
           FastScanner in = newInput();
           PrintWriter out = newOutput();
          // System.out.println(out);
           Queue q = new Queue();
           int t = in.nextInt();
           while(t-- > 0) {
               String[] inp = in.nextLine().split(" ");
               switch (inp[0]) {
                   case "+":
                       q.enQueue(Integer.parseInt(inp[1]));
                       break;
                   case "-":
                       q.deQueue();
                       break;
                   case "?":
                       out.println(q.getMin());
                   default:
                       break;
               }
           }
           out.flush();
           out.close();

       } catch(IOException e){
          e.printStackTrace();
       }
    }

    static class FastScanner {
        static BufferedReader br;
        static StringTokenizer st;

        FastScanner(File f) {
            try {
                br = new BufferedReader(new FileReader(f));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        public FastScanner(InputStream f) {
            br = new BufferedReader(new InputStreamReader(f));
        }
        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        String nextLine(){
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
        long nextLong() {
            return Long.parseLong(next());
        }
        double nextDoulbe() {
            return Double.parseDouble(next());
        }
    }

    static FastScanner newInput() throws IOException {
        if (System.getProperty("JUDGE") != null) {
            return new FastScanner(new File("input.txt"));
        } else {
            return new FastScanner(System.in);
        }
    }
    static PrintWriter newOutput() throws IOException {
        if (System.getProperty("JUDGE") != null) {
            return new PrintWriter("output.txt");
        } else {
            return new PrintWriter(System.out);
        }
    }
}

0

Triển khai JavaScript

(Tín dụng cho giải pháp của adamax cho ý tưởng; tôi dựa trên việc triển khai nó một cách lỏng lẻo . Chuyển xuống cuối để xem mã được nhận xét đầy đủ hoặc đọc qua các bước chung bên dưới. Lưu ý rằng điều này tìm giá trị lớn nhất theo thời gian không đổi O (1) chứ không phải các giá trị tối thiểu --easy để thay đổi trở lên):

Ý tưởng chung là tạo hai Ngăn xếp khi xây dựng MaxQueue (tôi đã sử dụng danh sách được liên kết làm Stackcấu trúc dữ liệu cơ bản - không được bao gồm trong mã; nhưng bất kỳ Stacksẽ làm được miễn là nó được triển khai với O (1) chèn / xóa). Một chúng tôi chủ yếu poptừ ( dqStack) và một chúng tôi sẽ chủ yếu pushđến ( eqStack).


Chèn: O (1) trường hợp xấu nhất

Đối với enqueue, nếu MaxQueuetrống, chúng tôi sẽpush giá trị đặt giá trị dqStackcùng với giá trị tối đa hiện tại trong một bộ giá trị (cùng một giá trị vì nó là giá trị duy nhất trong MaxQueue); ví dụ:

const m = new MaxQueue();

m.enqueue(6);

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

Nếu MaxQueue không trống, chúng tôi pushchỉ giá trị để eqStack;

m.enqueue(7);
m.enqueue(8);

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

sau đó, cập nhật giá trị lớn nhất trong bộ giá trị.

/*
dqStack:         eqStack: 8
         [6, 8]           7
*/


Xóa: O (1) được khấu hao

Đối với dequeue chúng tôi sẽ poptừ dqStackvà trả về giá trị từ tuple.

m.dequeue();
> 6

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

Sau đó, nếu dqStacktrống, hãy chuyển tất cả các giá trị vàoeqStack để dqStack, ví dụ như:

// 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
*/

Khi mỗi giá trị được chuyển qua, chúng tôi sẽ kiểm tra xem nó có lớn hơn giá trị tối đa cho đến nay hay không và lưu trữ nó trong từng bộ giá trị:

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
*/

Vì mỗi giá trị được chuyển đến dqStack nhiều nhất một lần , chúng ta có thể nói rằng giá trị đó dequeuecó độ phức tạp theo thời gian phân bổ là O (1).


Tìm giá trị lớn nhất: O (1) trường hợp xấu nhất

Sau đó, tại bất kỳ thời điểm nào, chúng ta có thể gọi getMaxđể truy xuất giá trị lớn nhất hiện tại theo thời gian không đổi O (1). Miễn là MaxQueuegiá trị không trống, giá trị lớn nhất có thể dễ dàng kéo ra khỏi bộ giá trị tiếp theo 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];
  }
}


0

Chúng ta biết rằng push và pop là các phép toán thời gian không đổi [chính xác là O (1)].

Nhưng khi chúng ta nghĩ đến get_min () [tức là để tìm số tối thiểu hiện tại trong hàng đợi], điều đầu tiên nghĩ đến là tìm kiếm toàn bộ hàng đợi mỗi khi yêu cầu phần tử tối thiểu được thực hiện. Nhưng điều này sẽ không bao giờ cung cấp cho hoạt động thời gian liên tục, đó là mục đích chính của vấn đề.

Điều này thường được hỏi rất thường xuyên trong các cuộc phỏng vấn, vì vậy bạn phải biết mẹo

Để làm điều này, chúng ta phải sử dụng thêm hai hàng đợi để theo dõi phần tử tối thiểu và chúng ta phải tiếp tục sửa đổi 2 hàng đợi này khi chúng ta thực hiện các hoạt động đẩy và bật trên hàng đợi sao cho phần tử tối thiểu nhận được trong thời gian O (1) .

Đây là mã giả tự mô tả dựa trên cách tiếp cận ở trên đã đề cập.

    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;
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.