前言:題圖無關,只是好看,接下來就來複習一下棧和隊列的相關知識java
前序文章:git
棧是一種用於存儲數據的簡單數據結構(與鏈表相似)。數據入棧的次序是棧的關鍵。能夠把一桶桶裝的薯片看做是一個棧的例子,當薯片作好以後,它們會依次被添加到桶裏,每一片都會是當前的最上面一片,而每次咱們取的時候也是取的最上面的那一片,規定你不能破壞桶也不能把底部捅穿,因此第一個放入桶的薯片只能最後一個從桶裏取出;github
定義:棧(Stack)是一個有序線性表,只能在表的一端(稱爲棧頂,top)執行插入和刪除操做。最後插入的元素將第一個被刪除,因此棧也稱爲後進先出(Last In First Out,LIFO)或先進後出(First In Last Out)線性表;web
兩個改變棧的操做都有專用名稱。一個稱爲入棧(push),表示在棧中插入一個元素;另外一個稱爲出棧(pop),表示從棧中刪除一個元素。試圖對一個空棧執行棧操做稱爲下溢(underflow);試圖對一個滿棧執行棧操做稱爲溢出(overflow)。一般,溢出和下溢均認爲是異常;面試
下面給出棧抽象數據類型中的操做,爲了簡單起見,假設數據類型爲整型;算法
void push(int data)
:將data(數據)插入棧;int pop()
:刪除並返回最後一個插入棧的元素;int top()
:返回最後一個插入棧的元素,但不刪除;int size()
:返回存儲在棧中元素的個數;int isEmpty()
:判斷棧中是否有元素;int isStackFull()
:判斷棧中是否存滿元素;咱們結合以前建立的Array類,咱們可以很好的建立屬於咱們本身的動態數組實現的棧結構,對於用戶來講,咱們只須要完成咱們的相關操做,而且知道我可以不斷地往裏添加元素而不出錯就好了,因此咱們先來定義一個通用的棧接口:數組
public interface Stack<E> { int getSize(); boolean isEmepty(); void push(E e); E pop(); E top(); }
而後咱們往以前的動態數組中添加兩個用戶友好的方法:微信
public E getLast() { return get(size - 1); } public E getFirst() { return get(0); }
而後實現本身的動態數組爲底層的棧結構就輕鬆多了:數據結構
public class ArrayStack<E> implements Stack<E> { Array<E> array; public ArrayStack(int capacity) { array = new Array<>(capacity); } public ArrayStack() { array = new Array<>(); } @Override public int getSize() { return array.getSize(); } @Override public boolean isEmepty() { return array.isEmpty(); } public int getCapacity() { return array.getCapacity(); } @Override public void push(E e) { array.addLast(e); } @Override public E pop() { return array.removeLast(); } @Override public E top() { return array.getLast(); } @Override public String toString() { StringBuilder res = new StringBuilder(); res.append("Stack:"); res.append("["); for (int i = 0; i < array.getSize(); i++) { res.append(array.get(i)); if (i != array.getSize() - 1) { res.append(","); } } res.append("]"); return res.toString(); } }
從代碼中能夠看出,幾乎全部的時間複雜度都爲O(1)級別,比較特別的是push()
和pop()
操做可能涉及到底層數組的擴容或縮容的操做,因此是均攤下來的複雜度;app
隊列是一種用於存儲數據的數據結構(與鏈表和棧相似),數據到達的次序是隊列的關鍵;在平常生活中隊列是指從序列的開始按照順序等待服務的一隊人或物;
定義:隊列是一種只能在一端插入(隊尾),在另外一端刪除(隊首)的有序線性表。隊列中第一個插入的元素也是第一個被刪除的元素,因此隊列是一種先進先出(FIFO,First In First Out)或後進後出(LiLO,Last In Last Out)線性表;
與棧相似,兩個改變隊列的操做各有專用名稱;在隊列中插入一個元素,稱爲入隊(EnQueue),從隊列中刪除一個元素,稱爲出隊(DeQueue);試圖對一個空隊列執行出隊操做稱爲下溢(underflow),試圖對一個滿隊列執行入隊操做稱爲溢出(overflow);一般認爲溢出和下溢是異常。
咱們仍然定義一個Queue接口來講明咱們隊列中經常使用的一些方法:
public interface Queue<E> { int getSize(); boolean isEmpty(); void enqueue(E e); E dequeue(); E getFront(); }
藉由咱們以前本身實現的動態數組,那麼咱們的隊列就很簡單了:
public class ArrayQueue<E> implements Queue<E> { private Array<E> array; public ArrayQueue(int capacity){ array = new Array<>(capacity); } public ArrayQueue(){ array = new Array<>(); } @Override public int getSize(){ return array.getSize(); } @Override public boolean isEmpty(){ return array.isEmpty(); } public int getCapacity(){ return array.getCapacity(); } @Override public void enqueue(E e){ array.addLast(e); } @Override public E dequeue(){ return array.removeFirst(); } @Override public E getFront(){ return array.getFirst(); } @Override public String toString(){ StringBuilder res = new StringBuilder(); res.append("Queue: "); res.append("front ["); for(int i = 0 ; i < array.getSize() ; i ++){ res.append(array.get(i)); if(i != array.getSize() - 1) res.append(", "); } res.append("] tail"); return res.toString(); } }
void enquque(E)
:O(1)(均攤)E dequeue()
:O(n)E front()
:O(1)int getSize()
:O(1)boolean isEmpty()
:O(1)循環隊列的實現其實就是維護了一個front和一個tail分別指向頭和尾,而後須要特別注意的呢是斷定隊滿和隊空的條件:
front == tail
,這沒啥好說的;tail + 1 == front
,這裏實際上是有意浪費了一個空間,否則就斷定不了究竟是隊空仍是隊滿了,由於條件都同樣...public class LoopQueue<E> implements Queue<E> { private E[] data; private int front, tail; private int size; public LoopQueue(int capacity){ data = (E[])new Object[capacity + 1]; front = 0; tail = 0; size = 0; } public LoopQueue(){ this(10); } public int getCapacity(){ return data.length - 1; } @Override public boolean isEmpty(){ return front == tail; } @Override public int getSize(){ return size; } @Override public void enqueue(E e){ if((tail + 1) % data.length == front) resize(getCapacity() * 2); data[tail] = e; tail = (tail + 1) % data.length; size ++; } @Override public E dequeue(){ if(isEmpty()) throw new IllegalArgumentException("Cannot dequeue from an empty queue."); E ret = data[front]; data[front] = null; front = (front + 1) % data.length; size --; if(size == getCapacity() / 4 && getCapacity() / 2 != 0) resize(getCapacity() / 2); return ret; } @Override public E getFront(){ if(isEmpty()) throw new IllegalArgumentException("Queue is empty."); return data[front]; } private void resize(int newCapacity){ E[] newData = (E[])new Object[newCapacity + 1]; for(int i = 0 ; i < size ; i ++) newData[i] = data[(i + front) % data.length]; data = newData; front = 0; tail = size; } @Override public String toString(){ StringBuilder res = new StringBuilder(); res.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity())); res.append("front ["); for(int i = front ; i != tail ; i = (i + 1) % data.length){ res.append(data[i]); if((i + 1) % data.length != tail) res.append(", "); } res.append("] tail"); return res.toString(); } }
void enquque(E)
:O(1)(均攤)E dequeue()
:O(1)(均攤)E front()
:O(1)int getSize()
:O(1)boolean isEmpty()
:O(1)咱們來簡單對比一下兩個隊列的性能吧,這裏直接上代碼:
// 測試使用q運行opCount個enqueueu和dequeue操做所須要的時間,單位:秒 private static double testQueue(Queue<Integer> q, int opCount){ long startTime = System.nanoTime(); Random random = new Random(); for(int i = 0 ; i < opCount ; i ++) q.enqueue(random.nextInt(Integer.MAX_VALUE)); for(int i = 0 ; i < opCount ; i ++) q.dequeue(); long endTime = System.nanoTime(); return (endTime - startTime) / 1000000000.0; } public static void main(String[] args) { int opCount = 100000; ArrayQueue<Integer> arrayQueue = new ArrayQueue<>(); double time1 = testQueue(arrayQueue, opCount); System.out.println("ArrayQueue, time: " + time1 + " s"); LoopQueue<Integer> loopQueue = new LoopQueue<>(); double time2 = testQueue(loopQueue, opCount); System.out.println("LoopQueue, time: " + time2 + " s"); }
我這裏的測試結果是這樣的,你們也就可見一斑啦:
其實ArrayQueue慢主要是由於出棧時每次都須要把整個結構往前挪一下
個人答案:(10ms)
public boolean isValid(String s) { // 正確性判斷 if (null == s || s.length() == 1) { return false; } Stack<Character> stack = new Stack<>(); // 遍歷輸入的字符 for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); // 若是爲左括號則push進棧 if (c == '(' || c == '[' || c == '{') { stack.push(c); } else { if (stack.isEmpty()) { return false; } char topChar = stack.pop(); if (c == ')' && topChar != '(') { return false; } if (c == ']' && topChar != '[') { return false; } if (c == '}' && topChar != '{') { return false; } } } // 最後棧爲空才能返回true return stack.isEmpty(); }
參考答案:(8ms)
public boolean isValid(String s) { // 正確性判斷 if (0 == s.length()) { return true; } if (s.length() % 2 == 1) { return false; } Stack<Character> stack = new Stack(); char[] cs = s.toCharArray(); for (int i = 0; i < cs.length; i++) { if (cs[i] == '(' || cs[i] == '[' || cs[i] == '{') { stack.push(cs[i]); } else { if (stack.isEmpty()) { return false; } char c = stack.pop(); if ((cs[i] == ')' && c == '(') || (cs[i] == '}' && c == '{') || (cs[i] == ']' && c == '[')) { } else { return false; } } } return stack.isEmpty(); }
參考答案(107ms)
class MinStack { // 數據棧,用於存放插入的數據 private Stack<Integer> dataStack; // 最小數位置棧,存放數據棧中最小的數的位置 private Stack<Integer> minStack; /** * initialize your data structure here. */ public MinStack() { this.dataStack = new Stack<>(); this.minStack = new Stack<>(); } /** * 元素入棧 * * @param x 入棧的元素 */ public void push(int x) { dataStack.push(x); // 若是最小棧是空的,只要將元素入棧 if (minStack.isEmpty()) { minStack.push(x); } // 若是最小棧中有數據 else { minStack.push(Math.min(x, minStack.peek())); } } /** * 出棧方法 */ public void pop() { // 若是棧已經爲空,則返回(LeetCode不能拋異常...) if (dataStack.isEmpty()) { return; } // 若是有數據,最小數位置棧和數據棧一定是有相同的元素個數, // 兩個棧同時出棧 minStack.pop(); dataStack.pop(); } /** * 返回棧頂元素 * * @return 棧頂元素 */ public int top() { return dataStack.peek(); } /** * 獲取棧中的最小元素 * * @return 棧中的最小元素 */ public int getMin() { // 若是最小數公位置棧已經爲空(數據棧中已經沒有數據了),則拋出異常 if (minStack.isEmpty()) { return 0; } // 獲取數據佔中的最小元素,而且返回結果 return minStack.peek(); } }
改進答案:
上面求解方法的主要問題在於,每次push操做時,minStack也執行了一次push操做(新元素或當前的最小元素),也就是說,重複執行了最小值的入棧操做,因此如今咱們來修改算法下降空間複雜度。仍然須要設置一個minStack,可是隻有當從dataStack中出棧的元素等於minStack棧頂元素時,纔對minStack執行出棧的操做;也只有當dataStack入棧的元素小於或等於當前最小值時,纔對minStack執行入棧操做,下面就簡單寫一下了主要看一下出棧和入棧實現的邏輯就行了:
class MinStack { private Stack<Integer> dataStack; private Stack<Integer> minStack; public MinStack() { this.dataStack = new Stack<>(); this.minStack = new Stack<>(); } public void push(int x) { dataStack.push(x); if (minStack.isEmpty() || minStack.peek() >= (Integer) x) { minStack.push(x); } } public void pop() { if (dataStack.isEmpty()) { return; } Integer minTop = minStack.peek(); Integer dataTop = dataStack.peek(); if (minTop.intValue() == dataTop.intValue()) { minStack.pop(); } dataStack.pop(); } public int top() { return dataStack.peek(); } public int getMin() { return minStack.peek(); } }
個人答案:(118ms)
class MyStack { private Queue<Integer> queue1; private Queue<Integer> queue2; /** * Initialize your data structure here. */ public MyStack() { queue1 = new LinkedList<>(); queue2 = new LinkedList<>(); } /** * Push element x onto stack. */ public void push(int x) { if (queue1.isEmpty()) { queue2.offer(x); } else { queue1.offer(x); } } /** * Removes the element on top of the stack and returns that element. */ public int pop() { int size; if (!queue1.isEmpty()) { size = queue1.size(); for (int i = 0; i < size - 1; i++) { queue2.offer(queue1.poll()); } return queue1.poll(); } else { size = queue2.size(); for (int i = 0; i < size - 1; i++) { queue1.offer(queue2.poll()); } return queue2.poll(); } } /** * Get the top element. */ public int top() { int size; if (!queue1.isEmpty()) { size = queue1.size(); for (int i = 0; i < size - 1; i++) { queue2.offer(queue1.poll()); } int result = queue1.peek(); queue2.offer(queue1.poll()); return result; } else { size = queue2.size(); for (int i = 0; i < size - 1; i++) { queue1.offer(queue2.poll()); } int result = queue2.peek(); queue1.offer(queue2.poll()); return result; } } /** * Returns whether the stack is empty. */ public boolean empty() { return queue1.isEmpty() && queue2.isEmpty(); } }
參考答案:(121ms)
class MyStack { Queue<Integer> q; /** * Initialize your data structure here. */ public MyStack() { this.q = new LinkedList<Integer>(); } /** * Push element x onto stack. */ public void push(int x) { q.add(x); } /** * Removes the element on top of the stack and returns that element. */ public int pop() { int size = q.size(); for (int i = 0; i < size - 1; i++) { q.add(q.remove()); } return q.remove(); } /** * Get the top element. */ public int top() { int size = q.size(); for (int i = 0; i < size - 1; i++) { q.add(q.remove()); } int ret = q.remove(); q.add(ret); return ret; } /** * Returns whether the stack is empty. */ public boolean empty() { return q.isEmpty(); } }
確實寫得簡潔啊,這樣一來我就使用一個隊列和兩個隊列都掌握啦,開心~
參考答案:(72ms)
class MyQueue { Stack<Integer> pushstack; Stack<Integer> popstack; /** * Initialize your data structure here. */ public MyQueue() { this.pushstack = new Stack(); this.popstack = new Stack(); } /** * Push element x to the back of queue. */ public void push(int x) { pushstack.push(x); } /** * Removes the element from in front of queue and returns that element. */ public int pop() { if (popstack.isEmpty()) { while (!pushstack.isEmpty()) { popstack.push(pushstack.pop()); } } return popstack.pop(); } /** * Get the front element. */ public int peek() { if (popstack.isEmpty()) { while (!pushstack.isEmpty()) { popstack.push(pushstack.pop()); } } return popstack.peek(); } /** * Returns whether the queue is empty. */ public boolean empty() { return pushstack.isEmpty() && popstack.isEmpty(); } }
題目:輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否爲該棧的彈出順序。假設壓入棧的全部數字均不相等。例如,序列{1,2,3,4,5}是某棧的壓棧序列,序列{4,5,3,2,1}是該壓棧序列對應的一個彈出序列,但{4,3,5,1,2}就不多是該壓棧序列的彈出序列。
參考答案:(原文連接:https://blog.csdn.net/derrantcm/article/details/46691083)
public class Test22 { /** * 輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷二個序列是否爲該棧的彈出順序。 * 假設壓入棧的全部數字均不相等。例如序列1 、二、3 、四、5 是某棧壓棧序列, * 序列四、五、三、二、1是該壓棧序列對應的一個彈出序列, * 但四、三、五、一、2就不多是該壓棋序列的彈出序列。 * 【與書本的的方法不一樣】 * * @param push 入棧序列 * @param pop 出棧序列 * @return true:出棧序列是入棧序列的一個彈出順序 */ public static boolean isPopOrder(int[] push, int[] pop) { // 輸入校驗,參數不能爲空,而且兩個數組中必須有數字,而且兩個數組中的數字個數相同 // 不然返回false if (push == null || pop == null || pop.length == 0 || push.length == 0 || push.length != pop.length) { return false; } // 通過上面的參數校驗,兩個數組中必定有數據,且數據數目相等 // 用於存放入棧時的數據 Stack<Integer> stack = new Stack<>(); // 用於記錄入棧數組元素的處理位置 int pushIndex = 0; // 用於記錄出棧數組元素的處理位置 int popIndex = 0; // 若是還有出棧元素要處理 while (popIndex < pop.length) { // 入棧元素還未所有入棧的條件下,若是棧爲空,或者棧頂的元素不與當前處理的相等,則一直進行棧操做, // 直到入棧元素所有入棧或者找到了一個與當出棧元素相等的元素 while (pushIndex < push.length && (stack.isEmpty() || stack.peek() != pop[popIndex])) { // 入棧數組中的元素入棧 stack.push(push[pushIndex]); // 指向下一個要處理的入棧元素 pushIndex++; } // 若是在上一步的入棧過程當中找到了與出棧的元素相等的元素 if (stack.peek() == pop[popIndex]) { // 將元素出棧 stack.pop(); // 處理下一個出棧元素 popIndex++; } // 若是沒有找到與出棧元素相等的元素,說明這個出棧順序是不合法的 // 就返回false else { return false; } } // 下面的語句老是成立的 // return stack.isEmpty(); // 爲何能夠直接返回true:對上面的外層while進行分析可知道,對每個入棧的元素, // 在stack棧中,經過一些入棧操做,總能夠在棧頂上找到與入棧元素值相同的元素, // 這就說明了這個出棧的順序是入棧順序的一個彈出隊列,這也能夠解釋爲何stack.isEmpty() // 老是返回true,全部的入棧元素均可以進棧,而且能夠被匹配到,以後就彈出,最後棧中就無元素。 return true; } /** * 輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷二個序列是否爲該棧的彈出順序。 * 【按書本上的思路進行求解,二者相差不大】 * * @param push 入棧序列 * @param pop 出棧序列 * @return true:出棧序列是入棧序列的一個彈出順序 */ public static boolean isPopOrder2(int[] push, int[] pop) { // 用於記錄判斷出棧順序是否是入棧順的一個出棧序列,默認false boolean isPossible = false; // 當入棧和出棧數組者都不爲空,而且都有數據,而且數據個數都相等 if (push != null && pop != null && push.length > 0 && push.length == pop.length) { // 用於存放入棧時的數據 Stack<Integer> stack = new Stack<>(); // 記錄下一個要處理的入棧元素的位置 int nextPush = 0; // 記錄下一個要處理的出棧元素的位置 int nextPop = 0; // 若是出棧元素沒有處理完就繼續進行處理 while (nextPop < pop.length) { // 若是棧爲空或者棧頂的元素與當前處理的出棧元素不相同,一直進行操做 while (stack.isEmpty() || stack.peek() != pop[nextPop]) { // 若是入棧的元素已經所有入棧了,就退出內層循環 if (nextPush >= push.length) { break; } // 執行到此處說明還有入棧元素能夠入棧 // 即將元素入棧 stack.push(push[nextPush]); // 指向下一個要處理的入棧元素的位置 nextPush++; } // 執行到此處有兩種狀況: // 第一種:在棧頂上找到了一個與入棧元素相等的元素 // 第二種:在棧頂上沒有找到一個與入棧元素相等的元素,並且輸入棧的元素已經所有入棧了 // 對於第二種狀況就說彈出棧的順序是不符合要求的,退出外層循環 if (stack.peek() != pop[nextPop]) { break; } // 對應到第一種狀況:須要要棧的棧頂元素彈出 stack.pop(); // 指向下一個要處理的出棧元素的位置 nextPop++; } // 執行到此處有兩種狀況 // 第一種:外層while循環的在第一種狀況下退出, // 第二種:全部的出棧元素都被正確匹配 // 對於出現的第一種狀況其stack.isEmpty()必不爲空,緣由爲分析以下: // 全部的入棧元素必定會入棧,可是隻有匹配的狀況下才會出棧, // 匹配的次數最多與入棧元素個數元素相同(兩個數組的長度相等),若是有不匹配的元素, // 必然會使出棧的次數比入棧的次數少,這樣棧中至少會有一個元素 // 對於第二種狀況其stack.isEmpty()必定爲空 // 因此書本上的nextPop == pop.length(pNextPop-pPop==nLength)是多餘的 if (stack.isEmpty()) { isPossible = true; } } return isPossible; } public static void main(String[] args) { int[] push = {1, 2, 3, 4, 5}; int[] pop1 = {4, 5, 3, 2, 1}; int[] pop2 = {3, 5, 4, 2, 1}; int[] pop3 = {4, 3, 5, 1, 2}; int[] pop4 = {3, 5, 4, 1, 2}; System.out.println("true: " + isPopOrder(push, pop1)); System.out.println("true: " + isPopOrder(push, pop2)); System.out.println("false: " + isPopOrder(push, pop3)); System.out.println("false: " + isPopOrder(push, pop4)); int[] push5 = {1}; int[] pop5 = {2}; System.out.println("false: " + isPopOrder(push5, pop5)); int[] push6 = {1}; int[] pop6 = {1}; System.out.println("true: " + isPopOrder(push6, pop6)); System.out.println("false: " + isPopOrder(null, null)); // 測試方法2 System.out.println(); System.out.println("true: " + isPopOrder2(push, pop1)); System.out.println("true: " + isPopOrder2(push, pop2)); System.out.println("false: " + isPopOrder2(push, pop3)); System.out.println("false: " + isPopOrder2(push, pop4)); System.out.println("false: " + isPopOrder2(push5, pop5)); System.out.println("true: " + isPopOrder2(push6, pop6)); System.out.println("false: " + isPopOrder2(null, null)); } }
棧和隊列的應用遠不止上面學習到的那些,實現方式也有不少種,如今也只是暫時學到這裏,經過刷LeetCode也加深了我對於這兩種數據結構的認識,不過本身還須要去熟悉瞭解一下計算機系統關於棧的應用這方面的知識,由於棧這種結構自己就很適合用來保存CPU現場之類的工做,仍是抓緊時間吧,過兩天還考試,這兩天就先複習啦...
歡迎轉載,轉載請註明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz 歡迎關注公衆微信號:wmyskxz_javaweb 分享本身的Java Web學習之路以及各類Java學習資料