Antworten:
Behalte 2 Stapel, nennen wir sie inbox
und outbox
.
Enqueue :
inbox
Warteschlange :
Wenn outbox
es leer ist, füllen Sie inbox
es wieder auf, indem Sie jedes Element herausnehmen und darauf schiebenoutbox
Pop und geben Sie das oberste Element von zurück outbox
Bei dieser Methode befindet sich jedes Element genau einmal in jedem Stapel. Dies bedeutet, dass jedes Element zweimal gedrückt und zweimal gepoppt wird, was zu amortisierten Operationen mit konstanter Zeit führt.
Hier ist eine Implementierung in 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();
}
}
Um zu verstehen, wie eine Warteschlange mit zwei Stapeln erstellt wird, sollten Sie wissen, wie Sie einen Stapel kristallklar umkehren. Denken Sie daran, wie der Stapel funktioniert. Er ist dem Geschirrstapel in Ihrer Küche sehr ähnlich. Das zuletzt gewaschene Gericht befindet sich oben auf dem sauberen Stapel, der als L ast I n F irst O bezeichnet wird in der Informatik ut (LIFO) bezeichnet wird.
Stellen wir uns unseren Stapel wie eine Flasche vor;
Wenn wir die Ganzzahlen 1,2,3 drücken, befindet sich 3 oben auf dem Stapel. Da 1 zuerst gedrückt wird, wird 2 oben auf 1 gesetzt. Zuletzt wird 3 oben auf den Stapel gelegt, und der letzte Status unseres Stapels, der als Flasche dargestellt wird, ist wie folgt.
Jetzt haben wir unseren Stapel so dargestellt, dass eine Flasche mit den Werten 3,2,1 gefüllt ist. Und wir wollen den Stapel umkehren, so dass das obere Element des Stapels 1 und das untere Element des Stapels 3 ist. Was können wir tun? Wir können die Flasche nehmen und sie verkehrt herum halten, damit sich alle Werte in der Reihenfolge umkehren?
Ja, das können wir, aber das ist eine Flasche. Um den gleichen Prozess durchzuführen, benötigen wir einen zweiten Stapel, in dem die ersten Stapelelemente in umgekehrter Reihenfolge gespeichert werden. Lassen Sie uns unseren bestückten Stapel links und unseren neuen leeren Stapel rechts platzieren. Um die Reihenfolge der Elemente umzukehren, werden wir jedes Element vom linken Stapel entfernen und auf den rechten Stapel verschieben. Sie können auf dem Bild unten sehen, was passiert, wenn wir dies tun.
Wir wissen also, wie man einen Stapel umkehrt.
Im vorherigen Teil habe ich erklärt, wie wir die Reihenfolge der Stapelelemente umkehren können. Dies war wichtig, da die Ausgabe genau in umgekehrter Reihenfolge einer Warteschlange erfolgt, wenn Elemente in den Stapel verschoben und eingefügt werden. Lassen Sie uns anhand eines Beispiels das Array von Ganzzahlen {1, 2, 3, 4, 5}
auf einen Stapel verschieben. Wenn wir die Elemente {5, 4, 3, 2, 1}
einfügen und drucken, bis der Stapel leer ist, erhalten wir das Array in der umgekehrten Reihenfolge der Push-Reihenfolge. Beachten Sie, dass bei derselben Eingabe die Ausgabe in die Warteschlange gestellt wird, bis die Warteschlange leer ist wird sein {1, 2, 3, 4, 5}
. Es ist also offensichtlich, dass bei derselben Eingabereihenfolge von Elementen die Ausgabe der Warteschlange genau umgekehrt ist wie die Ausgabe eines Stapels. Da wir wissen, wie man einen Stapel mit einem zusätzlichen Stapel umkehrt, können wir eine Warteschlange mit zwei Stapeln erstellen.
Unser Warteschlangenmodell besteht aus zwei Stapeln. Ein Stapel wird für die enqueue
Operation verwendet (Stapel Nr. 1 links wird als Eingabestapel bezeichnet), ein anderer Stapel wird für die dequeue
Operation verwendet (Stapel Nr. 2 rechts wird als Ausgabestapel bezeichnet). Schauen Sie sich das Bild unten an.
Unser Pseudocode ist wie folgt;
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
Lassen Sie uns die Ganzzahlen in die Warteschlange stellen {1, 2, 3}
. Ganzzahlen werden auf den Eingangsstapel ( Stapel Nr. 1 ) auf der linken Seite verschoben.
Was passiert dann, wenn wir eine Dequeue-Operation ausführen? Immer wenn eine Dequeue-Operation ausgeführt wird, prüft die Warteschlange, ob der Ausgabestapel leer ist oder nicht (siehe Pseudocode oben). Wenn der Ausgabestapel leer ist, wird der Eingabestapel in der Ausgabe extrahiert, damit die Elemente des Eingangsstapels wird umgekehrt. Vor der Rückgabe eines Werts lautet der Status der Warteschlange wie folgt:
Überprüfen Sie die Reihenfolge der Elemente im Ausgabestapel (Stapel 2). Es ist offensichtlich, dass wir die Elemente aus dem Ausgabestapel entfernen können, sodass die Ausgabe dieselbe ist, als ob wir uns aus einer Warteschlange entfernt hätten. Wenn wir also zwei Dequeue-Operationen ausführen, erhalten wir {1, 2}
jeweils zuerst. Dann ist Element 3 das einzige Element des Ausgabestapels, und der Eingabestapel ist leer. Wenn wir die Elemente 4 und 5 in die Warteschlange stellen, lautet der Status der Warteschlange wie folgt:
Jetzt ist der Ausgabestapel nicht leer, und wenn wir eine Dequeue-Operation ausführen, werden nur 3 aus dem Ausgabestapel entfernt. Dann wird der Zustand wie folgt gesehen;
Wenn wir zwei weitere Dequeue-Operationen ausführen, prüft die Warteschlange bei der ersten Dequeue-Operation erneut, ob der Ausgabestapel leer ist, was wahr ist. Ziehen Sie dann die Elemente des Eingabestapels heraus und verschieben Sie sie in den Ausgabestapel, bis der Eingabestapel leer ist. Der Status der Warteschlange lautet dann wie folgt.
Leicht zu sehen ist die Ausgabe der beiden Dequeue-Operationen {4, 5}
Hier ist eine Implementierung in Java. Ich werde die vorhandene Implementierung von Stack nicht verwenden, daher wird das Beispiel hier das Rad neu erfinden.
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
Sie können sogar eine Warteschlange mit nur einem Stapel simulieren. Der zweite (temporäre) Stapel kann durch den Aufrufstapel rekursiver Aufrufe der Einfügemethode simuliert werden.
Das Prinzip bleibt beim Einfügen eines neuen Elements in die Warteschlange gleich:
Eine Warteschlangenklasse, die nur einen Stapel verwendet, lautet wie folgt:
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
Verwendung der obigen Datenstruktur in die Warteschlange einzufügen . die Summe (1 + 2 + 4 + 8 + .... + 2(n-1))
ergibt ~O(n^2)
. Ich hoffe du verstehst, worum es geht.
Die zeitliche Komplexität wäre jedoch schlimmer. Eine gute Warteschlangenimplementierung erledigt alles in konstanter Zeit.
Bearbeiten
Ich bin mir nicht sicher, warum meine Antwort hier abgelehnt wurde. Wenn wir programmieren, ist uns die Zeitkomplexität wichtig, und die Verwendung von zwei Standardstapeln zum Erstellen einer Warteschlange ist ineffizient. Es ist ein sehr gültiger und relevanter Punkt. Wenn jemand anderes das Bedürfnis hat, dies weiter abzustimmen, würde mich interessieren, warum.
Ein bisschen mehr Details : Warum die Verwendung von zwei Stapeln schlechter ist als nur eine Warteschlange: Wenn Sie zwei Stapel verwenden und jemand die Warteschlange aufruft, während der Postausgang leer ist, benötigen Sie eine lineare Zeit, um zum Ende des Posteingangs zu gelangen (wie Sie sehen können) in Daves Code).
Sie können eine Warteschlange als einfach verknüpfte Liste implementieren (jedes Element zeigt auf das nächst eingefügte Element), wobei Sie einen zusätzlichen Zeiger auf das zuletzt eingefügte Element für Pushs behalten (oder es zu einer zyklischen Liste machen). Das Implementieren von Warteschlangen und Warteschlangen in dieser Datenstruktur ist in konstanter Zeit sehr einfach. Das ist im schlimmsten Fall eine konstante Zeit, die nicht abgeschrieben wird. Und da die Kommentare nach dieser Klarstellung zu fragen scheinen, ist die konstante Zeit im ungünstigsten Fall strikt besser als die amortisierte konstante Zeit.
Die zu implementierende Warteschlange sei q und die zur Implementierung von q verwendeten Stapel seien Stapel1 und Stapel2.
q kann auf zwei Arten implementiert werden:
Methode 1 (indem der enQueue-Betrieb teuer wird)
Diese Methode stellt sicher, dass sich das neu eingegebene Element immer oben in Stapel 1 befindet, sodass die deQueue-Operation nur aus Stapel 1 angezeigt wird. Um das Element oben auf Stapel1 zu platzieren, wird Stapel2 verwendet.
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.
Methode 2 (Durch kostspielige DeQueue-Operation)
Bei dieser Methode wird im En-Queue-Betrieb das neue Element oben in Stapel1 eingegeben. Wenn im De-Queue-Betrieb Stapel2 leer ist, werden alle Elemente nach Stapel2 verschoben und schließlich wird der Anfang von Stapel2 zurückgegeben.
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.
Methode 2 ist definitiv besser als Methode 1. Methode 1 verschiebt alle Elemente zweimal in der enQueue-Operation, während Methode 2 (in der deQueue-Operation) die Elemente einmal verschiebt und Elemente nur verschiebt, wenn stack2 leer ist.
Eine Lösung in 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();
}
}
Zwei Stapel in der Warteschlange sind als Stapel1 und Stapel2 definiert .
Enqueue: Die euqueued-Elemente werden immer in stack1 verschoben
Dequeue: Die Oberseite von stack2 kann herausspringen, da es das erste Element ist, das in die Warteschlange eingefügt wird, wenn stack2 nicht leer ist. Wenn stack2 leer ist, werden alle Elemente aus entfernt stack1 entfernt und einzeln in stack2 verschoben . Das erste Element in einer Warteschlange wird in den unteren Bereich von Stack1 verschoben . Es kann direkt nach dem Poppen und Drücken herausspringen, da es sich oben auf Stapel2 befindet .
Das Folgende ist der gleiche C ++ - Beispielcode:
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;
}
Diese Lösung ist ausgeliehen von meinem Blog . Eine detailliertere Analyse mit schrittweisen Betriebssimulationen finden Sie auf meiner Blog-Webseite.
Sie müssen alles vom ersten Stapel entfernen, um das untere Element zu erhalten. Legen Sie sie dann bei jeder "Dequeue" -Operation alle wieder auf den zweiten Stapel.
Für C # -Entwickler ist hier das komplette Programm:
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();
}
}
}
Implementieren Sie die folgenden Operationen einer Warteschlange mithilfe von Stapeln.
push (x) - Schieben Sie das Element x in den hinteren Bereich der Warteschlange.
pop () - Entfernt das Element vor der Warteschlange.
peek () - Holen Sie sich das vordere Element.
empty () - Gibt zurück, ob die Warteschlange leer ist.
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();
}
}
Eine Implementierung einer Warteschlange mit zwei Stapeln in 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()!)
}
}
}
}
Sie erhalten zwar viele Beiträge zum Implementieren einer Warteschlange mit zwei Stapeln: 1. Entweder indem Sie den enQueue-Prozess viel teurer machen. 2. Oder indem Sie den deQueue-Prozess viel teurer machen
https://www.geeksforgeeks.org/queue-using-stacks/
Ein wichtiger Weg, den ich aus dem obigen Beitrag herausgefunden habe, war das Erstellen einer Warteschlange mit nur der Stapeldatenstruktur und dem Rekursionsaufrufstapel.
Man kann zwar argumentieren, dass buchstäblich immer noch zwei Stapel verwendet werden, aber im Idealfall wird nur eine Stapeldatenstruktur verwendet.
Nachfolgend finden Sie die Erklärung des Problems:
Deklarieren Sie einen einzelnen Stapel zum Aktivieren und Deaktivieren der Daten und verschieben Sie die Daten in den Stapel.
Während deQueueing eine Grundbedingung hat, bei der das Element des Stapels eingeblendet wird, wenn die Größe des Stapels 1 beträgt. Dadurch wird sichergestellt, dass während der deQueue-Rekursion kein Stapelüberlauf auftritt.
Während des DeQueueing werden zuerst die Daten vom oberen Rand des Stapels eingeblendet. Idealerweise ist dieses Element das Element, das sich oben auf dem Stapel befindet. Rufen Sie nun rekursiv die Funktion deQueue auf und schieben Sie das oben platzierte Element zurück in den Stapel.
Der Code sieht wie folgt aus:
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;
Auf diese Weise können Sie eine Warteschlange mithilfe einer einzelnen Stack-Datenstruktur und des Rekursionsaufrufstapels erstellen.
Unten finden Sie die Lösung in Javascript-Sprache mit ES6-Syntax.
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 };
Unten ist die Verwendung:
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
. Wenn Sie erneut zu gehen dequeue
, verschieben Sie sie in Elemente stack2
und stellen sie vor das, was bereits vorhanden war.
Ich werde diese Frage in Go beantworten, da Go nicht viele Sammlungen in seiner Standardbibliothek hat.
Da ein Stack sehr einfach zu implementieren ist, dachte ich, ich würde versuchen, zwei Stacks zu verwenden, um eine Warteschlange mit zwei Enden zu erstellen. Um besser zu verstehen, wie ich zu meiner Antwort gekommen bin, habe ich die Implementierung in zwei Teile geteilt. Der erste Teil ist hoffentlich leichter zu verstehen, aber unvollständig.
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:]
}
}
Es sind im Grunde zwei Stapel, bei denen wir zulassen, dass der Boden der Stapel voneinander manipuliert wird. Ich habe auch die STL-Namenskonventionen verwendet, bei denen die traditionellen Push-, Pop- und Peek-Operationen eines Stapels ein Front / Back-Präfix haben, unabhängig davon, ob sie sich auf die Vorder- oder Rückseite der Warteschlange beziehen.
Das Problem mit dem obigen Code ist, dass er den Speicher nicht sehr effizient nutzt. Tatsächlich wächst es endlos, bis Ihnen der Platz ausgeht. Das ist wirklich schlimm. Die Lösung hierfür besteht darin, den unteren Rand des Stapelbereichs nach Möglichkeit einfach wiederzuverwenden. Wir müssen einen Versatz einführen, um dies zu verfolgen, da ein Slice in Go nach dem Schrumpfen nicht mehr vorne wachsen kann.
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.")
}
}
}
Es sind viele kleine Funktionen, aber von den 6 Funktionen sind 3 nur Spiegel der anderen.
Hier ist meine Lösung in Java mit Linkedlist.
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;
}
}
}}
Hinweis: In diesem Fall ist der Pop-Vorgang sehr zeitaufwändig. Daher werde ich nicht vorschlagen, eine Warteschlange mit zwei Stapeln zu erstellen.
Mit O(1)
dequeue()
, was der Antwort von Pythonquick entspricht :
// 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()
Mit O(1)
enqueue()
(dies wird in diesem Beitrag nicht erwähnt, also diese Antwort), das auch Backtracking verwendet, um das unterste Element in die Luft zu sprudeln und zurückzugeben.
// 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
Offensichtlich ist es eine gute Codierungsübung, da sie ineffizient, aber dennoch elegant ist.
** Einfache JS-Lösung **
/*
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();
}
}
Für jede Enqueue-Operation fügen wir oben im Stapel1 hinzu. Für jede Warteschlange leeren wir den Inhalt von Stapel1 in Stapel2 und entfernen das Element oben im Stapel. Die Zeitkomplexität beträgt O (n) für die Warteschlange, da wir den Stapel1 in Stapel2 kopieren müssen. Die zeitliche Komplexität der Warteschlange entspricht der eines regulären Stapels
if (stack2 != null)
ist immer wahr, weil er stack2
im Konstruktor instanziiert wird.
Warteschlangenimplementierung mit zwei java.util.Stack-Objekten:
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()
und return inbox.size() + outbox.size()
, respectively. Der Code von Dave L. löst bereits eine Ausnahme aus, wenn Sie sich aus einer leeren Warteschlange entfernen. Die ursprüngliche Frage betraf nicht einmal Java. Es ging um Datenstrukturen / Algorithmen im Allgemeinen. Die Java-Implementierung war nur eine zusätzliche Illustration.