數據結構與算法(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<>();
}
@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()
操作可能涉及到底層數組的擴容或縮容的操作,所以是均攤下來的復雜度;
隊列
什麽是隊列
隊列是一種用於存儲數據的數據結構(與鏈表和棧類似),數據到達的次序是隊列的關鍵;在日常生活中隊列是指從序列的開始按照順序等待服務的一隊人或物;
定義:隊列是一種只能在一端插入(隊尾),在另一端刪除(隊首)的有序線性表。隊列中第一個插入的元素也是第一個被刪除的元素,所以隊列是一種先進先出(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慢主要是因為出棧時每次都需要把整個結構往前挪一下
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;
}
}
}
// 最後棧為空才能返回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();
}
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) {
// 輸入校驗,參數不能為空,並且兩個數組中必須有數字,並且兩個數組中的數字個數相同
// 否則返回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學習資料
數據結構與算法(2)——棧和隊列