Câu trả lời:
Giữ 2 ngăn xếp, hãy gọi cho họ inbox
và outbox
.
Enqueue :
inbox
Dequeue :
Nếu outbox
trống, đổ đầy nó bằng cách bật từng phần tử từ inbox
và đẩy nó lênoutbox
Pop và trả lại phần tử hàng đầu từ outbox
Sử dụng phương pháp này, mỗi phần tử sẽ nằm trong mỗi ngăn xếp chính xác một lần - có nghĩa là mỗi phần tử sẽ được đẩy hai lần và bật hai lần, cho các hoạt động thời gian không đổi được khấu hao.
Đây là một triển khai trong Java:
public class Queue<E>
{
private Stack<E> inbox = new Stack<E>();
private Stack<E> outbox = new Stack<E>();
public void queue(E item) {
inbox.push(item);
}
public E dequeue() {
if (outbox.isEmpty()) {
while (!inbox.isEmpty()) {
outbox.push(inbox.pop());
}
}
return outbox.pop();
}
}
Để hiểu cách xây dựng một hàng đợi bằng hai ngăn xếp, bạn nên hiểu làm thế nào để đảo ngược một ngăn xếp rõ ràng. Hãy nhớ cách stack hoạt động, nó rất giống với stack stack trên bếp của bạn. Đĩa rửa cuối cùng sẽ nằm trên đỉnh của ngăn xếp sạch, được gọi là L ast I n F irst O ut (LIFO) trong khoa học máy tính.
Hãy tưởng tượng ngăn xếp của chúng tôi như một cái chai như dưới đây;
Nếu chúng ta đẩy các số nguyên tương ứng 1,2,3, thì 3 sẽ nằm trên đỉnh của ngăn xếp. Bởi vì 1 sẽ được đẩy lên trước, sau đó 2 sẽ được đặt lên trên cùng 1. Cuối cùng, 3 sẽ được đặt lên trên cùng của ngăn xếp và trạng thái mới nhất của ngăn xếp của chúng tôi được biểu thị dưới dạng chai sẽ như dưới đây;
Bây giờ chúng ta có ngăn xếp của chúng ta được biểu diễn dưới dạng một chai được điền với các giá trị 3,2,1. Và chúng tôi muốn đảo ngược ngăn xếp để phần tử trên cùng của ngăn xếp sẽ là 1 và phần tử dưới cùng của ngăn xếp sẽ là 3. Chúng ta có thể làm gì? Chúng ta có thể lấy cái chai và giữ nó lộn ngược để tất cả các giá trị sẽ đảo ngược theo thứ tự?
Vâng, chúng tôi có thể làm điều đó, nhưng đó là một chai. Để thực hiện cùng một quy trình, chúng ta cần có một ngăn xếp thứ hai sẽ lưu trữ các phần tử ngăn xếp thứ nhất theo thứ tự ngược lại. Hãy đặt ngăn xếp dân cư của chúng tôi sang trái và ngăn xếp trống mới của chúng tôi ở bên phải. Để đảo ngược thứ tự của các phần tử, chúng ta sẽ bật từng phần tử từ ngăn xếp bên trái và đẩy chúng sang ngăn xếp bên phải. Bạn có thể thấy những gì xảy ra khi chúng tôi làm như vậy trên hình ảnh dưới đây;
Vì vậy, chúng tôi biết làm thế nào để đảo ngược một ngăn xếp.
Ở phần trước, tôi đã giải thích làm thế nào chúng ta có thể đảo ngược thứ tự các phần tử ngăn xếp. Điều này rất quan trọng, bởi vì nếu chúng ta đẩy và bật các phần tử pop vào ngăn xếp, đầu ra sẽ chính xác theo thứ tự ngược lại của hàng đợi. Suy nghĩ về một ví dụ, hãy đẩy mảng số nguyên {1, 2, 3, 4, 5}
lên một ngăn xếp. Nếu chúng ta bật các phần tử và in chúng cho đến khi ngăn xếp trống, chúng ta sẽ nhận được mảng theo thứ tự ngược lại, sẽ {5, 4, 3, 2, 1}
nhớ rằng với cùng một đầu vào, nếu chúng ta hủy hàng đợi cho đến khi hàng đợi trống, đầu ra sẽ {1, 2, 3, 4, 5}
. Vì vậy, rõ ràng là với cùng một thứ tự đầu vào của các phần tử, đầu ra của hàng đợi hoàn toàn ngược lại với đầu ra của ngăn xếp. Vì chúng ta biết cách đảo ngược một ngăn xếp bằng cách sử dụng một ngăn xếp bổ sung, chúng ta có thể xây dựng một hàng đợi bằng hai ngăn xếp.
Mô hình hàng đợi của chúng tôi sẽ bao gồm hai ngăn xếp. Một ngăn xếp sẽ được sử dụng cho enqueue
hoạt động (ngăn xếp số 1 ở bên trái, sẽ được gọi là Ngăn xếp đầu vào), một ngăn xếp khác sẽ được sử dụng cho dequeue
hoạt động (ngăn xếp số 2 ở bên phải, sẽ được gọi là Ngăn xếp đầu ra). Kiểm tra hình ảnh dưới đây;
Mã giả của chúng tôi là như dưới đây;
Push every input element to the Input Stack
If ( Output Stack is Empty)
pop every element in the Input Stack
and push them to the Output Stack until Input Stack is Empty
pop from Output Stack
Hãy liệt kê các số nguyên {1, 2, 3}
tương ứng. Các số nguyên sẽ được đẩy vào Ngăn xếp đầu vào ( Ngăn xếp số 1 ) nằm ở bên trái;
Sau đó, điều gì sẽ xảy ra nếu chúng ta thực hiện một hoạt động dequeue? Bất cứ khi nào một thao tác dequeue được thực thi, hàng đợi sẽ kiểm tra xem Stack đầu ra có trống hay không (xem mã giả ở trên) Nếu Stack đầu ra trống, thì Stack đầu vào sẽ được trích xuất trên đầu ra để các phần tử của Stack đầu vào sẽ được đảo ngược. Trước khi trả về một giá trị, trạng thái của hàng đợi sẽ như dưới đây;
Kiểm tra thứ tự của các phần tử trong Ngăn xếp đầu ra (Ngăn xếp số 2). Rõ ràng là chúng ta có thể bật các phần tử từ Ngăn xếp đầu ra để đầu ra sẽ giống như khi chúng ta thoát khỏi hàng đợi. Do đó, nếu chúng ta thực hiện hai thao tác dequeue, đầu tiên chúng ta sẽ nhận được {1, 2}
tương ứng. Sau đó, phần tử 3 sẽ là phần tử duy nhất của Ngăn xếp đầu ra và Ngăn xếp đầu vào sẽ trống. Nếu chúng ta liệt kê các phần tử 4 và 5, thì trạng thái của hàng đợi sẽ như sau;
Bây giờ Ngăn xếp đầu ra không trống và nếu chúng tôi thực hiện thao tác dequeue, chỉ có 3 sẽ được bật ra từ Ngăn xếp đầu ra. Sau đó, nhà nước sẽ được nhìn thấy như dưới đây;
Một lần nữa, nếu chúng ta thực hiện thêm hai thao tác dequeue, trên thao tác dequeue đầu tiên, hàng đợi sẽ kiểm tra xem Stack đầu ra có trống không, điều đó có đúng không. Sau đó bật ra các phần tử của Ngăn xếp đầu vào và đẩy chúng vào Ngăn xếp đầu ra cho đến khi Ngăn xếp đầu vào trống, sau đó trạng thái của Hàng đợi sẽ như dưới đây;
Dễ thấy, đầu ra của hai thao tác dequeue sẽ là {4, 5}
Đây là một triển khai trong Java. Tôi sẽ không sử dụng triển khai Stack hiện có nên ví dụ ở đây sẽ phát minh lại bánh xe;
public class MyStack<T> {
// inner generic Node class
private class Node<T> {
T data;
Node<T> next;
public Node(T data) {
this.data = data;
}
}
private Node<T> head;
private int size;
public void push(T e) {
Node<T> newElem = new Node(e);
if(head == null) {
head = newElem;
} else {
newElem.next = head;
head = newElem; // new elem on the top of the stack
}
size++;
}
public T pop() {
if(head == null)
return null;
T elem = head.data;
head = head.next; // top of the stack is head.next
size--;
return elem;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void printStack() {
System.out.print("Stack: ");
if(size == 0)
System.out.print("Empty !");
else
for(Node<T> temp = head; temp != null; temp = temp.next)
System.out.printf("%s ", temp.data);
System.out.printf("\n");
}
}
public class MyQueue<T> {
private MyStack<T> inputStack; // for enqueue
private MyStack<T> outputStack; // for dequeue
private int size;
public MyQueue() {
inputStack = new MyStack<>();
outputStack = new MyStack<>();
}
public void enqueue(T e) {
inputStack.push(e);
size++;
}
public T dequeue() {
// fill out all the Input if output stack is empty
if(outputStack.isEmpty())
while(!inputStack.isEmpty())
outputStack.push(inputStack.pop());
T temp = null;
if(!outputStack.isEmpty()) {
temp = outputStack.pop();
size--;
}
return temp;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
}
public class TestMyQueue {
public static void main(String[] args) {
MyQueue<Integer> queue = new MyQueue<>();
// enqueue integers 1..3
for(int i = 1; i <= 3; i++)
queue.enqueue(i);
// execute 2 dequeue operations
for(int i = 0; i < 2; i++)
System.out.println("Dequeued: " + queue.dequeue());
// enqueue integers 4..5
for(int i = 4; i <= 5; i++)
queue.enqueue(i);
// dequeue the rest
while(!queue.isEmpty())
System.out.println("Dequeued: " + queue.dequeue());
}
}
Dequeued: 1
Dequeued: 2
Dequeued: 3
Dequeued: 4
Dequeued: 5
Bạn thậm chí có thể mô phỏng một hàng đợi chỉ bằng một ngăn xếp. Ngăn xếp thứ hai (tạm thời) có thể được mô phỏng bằng ngăn xếp cuộc gọi của các cuộc gọi đệ quy đến phương thức chèn.
Nguyên tắc giữ nguyên khi chèn một phần tử mới vào hàng đợi:
Một lớp Queue chỉ sử dụng một Stack, sẽ như sau:
public class SimulatedQueue<E> {
private java.util.Stack<E> stack = new java.util.Stack<E>();
public void insert(E elem) {
if (!stack.empty()) {
E topElem = stack.pop();
insert(elem);
stack.push(topElem);
}
else
stack.push(elem);
}
public E remove() {
return stack.pop();
}
}
n items
vào hàng đợi bằng cấu trúc dữ liệu trên. tổng (1 + 2 + 4 + 8 + .... + 2(n-1))
kết quả trong ~O(n^2)
. Tôi hy vọng bạn có được điểm.
Sự phức tạp thời gian sẽ tồi tệ hơn, mặc dù. Một thực hiện hàng đợi tốt làm mọi thứ trong thời gian liên tục.
Biên tập
Không chắc chắn tại sao câu trả lời của tôi đã bị đánh giá thấp ở đây. Nếu chúng tôi lập trình, chúng tôi quan tâm đến sự phức tạp về thời gian và sử dụng hai ngăn xếp tiêu chuẩn để thực hiện một hàng đợi là không hiệu quả. Đó là một điểm rất hợp lệ và có liên quan. Nếu ai đó cảm thấy cần phải hạ thấp điều này nhiều hơn, tôi sẽ quan tâm để biết tại sao.
Chi tiết hơn một chút : về lý do tại sao sử dụng hai ngăn xếp lại tệ hơn chỉ là một hàng đợi: nếu bạn sử dụng hai ngăn xếp và ai đó gọi dequeue trong khi hộp thư đi trống, bạn cần thời gian tuyến tính để đến cuối hộp thư đến (như bạn có thể thấy trong mã của Dave).
Bạn có thể triển khai hàng đợi dưới dạng danh sách liên kết đơn (mỗi phần tử trỏ đến phần tử được chèn tiếp theo), giữ một con trỏ bổ sung cho phần tử được chèn cuối cùng để đẩy (hoặc biến nó thành danh sách tuần hoàn). Việc thực hiện hàng đợi và dequeue trên cấu trúc dữ liệu này rất dễ thực hiện trong thời gian liên tục. Đó là trường hợp xấu nhất trong thời gian không đổi, không được khấu hao. Và, như các ý kiến dường như yêu cầu làm rõ điều này, thời gian liên tục trong trường hợp xấu nhất hoàn toàn tốt hơn thời gian không đổi được khấu hao.
Đặt hàng đợi được thực hiện là q và ngăn xếp được sử dụng để thực hiện q là stack1 và stack2.
q có thể được thực hiện theo hai cách:
Phương pháp 1 (Bằng cách thực hiện thao tác enQueue tốn kém)
Phương thức này đảm bảo rằng phần tử mới được nhập luôn nằm ở đầu ngăn xếp 1, do đó thao tác deQueue chỉ bật từ stack1. Để đặt phần tử ở đầu stack1, stack2 được sử dụng.
enQueue(q, x)
1) While stack1 is not empty, push everything from stack1 to stack2.
2) Push x to stack1 (assuming size of stacks is unlimited).
3) Push everything back to stack1.
deQueue(q)
1) If stack1 is empty then error
2) Pop an item from stack1 and return it.
Phương pháp 2 (Bằng cách làm cho hoạt động deQueue tốn kém)
Trong phương thức này, trong hoạt động en-queue, phần tử mới được nhập ở đầu stack1. Trong hoạt động hủy hàng đợi, nếu stack2 trống thì tất cả các phần tử được chuyển sang stack2 và cuối cùng đỉnh của stack2 được trả về.
enQueue(q, x)
1) Push x to stack1 (assuming size of stacks is unlimited).
deQueue(q)
1) If both stacks are empty then error.
2) If stack2 is empty
While stack1 is not empty, push everything from stack1 to stack2.
3) Pop the element from stack2 and return it.
Phương pháp 2 chắc chắn tốt hơn phương pháp 1. Phương pháp 1 di chuyển tất cả các phần tử hai lần trong hoạt động enQueue, trong khi phương thức 2 (trong hoạt động deQueue) di chuyển các phần tử một lần và chỉ di chuyển các phần tử nếu stack2 trống.
Một giải pháp trong c #
public class Queue<T> where T : class
{
private Stack<T> input = new Stack<T>();
private Stack<T> output = new Stack<T>();
public void Enqueue(T t)
{
input.Push(t);
}
public T Dequeue()
{
if (output.Count == 0)
{
while (input.Count != 0)
{
output.Push(input.Pop());
}
}
return output.Pop();
}
}
Hai ngăn xếp trong hàng đợi được định nghĩa là stack1 và stack2 .
Enqueue: Các yếu tố euqueued luôn được đẩy vào stack1
Dequeue: Phần đầu của stack2 có thể được bật ra vì đây là phần tử đầu tiên được chèn vào hàng đợi khi stack2 không trống. Khi stack2 trống, chúng ta bật tất cả các phần tử từ stack1 và đẩy chúng vào stack2 từng cái một. Phần tử đầu tiên trong hàng đợi được đẩy xuống dưới cùng của stack1 . Nó có thể được bật ra trực tiếp sau khi hoạt động popping và đẩy vì nó nằm trên đỉnh stack2 .
Dưới đây là mã mẫu C ++ giống nhau:
template <typename T> class CQueue
{
public:
CQueue(void);
~CQueue(void);
void appendTail(const T& node);
T deleteHead();
private:
stack<T> stack1;
stack<T> stack2;
};
template<typename T> void CQueue<T>::appendTail(const T& element) {
stack1.push(element);
}
template<typename T> T CQueue<T>::deleteHead() {
if(stack2.size()<= 0) {
while(stack1.size()>0) {
T& data = stack1.top();
stack1.pop();
stack2.push(data);
}
}
if(stack2.size() == 0)
throw new exception("queue is empty");
T head = stack2.top();
stack2.pop();
return head;
}
Giải pháp này được mượn từ blog của tôi . Phân tích chi tiết hơn với các mô phỏng hoạt động từng bước có sẵn trong trang web blog của tôi.
Bạn sẽ phải bật mọi thứ ra khỏi ngăn xếp đầu tiên để có được phần tử dưới cùng. Sau đó đặt tất cả chúng trở lại vào ngăn xếp thứ hai cho mọi hoạt động "dequeue".
Đối với nhà phát triển c # ở đây là chương trình hoàn chỉnh:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QueueImplimentationUsingStack
{
class Program
{
public class Stack<T>
{
public int size;
public Node<T> head;
public void Push(T data)
{
Node<T> node = new Node<T>();
node.data = data;
if (head == null)
head = node;
else
{
node.link = head;
head = node;
}
size++;
Display();
}
public Node<T> Pop()
{
if (head == null)
return null;
else
{
Node<T> temp = head;
//temp.link = null;
head = head.link;
size--;
Display();
return temp;
}
}
public void Display()
{
if (size == 0)
Console.WriteLine("Empty");
else
{
Console.Clear();
Node<T> temp = head;
while (temp!= null)
{
Console.WriteLine(temp.data);
temp = temp.link;
}
}
}
}
public class Queue<T>
{
public int size;
public Stack<T> inbox;
public Stack<T> outbox;
public Queue()
{
inbox = new Stack<T>();
outbox = new Stack<T>();
}
public void EnQueue(T data)
{
inbox.Push(data);
size++;
}
public Node<T> DeQueue()
{
if (outbox.size == 0)
{
while (inbox.size != 0)
{
outbox.Push(inbox.Pop().data);
}
}
Node<T> temp = new Node<T>();
if (outbox.size != 0)
{
temp = outbox.Pop();
size--;
}
return temp;
}
}
public class Node<T>
{
public T data;
public Node<T> link;
}
static void Main(string[] args)
{
Queue<int> q = new Queue<int>();
for (int i = 1; i <= 3; i++)
q.EnQueue(i);
// q.Display();
for (int i = 1; i < 3; i++)
q.DeQueue();
//q.Display();
Console.ReadKey();
}
}
}
Thực hiện các hoạt động sau đây của một hàng đợi bằng cách sử dụng ngăn xếp.
đẩy (x) - Đẩy phần tử x vào phía sau hàng đợi.
pop () - Loại bỏ phần tử từ phía trước hàng đợi.
peek () - Lấy phần tử phía trước.
blank () - Trả về cho dù hàng đợi trống.
class MyQueue {
Stack<Integer> input;
Stack<Integer> output;
/** Initialize your data structure here. */
public MyQueue() {
input = new Stack<Integer>();
output = new Stack<Integer>();
}
/** Push element x to the back of queue. */
public void push(int x) {
input.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
peek();
return output.pop();
}
/** Get the front element. */
public int peek() {
if(output.isEmpty()) {
while(!input.isEmpty()) {
output.push(input.pop());
}
}
return output.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return input.isEmpty() && output.isEmpty();
}
}
// Two stacks s1 Original and s2 as Temp one
private Stack<Integer> s1 = new Stack<Integer>();
private Stack<Integer> s2 = new Stack<Integer>();
/*
* Here we insert the data into the stack and if data all ready exist on
* stack than we copy the entire stack s1 to s2 recursively and push the new
* element data onto s1 and than again recursively call the s2 to pop on s1.
*
* Note here we can use either way ie We can keep pushing on s1 and than
* while popping we can remove the first element from s2 by copying
* recursively the data and removing the first index element.
*/
public void insert( int data )
{
if( s1.size() == 0 )
{
s1.push( data );
}
else
{
while( !s1.isEmpty() )
{
s2.push( s1.pop() );
}
s1.push( data );
while( !s2.isEmpty() )
{
s1.push( s2.pop() );
}
}
}
public void remove()
{
if( s1.isEmpty() )
{
System.out.println( "Empty" );
}
else
{
s1.pop();
}
}
Việc triển khai hàng đợi bằng hai ngăn xếp trong Swift:
struct Stack<Element> {
var items = [Element]()
var count : Int {
return items.count
}
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
return items.removeLast()
}
func peek() -> Element? {
return items.last
}
}
struct Queue<Element> {
var inStack = Stack<Element>()
var outStack = Stack<Element>()
mutating func enqueue(_ item: Element) {
inStack.push(item)
}
mutating func dequeue() -> Element? {
fillOutStack()
return outStack.pop()
}
mutating func peek() -> Element? {
fillOutStack()
return outStack.peek()
}
private mutating func fillOutStack() {
if outStack.count == 0 {
while inStack.count != 0 {
outStack.push(inStack.pop()!)
}
}
}
}
Mặc dù bạn sẽ nhận được rất nhiều bài đăng liên quan đến việc thực hiện hàng đợi với hai ngăn xếp: 1. Hoặc bằng cách làm cho quá trình enQueue tốn kém hơn rất nhiều 2. Hoặc bằng cách làm cho quá trình deQueue tốn kém hơn rất nhiều
https://www.geekforgeek.org/queue-USE-stacks/
Một cách quan trọng tôi đã tìm ra từ bài viết trên là xây dựng hàng đợi chỉ với cấu trúc dữ liệu ngăn xếp và ngăn xếp cuộc gọi đệ quy.
Mặc dù người ta có thể lập luận rằng theo nghĩa đen, điều này vẫn đang sử dụng hai ngăn xếp, nhưng lý tưởng nhất là việc này chỉ sử dụng một cấu trúc dữ liệu ngăn xếp.
Dưới đây là lời giải thích của vấn đề:
Khai báo một ngăn xếp để enQueue và khử dữ liệu và đẩy dữ liệu vào ngăn xếp.
trong khi deQueueing có một điều kiện cơ bản trong đó phần tử của ngăn xếp được bật khi kích thước của ngăn xếp là 1. Điều này sẽ đảm bảo rằng không có tràn ngăn xếp trong quá trình đệ quy deQueue.
Trong khi deQueueing đầu tiên bật dữ liệu từ đầu ngăn xếp. Lý tưởng nhất là phần tử này sẽ là phần tử có mặt ở đầu ngăn xếp. Bây giờ một khi điều này được thực hiện, hãy gọi đệ quy hàm deQueue và sau đó đẩy phần tử bật ở trên trở lại vào ngăn xếp.
Mã sẽ trông như dưới đây:
if (s1.isEmpty())
System.out.println("The Queue is empty");
else if (s1.size() == 1)
return s1.pop();
else {
int x = s1.pop();
int result = deQueue();
s1.push(x);
return result;
Bằng cách này, bạn có thể tạo một hàng đợi bằng cách sử dụng cấu trúc dữ liệu ngăn xếp đơn và ngăn xếp cuộc gọi đệ quy.
Dưới đây là giải pháp trong ngôn ngữ javascript sử dụng cú pháp ES6.
Stack.js
//stack using array
class Stack {
constructor() {
this.data = [];
}
push(data) {
this.data.push(data);
}
pop() {
return this.data.pop();
}
peek() {
return this.data[this.data.length - 1];
}
size(){
return this.data.length;
}
}
export { Stack };
QueueUsingTwoStacks.js
import { Stack } from "./Stack";
class QueueUsingTwoStacks {
constructor() {
this.stack1 = new Stack();
this.stack2 = new Stack();
}
enqueue(data) {
this.stack1.push(data);
}
dequeue() {
//if both stacks are empty, return undefined
if (this.stack1.size() === 0 && this.stack2.size() === 0)
return undefined;
//if stack2 is empty, pop all elements from stack1 to stack2 till stack1 is empty
if (this.stack2.size() === 0) {
while (this.stack1.size() !== 0) {
this.stack2.push(this.stack1.pop());
}
}
//pop and return the element from stack 2
return this.stack2.pop();
}
}
export { QueueUsingTwoStacks };
Dưới đây là cách sử dụng:
index.js
import { StackUsingTwoQueues } from './StackUsingTwoQueues';
let que = new QueueUsingTwoStacks();
que.enqueue("A");
que.enqueue("B");
que.enqueue("C");
console.log(que.dequeue()); //output: "A"
stack1
. Khi bạn đi đến dequeue
một lần nữa, bạn sẽ chuyển chúng vào các mục stack2
, đặt chúng trước những gì đã có.
Tôi sẽ trả lời câu hỏi này trong Go vì Go không có nhiều bộ sưu tập phong phú trong thư viện tiêu chuẩn của nó.
Vì một ngăn xếp thực sự dễ thực hiện, tôi nghĩ tôi nên thử và sử dụng hai ngăn xếp để thực hiện một hàng đợi kết thúc kép. Để hiểu rõ hơn về cách tôi đi đến câu trả lời của mình, tôi đã chia phần thực hiện thành hai phần, phần đầu hy vọng dễ hiểu hơn nhưng chưa hoàn chỉnh.
type IntQueue struct {
front []int
back []int
}
func (q *IntQueue) PushFront(v int) {
q.front = append(q.front, v)
}
func (q *IntQueue) Front() int {
if len(q.front) > 0 {
return q.front[len(q.front)-1]
} else {
return q.back[0]
}
}
func (q *IntQueue) PopFront() {
if len(q.front) > 0 {
q.front = q.front[:len(q.front)-1]
} else {
q.back = q.back[1:]
}
}
func (q *IntQueue) PushBack(v int) {
q.back = append(q.back, v)
}
func (q *IntQueue) Back() int {
if len(q.back) > 0 {
return q.back[len(q.back)-1]
} else {
return q.front[0]
}
}
func (q *IntQueue) PopBack() {
if len(q.back) > 0 {
q.back = q.back[:len(q.back)-1]
} else {
q.front = q.front[1:]
}
}
Về cơ bản, đó là hai ngăn xếp mà chúng ta cho phép phần dưới của các ngăn xếp được thao tác với nhau. Tôi cũng đã sử dụng các quy ước đặt tên STL, trong đó các thao tác đẩy, bật, nhìn truyền thống của ngăn xếp có tiền tố trước / sau cho dù chúng tham chiếu đến mặt trước hay mặt sau của hàng đợi.
Vấn đề với đoạn mã trên là nó không sử dụng bộ nhớ rất hiệu quả. Trên thực tế, nó phát triển vô tận cho đến khi bạn hết không gian. Nó rất là tệ. Cách khắc phục cho việc này chỉ đơn giản là sử dụng lại phần dưới của không gian ngăn xếp bất cứ khi nào có thể. Chúng tôi phải giới thiệu một phần bù để theo dõi điều này vì một lát cắt trong Go không thể phát triển ở phía trước một khi được thu nhỏ.
type IntQueue struct {
front []int
frontOffset int
back []int
backOffset int
}
func (q *IntQueue) PushFront(v int) {
if q.backOffset > 0 {
i := q.backOffset - 1
q.back[i] = v
q.backOffset = i
} else {
q.front = append(q.front, v)
}
}
func (q *IntQueue) Front() int {
if len(q.front) > 0 {
return q.front[len(q.front)-1]
} else {
return q.back[q.backOffset]
}
}
func (q *IntQueue) PopFront() {
if len(q.front) > 0 {
q.front = q.front[:len(q.front)-1]
} else {
if len(q.back) > 0 {
q.backOffset++
} else {
panic("Cannot pop front of empty queue.")
}
}
}
func (q *IntQueue) PushBack(v int) {
if q.frontOffset > 0 {
i := q.frontOffset - 1
q.front[i] = v
q.frontOffset = i
} else {
q.back = append(q.back, v)
}
}
func (q *IntQueue) Back() int {
if len(q.back) > 0 {
return q.back[len(q.back)-1]
} else {
return q.front[q.frontOffset]
}
}
func (q *IntQueue) PopBack() {
if len(q.back) > 0 {
q.back = q.back[:len(q.back)-1]
} else {
if len(q.front) > 0 {
q.frontOffset++
} else {
panic("Cannot pop back of empty queue.")
}
}
}
Đó là rất nhiều chức năng nhỏ nhưng trong số 6 chức năng thì 3 chức năng này chỉ là gương của người khác.
đây là giải pháp của tôi trong java bằng cách sử dụng danh sách liên kết.
class queue<T>{
static class Node<T>{
private T data;
private Node<T> next;
Node(T data){
this.data = data;
next = null;
}
}
Node firstTop;
Node secondTop;
void push(T data){
Node temp = new Node(data);
temp.next = firstTop;
firstTop = temp;
}
void pop(){
if(firstTop == null){
return;
}
Node temp = firstTop;
while(temp != null){
Node temp1 = new Node(temp.data);
temp1.next = secondTop;
secondTop = temp1;
temp = temp.next;
}
secondTop = secondTop.next;
firstTop = null;
while(secondTop != null){
Node temp3 = new Node(secondTop.data);
temp3.next = firstTop;
firstTop = temp3;
secondTop = secondTop.next;
}
}
}
Lưu ý: Trong trường hợp này, hoạt động pop rất tốn thời gian. Vì vậy, tôi sẽ không đề xuất tạo một hàng đợi bằng hai ngăn xếp.
Với O(1)
dequeue()
, giống như câu trả lời của pythonquick :
// time: O(n), space: O(n)
enqueue(x):
if stack.isEmpty():
stack.push(x)
return
temp = stack.pop()
enqueue(x)
stack.push(temp)
// time: O(1)
x dequeue():
return stack.pop()
Với O(1)
enqueue()
(điều này không được đề cập trong bài đăng này vì vậy câu trả lời này), cũng sử dụng quay lui để bong bóng và trả lại các mục cuối cùng.
// O(1)
enqueue(x):
stack.push(x)
// time: O(n), space: O(n)
x dequeue():
temp = stack.pop()
if stack.isEmpty():
x = temp
else:
x = dequeue()
stack.push(temp)
return x
Rõ ràng, đó là một bài tập mã hóa tốt vì nó không hiệu quả nhưng vẫn thanh lịch.
** Giải pháp JS dễ dàng **
/*
enQueue(q, x)
1) Push x to stack1 (assuming size of stacks is unlimited).
deQueue(q)
1) If both stacks are empty then error.
2) If stack2 is empty
While stack1 is not empty, push everything from stack1 to stack2.
3) Pop the element from stack2 and return it.
*/
class myQueue {
constructor() {
this.stack1 = [];
this.stack2 = [];
}
push(item) {
this.stack1.push(item)
}
remove() {
if (this.stack1.length == 0 && this.stack2.length == 0) {
return "Stack are empty"
}
if (this.stack2.length == 0) {
while (this.stack1.length != 0) {
this.stack2.push(this.stack1.pop())
}
}
return this.stack2.pop()
}
peek() {
if (this.stack2.length == 0 && this.stack1.length == 0) {
return 'Empty list'
}
if (this.stack2.length == 0) {
while (this.stack1.length != 0) {
this.stack2.push(this.stack1.pop())
}
}
return this.stack2[0]
}
isEmpty() {
return this.stack2.length === 0 && this.stack1.length === 0;
}
}
const q = new myQueue();
q.push(1);
q.push(2);
q.push(3);
q.remove()
console.log(q)
public class QueueUsingStacks<T>
{
private LinkedListStack<T> stack1;
private LinkedListStack<T> stack2;
public QueueUsingStacks()
{
stack1=new LinkedListStack<T>();
stack2 = new LinkedListStack<T>();
}
public void Copy(LinkedListStack<T> source,LinkedListStack<T> dest )
{
while(source.Head!=null)
{
dest.Push(source.Head.Data);
source.Head = source.Head.Next;
}
}
public void Enqueue(T entry)
{
stack1.Push(entry);
}
public T Dequeue()
{
T obj;
if (stack2 != null)
{
Copy(stack1, stack2);
obj = stack2.Pop();
Copy(stack2, stack1);
}
else
{
throw new Exception("Stack is empty");
}
return obj;
}
public void Display()
{
stack1.Display();
}
}
Đối với mọi hoạt động enqueue, chúng tôi thêm vào đầu stack1. Đối với mỗi dequeue, chúng ta làm trống nội dung của stack1 vào stack2 và loại bỏ phần tử ở đầu stack. Độ phức tạp của thời gian là O (n) cho dequeue, vì chúng ta phải sao chép stack1 sang stack2. độ phức tạp thời gian của enqueue giống như một ngăn xếp thông thường
if (stack2 != null)
luôn luôn đúng vì stack2
được khởi tạo trong hàm tạo.
Triển khai hàng đợi bằng hai đối tượng java.util.Stack:
public final class QueueUsingStacks<E> {
private final Stack<E> iStack = new Stack<>();
private final Stack<E> oStack = new Stack<>();
public void enqueue(E e) {
iStack.push(e);
}
public E dequeue() {
if (oStack.isEmpty()) {
if (iStack.isEmpty()) {
throw new NoSuchElementException("No elements present in Queue");
}
while (!iStack.isEmpty()) {
oStack.push(iStack.pop());
}
}
return oStack.pop();
}
public boolean isEmpty() {
if (oStack.isEmpty() && iStack.isEmpty()) {
return true;
}
return false;
}
public int size() {
return iStack.size() + oStack.size();
}
}
return inbox.isEmpty() && outbox.isEmpty()
và return inbox.size() + outbox.size()
, tương ứng. Mã của Dave L. đã ném một ngoại lệ khi bạn thoát khỏi hàng đợi trống. Câu hỏi ban đầu thậm chí không phải là về Java; đó là về cấu trúc dữ liệu / thuật toán nói chung. Việc triển khai Java chỉ là một minh họa bổ sung.