什么是棧(Stack)
棧是一種遵循特定操作順序的線性數據結構,
遵循的順序是先進后出(FILO:First In Last Out)或者后進先出(LIFO:Last In First Out)。
比如:
生活中,廚房里的一摞盤子,你使用的時候會拿最上面的一個,最下面的那個最后使用。這就是FILO。當你想用第二個盤子時,先要拿起第一個,再拿出第二個,然后把第一個放到最上面。
棧的示意圖大致如下:

實現和操作概述
棧的主要操作有以下5種
Object
push(Object element) : 向棧頂添加一個元素
Object
pop() : 移除棧頂的元素並返回
Object
peek() : 返回棧頂元素,但不刪除
boolean
empty() : 判斷棧是否為空,即棧頂是否為空
int
search(Object element) :查找element是否存在,存在則返回位置(棧頂為1),不存在則返回-1。
棧的實現
可以通過數組或鏈表都可以實現棧,下面都會詳細說明。
鏈表實現是自己寫的一個demo。
數組實現以java.util.Stack代碼做說明。java中的java.util.Stack類實現是通過數組的。
最后舉了一個常見的使用棧實現功能的例子:
符號匹配的問題
疑問
對比數組和鏈表的實現后,個人覺得用鏈表實現的更好,但JDK種使用的是數組實現。
這個
需要進一步了解,或者比較各個操作在運行時間及內存上的變化來分析。//TODO
鏈表實現在鏈表首部操作,數組實現在有效數組(數組中有效元素的部分,非數組最大容量)的末尾操作,pop、peek、empty、search操作感覺類似(時間、內存消耗)。
下面做了個簡單表格:
鏈表實現的劣勢:需額外信息存儲引用,內存浪費;push時需要新建節點。
數組實現的劣勢:數組大小不足時,在push時需要擴容。擴容即需要重新分配更大空間並復制數據。
|
數組
|
鏈表
|
內存浪費
|
無浪費
|
有浪費:需存如額外引用信息 |
動態
|
非動態:大小無法運行時隨意變動
|
動態的:可以隨意增加或縮小
|
push操作
|
當數組大小超過時,需要擴容O(n)。
數組大小足夠時,直接push完成 O(1)
|
直接鏈表首部插入O(1). 但需新建節點
|
單鏈表實現
下面是簡易的demo。
通過單鏈表實現棧很容易實現,push()和pop()直接通過對鏈表首部的操作即可,時間復雜度都是O(1)。
下面demo很容理解,可以看看注釋。需要了解
鏈表。
public class StackTest<E> { public static void main(String[] args) { StackTest<Integer> stackTest = new StackTest<>(); for (int i = 4; i > 0; i--) { System.out.println("push:" + stackTest.push(Integer.valueOf(i)).intValue()); } System.out.println("peek:" + stackTest.peek()); System.out.println("pop:" + stackTest.pop()); System.out.println("isEmpty:" + stackTest.isEmpty()); for (int i = 4; i > 0; i--) { System.out.println("search " + i + ":" + stackTest.search(Integer.valueOf(i))); } } //棧頂定義 StackNode<E> top; //節點定義: static class StackNode<E> { E data; StackNode<E> next; StackNode(E data, StackNode<E> next) { this.data = data; this.next = next; } } //向棧頂push一個元素,即向鏈表首部添加元素 public E push(E data) { top = new StackNode<E>(data, top); return top.data; } //返回棧頂的值。即鏈表首部節點的值。 public E peek() { if (isEmpty()) throw new RuntimeException("fail,stack is null!"); return top.data; } //從棧頂pop一個元素,即返回棧頂的值 並刪除鏈表第一個節點。 public E pop() { E preTopData = peek(); top = top.next; return preTopData; } //判空 public boolean isEmpty() { return top == null; } //查找數據為data的節點位置,棧頂為1.沒找到返回-1. public int search(E data) { int position = 1; StackNode<E> currNode = top; while (currNode != null && !currNode.data.equals(data)) { position++; currNode = currNode.next; } if (currNode == null) position=-1; return position; } }
push()和pop()操作后,效果為:

打印log為:
push:4 push:3 push:2 push:1 peek:1 pop:1 isEmpty:false search 4:3 search 3:2 search 2:1 search 1:-1
棧的數組實現
如上所說,棧的數組實現方式以java.util.Stack類的代碼作為說明。
Stack類是繼承了Vector類,兩個類方法都很多,截取了相關的代碼。
主要操作過程:定義一個數組(elementData)存放棧的數據;定義一個變量(elementCount)表示有效元素的個數或范圍,也就是棧中的元素;主要操作在有效元素部分的尾部,當push時超過數組大小,數組需要擴容。
大致示意圖如下:

push()操作
Stack<E>類:
public E push(E item) { addElement(item); return item; }
Vector<E>類:
默認構造時,數組大小初始化為10。 push()操作
即向數組最后一個有效元素位置后填入push的數據。當數組大小不足時,需要擴容。具體看下列注釋。
//數組變量定義 protected Object[] elementData; //有效元素個數,在棧中即表示棧的個數 protected int elementCount; //當數組溢出時,擴容 增加的大小。 protected int capacityIncrement; //3種構造方式,默認構造方式的 數組大小初始化為10. public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; } public Vector(int initialCapacity) { this(initialCapacity, 0); } public Vector() { this(10); } //增加元素 public synchronized void addElement(E obj) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = obj; }
數組擴容
這種擴容方式,如果有相關需求可以作為參考。
private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
peek()操作
先看peek()是,由於pop()會調用到。
Stack類:
棧有效個數為elementCount(也是數組有效元素部分)。返回數組第elementCount個元素即可,下標為elementCount-1。
public synchronized E peek() { int len = size(); if (len == 0) throw new EmptyStackException(); return elementAt(len - 1); }
Vector類:
public synchronized int size() { return elementCount; } public synchronized E elementAt(int index) { if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } return elementData(index); } @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; }
pop()操作
通過peek獲取棧頂元素並作為返回值。再刪除棧頂元素。
部分代碼上面已有,不列出。
Stack類:
刪除數組中有效元素的最后一個,即下標為elementCount-1
public synchronized E pop() { E obj; int len = size(); obj = peek(); removeElementAt(len - 1); return obj; }
Vector類:
通用方法,數組中刪除某個元素,之后的元素要前移。
實際對於棧,傳入的index=elementCount-1。 所以j=0。不需要移動元素,是刪除的最后一個元素。
public synchronized void removeElementAt(int index) { modCount++; if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } else if (index < 0) { throw new ArrayIndexOutOfBoundsException(index); } int j = elementCount - index - 1; if (j > 0) { System.arraycopy(elementData, index + 1, elementData, index, j); } elementCount--; elementData[elementCount] = null; /* to let gc do its work */ }
search()操作
查找到后,返回位置。沒找到即返回-1。
這個位置相對棧頂,棧頂(1)即數組最后一個有效元素(下標為 size-1)。如果是數組[i]是查找的元素,那么相對棧頂位置即((size-1)- i )+1 = size - i。
Stack類中
public synchronized int search(Object o) { int i = lastIndexOf(o); if (i >= 0) { return size() - i; } return -1; }
Vector類中:
public synchronized int lastIndexOf(Object o) { return lastIndexOf(o, elementCount-1); } public synchronized int lastIndexOf(Object o, int index) { if (index >= elementCount) throw new IndexOutOfBoundsException(index + " >= "+ elementCount); if (o == null) { for (int i = index; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = index; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; }
empty()操作
public boolean empty() { return size() == 0; }
棧的使用
符號匹配問題
這里只考慮3對符號,"{}[]()"。匹配規則如代碼中,如"{([])}”是符合的,"{([)]}"是不符合的。
思路很簡單:符合的情況就是第一個右符合與前面最近的一個左符號是匹配的。
匹配到左符號,壓入堆棧;匹配到右符號,與棧頂比較,匹配即符合 pop出棧頂元素;當所有匹配完,棧為空即符合的。
實現代碼如下:
public class StackTest<E> { public static void main(String[] args) { System.out.println(symbolMatch("{for(int i=0;i<10;i++)}")); System.out.println(symbolMatch("[5(3*2)+(2+2)]*(2+0)")); System.out.println(symbolMatch("([5(3*2)+(2+2))]*(2+0)")); } public static boolean symbolMatch(String expression) { final char CHAR_NULL = ' '; if (expression == null || expression.equals("")) throw new RuntimeException("expression is nothing or null"); //StackTest<Character> stack = new StackTest<Character>(); Stack<Character> stack = new Stack<Character>(); char[] exps = expression.toCharArray(); for (int i = 0; i < exps.length; i++) { char matchRight = CHAR_NULL; switch (exps[i]) { case '(': case '[': case '{': stack.push(Character.valueOf(exps[i])); break; case ')': matchRight = '('; break; case ']': matchRight = '['; break; case '}': matchRight = '{'; break; } if(matchRight == CHAR_NULL) continue; if (stack.isEmpty()) return false; if (stack.peek().charValue() == matchRight) stack.pop(); } if (stack.isEmpty()) return true; return false; } }
輸出結果:
true true false