日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

数据结构与算法(2)——栈和队列

發布時間:2025/3/21 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据结构与算法(2)——栈和队列 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言:題圖無關,只是好看,接下來就來復習一下棧和隊列的相關知識

前序文章:

  • 數據結構與算法(1)——數組與鏈表(https://www.jianshu.com/p/7b93b3570875)

什么是棧

棧是一種用于存儲數據的簡單數據結構(與鏈表類似)。數據入棧的次序是棧的關鍵。可以把一桶桶裝的薯片看作是一個棧的例子,當薯片做好之后,它們會依次被添加到桶里,每一片都會是當前的最上面一片,而每次我們取的時候也是取的最上面的那一片,規定你不能破壞桶也不能把底部捅穿,所以第一個放入桶的薯片只能最后一個從桶里取出;

定義:棧(Stack)是一個有序線性表,只能在表的一端(稱為棧頂,top)執行插入和刪除操作。最后插入的元素將第一個被刪除,所以棧也稱為后進先出(Last In First Out,LIFO)或先進后出(First In Last Out)線性表;

兩個改變棧的操作都有專用名稱。一個稱為入棧(push),表示在棧中插入一個元素;另一個稱為出棧(pop),表示從棧中刪除一個元素。試圖對一個空棧執行棧操作稱為下溢(underflow);試圖對一個滿棧執行棧操作稱為溢出(overflow)。通常,溢出和下溢均認為是異常;

棧的應用

  • 無處不在的Undo操作(撤銷);
  • 程序調用的系統棧;
  • 括號/符號匹配;
  • 等等等等....

棧抽象數據類型

下面給出棧抽象數據類型中的操作,為了簡單起見,假設數據類型為整型;

棧的主要操作

  • 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<>();}@Overridepublic int getSize() {return array.getSize();}@Overridepublic boolean isEmepty() {return array.isEmpty();}public int getCapacity() {return array.getCapacity();}@Overridepublic void push(E e) {array.addLast(e);}@Overridepublic E pop() {return array.removeLast();}@Overridepublic E top() {return array.getLast();}@Overridepublic 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()操作可能涉及到底層數組的擴容或縮容的操作,所以是均攤下來的復雜度;


隊列

什么是隊列

隊列是一種用于存儲數據的數據結構(與鏈表和棧類似),數據到達的次序是隊列的關鍵;在日常生活中隊列是指從序列的開始按照順序等待服務的一隊人或物;

定義:隊列是一種只能在一端插入(隊尾),在另一端刪除(隊首)的有序線性表。隊列中第一個插入的元素也是第一個被刪除的元素,所以隊列是一種先進先出(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<>();}@Overridepublic int getSize(){return array.getSize();}@Overridepublic boolean isEmpty(){return array.isEmpty();}public int getCapacity(){return array.getCapacity();}@Overridepublic void enqueue(E e){array.addLast(e);}@Overridepublic E dequeue(){return array.removeFirst();}@Overridepublic E getFront(){return array.getFirst();}@Overridepublic 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;}@Overridepublic boolean isEmpty(){return front == tail;}@Overridepublic int getSize(){return size;}@Overridepublic void enqueue(E e){if((tail + 1) % data.length == front)resize(getCapacity() * 2);data[tail] = e;tail = (tail + 1) % data.length;size ++;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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慢主要是因為出棧時每次都需要把整個結構往前挪一下


LeetCode 相關題目整理

20.有效的括號

我的答案:(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;}}}// 最后棧為空才能返回truereturn 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(); }

155. 最小棧(劍指Offer面試題30)

參考答案(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();} }

225. 用隊列實現棧

我的答案:(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();} }

確實寫得簡潔啊,這樣一來我就使用一個隊列和兩個隊列都掌握啦,開心~

232.用棧實現隊列(劍指Offer面試題9)

參考答案:(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();} }

其他題目整理

劍指Offer面試題31:棧的壓入、彈出序列

題目:輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否為該棧的彈出順序。假設壓入棧的所有數字均不相等。例如,序列{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 、2、3 、4、5 是某棧壓棧序列,* 序列4、5、3、2、1是該壓棧序列對應的一個彈出序列,* 但4、3、5、1、2就不可能是該壓棋序列的彈出序列。* 【與書本的的方法不同】** @param push 入棧序列* @param pop 出棧序列* @return true:出棧序列是入棧序列的一個彈出順序*/public static boolean isPopOrder(int[] push, int[] pop) {// 輸入校驗,參數不能為空,并且兩個數組中必須有數字,并且兩個數組中的數字個數相同// 否則返回falseif (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++;}// 如果沒有找到與出棧元素相等的元素,說明這個出棧順序是不合法的// 就返回falseelse {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) {// 用于記錄判斷出棧順序是不是入棧順的一個出棧序列,默認falseboolean 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));// 測試方法2System.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

總結

以上是生活随笔為你收集整理的数据结构与算法(2)——栈和队列的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。