1.棧介紹
棧是一種比較重要的線性結構。從數據結構的角度看,棧也是線性表,只不過它是操作受限的線性表。其限制是僅允許在表的一端進行插入和刪除操作,不允許在其他任何位置進行插入、查找、刪除等操作。
在表中進行插入、刪除操作的一端稱爲棧頂(top),棧頂保存的元素稱爲棧頂元素。相對的,棧的另一端稱爲棧底(bottom)。不含元素的空表稱爲空棧。
假設棧S=(a0,a1,…,an-1),則稱a0爲棧底元素,an-1爲棧頂元素。棧中元素按照a0,a1,…,an-1的次序進棧,退棧的第一個元素應爲棧頂元素。換句話說,棧的修改是按後進先出的原則進行的。因此,棧又稱爲後進先出(Last In First Out)的線性表(簡稱LIFO結構)。
下圖展示了一個棧及數據元素插入和刪除的過程:
在上圖中,當ABCD均已入棧後,出棧時得到的序列爲DCBA,這就是後進先出。
棧的基本操作除了進棧push()
,出棧pop()
之外,還有判空isEmpty()
、取棧頂元素peek()
等操作。
2.棧的順序存儲與實現
和線性表類似,棧也有兩種存儲結構:順序存儲和鏈式存儲。
順序棧是使用順序存儲結構實現的棧,即利用一組地址連續的存儲單元依次存放棧中的數據元素。由於棧是一種特殊的線性表,因此在線性表的順序存儲結構的基礎上,選擇線性表的一端作爲棧頂即可。那麼根據數組操作的特性,選擇數組下標大的一端,即線性表順序存儲的表尾來作爲棧頂,此時入棧、出棧操作可以O(1)時間完成。
由於棧的操作都是在棧頂完成,因此在順序棧的實現中需要附設一個指針top來動態地指示棧頂元素在數組中的位置。通常top可以用棧頂元素所在的數組下標來表示,top=-1
時表示空棧。
棧在使用過程中所需的最大空間難以估計,所以,一般構造棧的時候不應設定最大容量。一種合理的做法和線性表類似,先爲棧分配一個基本容量,然後在實際的使用過程中,當棧的空間不夠用時再倍增存儲空間。
下述代碼給出了棧的順序存儲的實現:
public class ArrayStack<T> {
private final int DEFAULT_LEN = 8;// 數組的默認大小
private T[] elements;// 數組
private int top; // 棧頂指針
@SuppressWarnings("unchecked")
public ArrayStack() {
top = -1;
elements = (T[]) new Object[DEFAULT_LEN];
}
/**
* 獲取棧的大小,即棧中數據元素的個數
*
* @return
*/
public int size() {
return top + 1;
}
/**
* 獲取堆棧是否爲空,爲空返回true,否則返回false
*
* @return
*/
public boolean isEmpty() {
return top == -1;
}
/**
* 入棧
*
* @param e
*/
public void push(T e) {
if (size() >= elements.length) {
// 棧滿
ensureCapacity();
}
elements[++top] = e;
}
/**
* 輔助方法,擴充容量
*/
@SuppressWarnings("unchecked")
private void ensureCapacity() {
T[] a = (T[]) new Object[2 * elements.length + 1];
System.arraycopy(elements, 0, a, 0, size());
elements = a;
}
/**
* 出棧
*
* @return
*/
public T pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
T e = elements[top];
elements[top--] = null;
return e;
}
/**
* 獲取棧頂元素
*
* @return
*/
public T peek() {
if (isEmpty()) {
throw new EmptyStackException();
}
return elements[top];
}
}
以上基於數據實現的棧代碼並不難理解。由於有top指針的存在,所以size()
、isEmpty()
方法均可在O(1)時間內完成。push()
、pop()
和peek()
方法,除了需要ensureCapacity()
外,都執行常數基本操作,因此它們的運行時間也是O(1)。
3.棧的鏈式存儲與實現
棧的鏈式存儲即採用鏈表實現棧。當採用單鏈表存儲線性表後,根據單鏈表的操作特性選擇單鏈表的頭部作爲棧頂,此時,入棧和出棧等操作可以在O(1)時間內完成。
由於棧的操作只在線性表的一端進行,在這裏使用帶頭結點的單鏈表或不帶頭結點的單鏈表都可以。使用帶頭結點的單鏈表時,結點的插入和刪除都在頭結點之後進行;使用不帶頭結點的單鏈表時,結點的插入和刪除都在鏈表的首結點上進行。
下面以不帶頭結點的單鏈表爲例實現棧,如下示意圖所示:
在上圖中,top爲棧頂結點的引用,始終指向當前棧頂元素所在的結點。若top爲null,則表示空棧。入棧操作是在top所指結點之前插入新的結點,使新結點的next域指向top,top前移即可;出棧則直接讓top後移即可。
如下給出了棧的鏈式存儲實現的代碼:
public class LinkedStack<T> {
/**
* 內部結點類
*/
private class Node{
private T data;
private Node next;
public Node() {
this(null,null);
}
public Node(T data, LinkedStack<T>.Node next) {
super();
this.data = data;
this.next = next;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
private Node top; // 首結點,即棧頂
private int size;// 棧的大小
public LinkedStack() {
top = null;
size = 0;
}
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
public void push(T e){
Node newNode = new Node(e, top);
top = newNode;
size ++;
}
public T pop(){
if(isEmpty()){
throw new EmptyStackException();
}
T data = top.getData();
top = top.getNext();
size --;
return data;
}
public T peek(){
if(isEmpty()){
throw new EmptyStackException();
}
return top.getData();
}
}
上述LinkedStack
類中有兩個成員變量,其中top
表示首結點,也就是棧頂元素所在的結點;size
指示棧的大小,即棧中數據元素的個數。不難理解,所有的操作均可以在O(1)時間內完成。