Відповіді:
Зберігайте 2 стеки, давайте назвемо їх inbox
і outbox
.
Запитання :
inbox
Dequeue :
Якщо outbox
порожній, поповніть його, вискакуючи з нього кожен елемент inbox
і натисніть на ньогоoutbox
Розмістіть і поверніть верхній елемент з outbox
Використовуючи цей метод, кожен елемент буде знаходитись у кожній стеці рівно один раз - це означає, що кожен елемент буде натиснений двічі і вискакується двічі, даючи амортизовані операції постійного часу.
Ось реалізація на 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();
}
}
Щоб зрозуміти, як побудувати чергу за допомогою двох стеків, слід зрозуміти, як повернути стек кришталево. Пам’ятайте, як працює стек, він дуже схожий на стек посуду на вашій кухні. Останнє вимите посуд опиниться у верхній частині чистої стопки, яку називають L ast I n F irst O в інформатиці ut (LIFO).
Давайте уявимо нашу стопку, як пляшку, як нижче;
Якщо ми натиснемо цілі числа 1,2,3 відповідно, то 3 буде на вершині стека. Оскільки 1 буде натиснуто спочатку, то 2 буде поставлено на вершину 1. Нарешті, 3 буде розміщено на вершині стопки, а останнє стан нашого стека, представлене як пляшка, буде як нижче;
Тепер у нас стек представлений як пляшка заповнена значеннями 3,2,1. І ми хочемо повернути стек так, щоб верхній елемент стека був 1, а нижній елемент стека був 3. Що ми можемо зробити? Ми можемо взяти пляшку і потримати її догори дном, щоб усі значення повинні були змінитись у порядку?
Так, ми можемо це зробити, але це пляшка. Для того ж процесу нам потрібно мати другий стек, який буде зберігати перші елементи стека у зворотному порядку. Покладемо наш заселений стек зліва і наш новий порожній стек праворуч. Щоб змінити порядок елементів, ми збираємося виводити кожен елемент з лівого стека та переміщувати їх у правий стек. Ви можете бачити, що відбувається, як ми робимо це, на зображенні нижче;
Тож ми знаємо, як повернути стек.
У попередній частині я пояснив, як можна змінити порядок елементів стеку. Це було важливо, тому що якщо ми натиснемо та виведемо елементи до стеку, вихід буде точно у зворотному порядку черги. Розмірковуючи про приклад, давайте підсунемо масив цілих чисел {1, 2, 3, 4, 5}
до стека. Якщо ми спливемо елементи та надрукуємо їх до тих пір, поки стек не буде порожнім, ми отримаємо масив у зворотному порядку поштовхового порядку, який буде {5, 4, 3, 2, 1}
пам'ятати, що для того ж входу, якщо ми відкладемо чергу, поки черга не буде порожньою, вихід буде{1, 2, 3, 4, 5}
. Тож очевидно, що для одного і того ж порядку введення елементів вихід черги є точно зворотним до виводу стека. Оскільки ми знаємо, як повернути стек, використовуючи додатковий стек, ми можемо побудувати чергу, використовуючи дві стеки.
Наша модель черги буде складатися з двох стеків. Один стек буде використовуватися для enqueue
роботи (стек №1 зліва, буде називатися вхідним стеком), інший стек буде використаний для dequeue
операції (стек №2 справа, буде називатися вихідним стеком). Перегляньте зображення нижче;
Наш псевдокод наведений нижче;
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
Зробимо {1, 2, 3}
відповідно цілі числа . Цілі особи будуть висунуті на стек вводу ( стек №1 ), який знаходиться зліва;
Тоді, що буде, якщо ми виконаємо операцію dequue? Щоразу, коли виконується операція декерування, черга збирається перевірити, чи стек виводу порожній чи ні (див. Псевдо-код вище). Якщо стек вихідного сигналу порожній, тоді стек вводу буде вилучений на виході, так що елементи стека вводу буде повернено. Перед поверненням значення стан черги буде таким, як нижче;
Перевірте порядок елементів у стеку виводу (стек №2). Очевидно, що ми можемо спливати елементи з вихідного стеку, щоб результат був таким самим, як якщо б ми вийшли з черги. Таким чином, якщо ми виконаємо дві операції dequeue, спочатку отримаємо {1, 2}
відповідно. Тоді елемент 3 буде єдиним елементом стеку виводу, а стек вводу буде порожнім. Якщо ми зав'яжемо елементи 4 та 5, то стан черги буде таким;
Тепер стек вихідного сигналу не порожній, і якщо ми виконаємо операцію з видалення, із стеку виводу буде виведено лише 3. Тоді стан буде розглядатися як нижче;
Знову ж таки, якщо ми виконаємо ще дві операції з черговою чергою, під час першої операції з черговою чергою черга перевірить, чи порожній вихідний стек, що правда. Потім вискочіть елементи вхідного стеку і натисніть їх на вихідний стек, поки стек вводу порожній, тоді стан черги буде, як показано нижче;
Легко помітити, вихід двох операцій з декеюванням буде {4, 5}
Ось реалізація в Java. Я не збираюся використовувати існуючу реалізацію Stack, тому приклад тут буде винаходити колесо;
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
Ви навіть можете імітувати чергу, використовуючи лише один стек. Другий (тимчасовий) стек може бути імітований стеком викликів рекурсивних викликів методом вставки.
Принцип залишається таким же, коли вставляєте новий елемент у чергу:
Клас черги, що використовує лише один стек, буде таким:
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
у чергу, використовуючи вищевказану структуру даних, потрібен квадратичний час O (n ^ 2) . сума (1 + 2 + 4 + 8 + .... + 2(n-1))
результатів в ~O(n^2)
. Я сподіваюся, що ви зрозумієте, що
Однак складності в часі були б гіршими. Хороша реалізація черги робить все в постійний час.
Редагувати
Не впевнений, чому мою відповідь тут спростовано. Якщо ми програмуємо, ми дбаємо про складність часу, а використання двох стандартних стеків для створення черги неефективно. Це дуже вагомий і відповідний момент. Якщо хтось інший відчує необхідність спростувати це більше, мені було б цікаво дізнатися, чому.
Трохи детальніше : чому використання двох стеків гірше, ніж просто черга: якщо ви використовуєте дві стеки, а хтось дзвонить у черзі, поки вихідна скринька порожня, вам потрібен лінійний час, щоб дістатися до нижньої частини папки "Вхідні" в коді Дейва).
Ви можете реалізувати чергу як окремо пов'язаний список (кожен елемент вказує на наступний вставлений елемент), зберігаючи додатковий вказівник на останній вставлений елемент для натискань (або роблячи його циклічним списком). Реалізацію черги та черги в цій структурі даних дуже просто зробити за постійний час. Це найгірший постійний час, не амортизований. Оскільки коментарі вимагають цього роз'яснення, постійний час у гіршому випадку суворо кращий, ніж постійний амортизований час.
Нехай чергою, яку потрібно реалізувати, є q, а стеки, які використовуються для реалізації q, будуть stack1 та stack2.
q може бути реалізовано двома способами:
Спосіб 1 (Дорога операція enQueue)
Цей метод гарантує, що щойно введений елемент завжди знаходиться у верхній частині стека 1, так що операція deQueue просто вискакує зі стека1. Для розміщення елемента в вершині стека1 використовується stack2.
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.
Спосіб 2 (Дорога операція deQueue)
У цьому методі при операції з чергою новий елемент вводиться у верхній частині стека1. У режимі де-черги, якщо stack2 порожній, всі елементи переміщуються в stack2 і, нарешті, повертається вершина stack2.
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.
Спосіб 2, безумовно, кращий, ніж метод 1. Метод 1 переміщує всі елементи двічі під час роботи enQueue, тоді як метод 2 (в операції deQueue) переміщує елементи один раз і переміщує елементи лише у тому випадку, якщо stack2 порожній.
Рішення в 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();
}
}
Два стеки у черзі визначаються як stack1 та stack2 .
Enqueue: Евкейовані елементи завжди висуваються в стек1
Ведіеій: Вершина stack2 може бути вискочило , так як це перший елемент вставляється в чергу , коли stack2 не пустили. Коли stack2 порожній, ми вискакуємо всі елементи з stack1 і штовхаємо їх у stack2 один за одним. Перший елемент черги висувається в нижню частину стека1 . Він може вискочити безпосередньо після операцій з виштовхуванням та натисканням, оскільки він знаходиться у верхній частині стека2 .
Нижче наведений той самий код вибірки C ++:
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;
}
Це рішення запозичено з мого блогу . Більш детальний аналіз з покроковими моделюваннями роботи доступний на веб-сторінці мого блогу.
Вам доведеться вискакувати все з першого стеку, щоб отримати нижній елемент. Потім покладіть їх назад на другий стек для кожної операції "dequeue".
для розробника c # ось повна програма:
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();
}
}
}
Виконайте наступні операції черги, використовуючи стеки.
push (x) - Натисніть на елемент x задній частині черги.
pop () - видаляє елемент перед чергою.
peek () - Отримайте передній елемент.
empty () - Повертає чи порожня черга.
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();
}
}
Реалізація черги з використанням двох стеків у 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()!)
}
}
}
}
Хоча ви отримаєте багато публікацій, пов’язаних із впровадженням черги з двома стеками: 1. Або зробивши процес enQueue набагато дорожчим 2. Або зробивши процес deQueue набагато дорожчим
https://www.geeksforgeeks.org/queue-using-stacks/
Один важливий спосіб, який я дізнався з вищезгаданого поста, - це побудова черги з лише структурою даних стека та стеком виклику рекурсії.
Хоча можна стверджувати, що буквально для цього все-таки використовується два стеки, але в ідеалі для цього використовується лише одна структура даних стека.
Нижче наведено пояснення проблеми:
Оголосіть єдиний стек для введення в дію черг і відключення даних і висуньте їх у стек.
в той час як deQueueing мають базову умову, коли елемент стека з'являється, коли розмір стека дорівнює 1. Це забезпечить відсутність переповнення стеку під час рекурсії deQueue.
Під час deQueueing спочатку виводите дані з верхньої частини стека. В ідеалі цей елемент буде тим елементом, який присутній у верхній частині стека. Тепер, коли це зроблено, рекурсивно викликайте функцію deQueue, а потім штовхайте елемент, що вискочив вище, назад у стек.
Код буде виглядати нижче:
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;
Таким чином ви можете створити чергу, використовуючи структуру даних одного стека та стек виклику рекурсії.
Нижче наведено рішення мовою javascript з використанням синтаксису 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 };
Нижче наведено використання:
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
. Коли ви dequeue
знову переходите до них , ви переміщуватимете в них предмети stack2
, висуваючи їх перед тим, що вже було.
Я відповім на це запитання у Go, тому що Go не має багато багато колекцій у своїй стандартній бібліотеці.
Оскільки стек реально легко реалізувати, я подумав, що спробую використати два стеки, щоб виконати подвійну чергу. Щоб краще зрозуміти, як я дійшов до своєї відповіді, я розділив реалізацію на дві частини, сподіваємось, першу частину простіше зрозуміти, але вона неповна.
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:]
}
}
Це, в основному, дві стеки, де ми дозволяємо маніпулювати нижньою частиною один одного. Я також використовував конвенції про іменування STL, де традиційні операції натискання, поп, пік стека мають префікс передній / задній частині, чи стосується вони передньої чи задньої черги.
Проблема з вищевказаним кодом полягає в тому, що він не використовує пам'ять дуже ефективно. Насправді він росте нескінченно, поки не вистачає місця. Це справді погано. Виправлення цього полягає в тому, щоб, коли це можливо, просто використати нижню частину місця стеку. Ми повинні ввести зміщення, щоб відстежувати це, оскільки фрагмент у Go не може рости спереду, як тільки зменшився.
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.")
}
}
}
Це дуже багато маленьких функцій, але з 6 функцій 3 з них - лише дзеркала іншої.
ось моє рішення в java за допомогою пов'язаного списку.
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;
}
}
}
Примітка. У цьому випадку поп-операція вимагає багато часу. Тому я не пропоную створювати чергу за допомогою двох стеків.
З O(1)
dequeue()
, що таке, як відповідь 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()
З O(1)
enqueue()
(про це не згадується у цій публікації, тому ця відповідь), який також використовує зворотний трек для підключення та повернення нижнього елемента.
// 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
Очевидно, що це хороша вправа кодування, оскільки вона неефективна, але елегантна.
** Легке рішення JS **
/*
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();
}
}
Для кожної операції enqueue додаємо до вершини стека1. Для кожного dequeue ми видаляємо вміст stack1 в stack2 і видаляємо елемент у верхній частині стека. Складність часу - O (n) для dequeue, оскільки ми повинні скопіювати stack1 в stack2. часова складність анкету така ж, як і звичайний стек
if (stack2 != null)
завжди правдивий, оскільки stack2
інстанціюється в конструкторі.
Реалізація черги за допомогою двох об’єктів 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()
і return inbox.size() + outbox.size()
, відповідно. Код Дейва Л. вже видає виняток, коли ви виходите з порожньої черги. Оригінальне питання стосувалося навіть не Java; мова йшла про структури даних / алгоритми загалом. Реалізація Java була лише додатковою ілюстрацією.