棧,和數組或鏈表一樣,也是一種用來存儲數據的線性結構,但不同的是,棧對數據的存取有着限制,它遵循着先進后出或后進先出的原則。怎么理解呢?想一想收件箱中的郵件。打開收件箱,郵件是按時間順序從晚到早時進行排列的,第一封郵件時間最晚,最后一封郵件時間最早,郵件來的越早,它越在收件箱的底部,郵件來的越晚,它越在收件箱的頂部。看郵件的時候,先看第一封郵件,看的是來的最晚的郵件。如果不重要就刪除了,這就是后進先出,最晚的郵件最先刪除。看了好久,終於快看完了,先進后出。此時,又收到一封郵件,它又放到收件箱的頂端,你發現,無論是添加還是刪除,都是在收件箱的頂部操作。如果把收件箱看作棧,你會出現棧的一些特征,棧中元素的排列順序,正好和它們的添加時間相反,添加的越早,越在棧的下面,添加的越晚,越在棧的上面。添加元素相當於把原有元素向下壓,刪除元素相當於下面的元素向上頂,把元素彈出去。添加或刪除,都在棧的一端操作,棧的頂部操作,而不是隨意的操作棧中的元素,如果想獲取棧底的元素,只能把它上面的元素都彈出來。元素稱為入棧或壓棧,刪除元素稱為出棧或彈棧。
棧是一種抽象的概念,要怎么實現它的呢?實際上,只要保證先進后出或后進先出的原則,用什么實現都可以,數組,鏈表,甚至隊列,都沒有問題。
數組實現
使用數組,怎么保證先進后出或后進先出呢?在數組的尾部進行添加或刪除元素就可以了。添加第一個元素放到數組的0位置,添加第二個元素放到數組的1位置 ,刪除的時候,先刪除第1個位置的元素,再刪第0個位置上的元素。 數組的尾部進行操作,保證了后進先出。數組尾部操作也簡單,它不會影響數組內的其它元素。根據描述,也可以知道實現棧的類的屬性,一個是數組的引用,一個是追蹤元素的索引。
構造函數就是要創建數組,進行初始化棧。創建數組要指明數組的長度,長度具體是多少,可以讓使用者指定,也可以設一個默認的長度。
public class ArrayStack<T> { private T[] stack; // 實現棧的底層數組 private int topIndex; // 記錄棧中或數組中最后一個元素的位置 private static final int DEFAULT_CAPACITY = 50; // 默認數組的大小 public ArrayStack() { this(DEFAULT_CAPACITY); } public ArrayStack(int initialCapacity) { stack = (T[])new Object[initialCapacity]; topIndex = 0; } }
添加元素(入棧),稱為push,就是向數組的尾部添加元素,如下圖所示
就是數組topIndex 的位置處設為添加的元素,同時topIndex加1。
public void push(String elem) { stack[topIndex] = elem; topIndex++; }
刪除元素或出棧,稱為pop。就是topInex--,獲取到topIndex處的元素,然后把數組這個位置設為null。
public T pop(){ topIndex--; String item = stack[topIndex]; stack[topIndex] = null; return item; }
當然,如果棧為空的話,是不能執行刪除操作的,如果執行刪除操作,就報錯。這時還需要isEmpty()的方法,同時在pop()中調用
public boolean isEmpty() { return topIndex == 0; } public T pop(){ if (isEmpty()){ throw new RuntimeErrorException("棧為空"); } topIndex--; T item = stack[topIndex]; stack[topIndex] = null; return item; }
除了主要的push和pop方法外,棧還有其它幾個基本方法。peek() 方法, 看一下棧的頂部元素是什么,它只需要獲取元素,而不用移動topIndex。和pop()方法一樣,如果棧為空,peek()方法報錯
public T peek() { if(isEmpty()){ throw new RuntimeErrorException("棧為空"); } return stack[topIndex - 1]; }
size()方法,棧中有多少個元素,直接返回topIndex 就可以了。
public int size() { return topIndex; }
clear(), 清空棧,簡單點,就直接讓topIndex = 0; 復雜一點,就是調用pop() 方法。
public void clear() { topIndex = 0; }
數組實現有一個問題,就是是數組的長度是固定的,如果不停地添加元素,數組會滿的。這個時候,就要擴容。擴容的實現方式是重新創建一個大數組,把stack數組中的元素,復制到新的數組中,然后讓stack指向這個新數組
public void ensureCapcity(){ T[] bigArray = (T[])new Object[stack.length * 2]; for(int i = 0; i < stack.length; i++){ bigArray[i] = stack[i]; } stack = bigArray; }
當不斷刪除元素的時候,數組就需要縮容。縮容和擴容的原理一樣,只不過是生成一個小的數組,這時可以把ensureCapcity()方法接受一個參數,擴容和縮容就可以使用一個方法了,把方法名改成resize()
private void resize(int max) { T[] tempArray = (T[])new Object[max]; for (int i = 0; i < N; i++) tempArray[i] = stack[i]; stack = temp; }
push() 方法,就要選判斷數組是不是還有剩余空間,如果沒有就擴容,調用resize()方法,擴多大呢?一般是原數組大小的兩倍。
public void push(String elem) { if (topIndex == stack.length) resize(2 * stack.length); stack[topIndex] = item; topIndex++; }
縮容,就是在斷刪除元素的時候,
public T pop(){ if (isEmpty()){ throw new RuntimeErrorException("棧為空"); } topIndex--; T item = stack[topIndex]; stack[topIndex] = null; if (topIndex > 0 && topIndex== stack.length / 4) { resize(stack.length / 2); } return item; }
迭代方法的實現
import java.util.Iterator; public class ArrayStack<T> implements Iterable<T> { @Override public Iterator<T> iterator() { return new ReverseArrayIterator(); } private class ReverseArrayIterator implements Iterator<T> { int i = topIndex; @Override public boolean hasNext() { return i > 0; } @Override public T next() { return stack[--i]; } } }
鏈表實現
鏈表需要節點和頭指針,棧需要記錄元素的個數,所能實現棧的鏈表類需要三個屬性,
public class LinkedListStack<T>{ private class Node { T item; // 棧中元素 Node next; } private Node topNode; private int N; }
入棧和出棧的操作可以放在鏈表的頭部,從頭部插入節點,頭指針指向最近插入的節點,從頭部刪除節點,最近插入的節點補刪除了,后進先出。鏈表的頭部插入節點,創建新節點newNode, 它的next指向原來的頭指針,再把新節點newNode賦值給頭指針
public void push(T item) { Node newNode = new Node(); newNode.item = item; newNode.next = topNode; topNode =newNode; N++; }
出棧,從鏈表的頭部刪除節點,直接讓topNode = topNode.next, topNode 指向鏈表的第二個節點,第一個節點也就引用不到了,也就刪除了。
public T pop() { if(isEmpty()){ throw new RuntimeException("棧為空"); } T item = topNode.item; first = topNode.next; N--; return item; }
其它幾個簡單方法
public boolean isEmpty() { return topNode == null; } public int size() { return N; } public void clear() { topNode = null; } public T peek(){ if(isEmpty()){ throw new RuntimeException("棧為空"); } return topNode.item; }
棧的迭代,一個是next()方法,返回棧中的元素,可以聲明一個變量temp,先指向第一個元素,每調用一次next方法,就讓這個變量,指向下一個節點,temp=temp.next 。一個是hasNext方法,棧中還有沒有元素,可以判斷,temp是否等於null,如果等於null,就表明到了鏈表的尾部,沒有元素了,反之,則有元素。
import java.util.Iterator; public class LinkedListStack<T> implements Iterable<T>{ @Override public Iterator<T> iterator() { return new StackIterator(); } private class StackIterator implements Iterator<T> { Node temp = topNode; @Override public boolean hasNext() { return temp != null; } @Override public T next() { T item = temp.item; temp = temp.next; return item; } } }