【資料結構與演算法】之棧
【資料結構與演算法】之棧
一、什麼是棧
前言:放假在家裡看書的感覺真好呀~。棧作為一種特殊的線性表,操作較為簡單,但是有著很多的應用場景。
(一)棧的定義
棧 (stack) 是限定僅在棧頂進行插入和刪除操作的線性表。
(二)棧的特點
- 遵循後進先出 (LIFO) 的原則。
- 棧只允許訪問一個數據(最後插入的資料),即只能對棧頂的資料進行操作。
- 一般的,棧的插入操作稱為進棧 (push),棧的刪除操作稱為出棧 (pop)。
二、為什麼要用棧
棧的引人簡化了程式設計的問題,劃分了不同關注層次,使得思考範圍縮小,更加聚焦於我們要解決的問題核心。反之,像陣列等,因為要分散精力去考慮陣列的下標增減等細節問題,反而可能掩蓋了問題的本質。
三、如何操作棧
棧作為一種抽象資料型別 (ADT),可通過陣列和連結串列來實現。
首先提供的是Stack介面,其內部定義了棧的抽象方法。
/**
* 棧介面,遵循後進先出的原則。
*
* @author likezhen
* @version 1.0
*/
public interface Stack<E> {
/**
* 返回棧中存放的資料個數
*
* @return 棧中存放的資料個數
*/
int size();
/**
* 判斷是否為空棧
*
* @return 是否為空棧
*/
boolean isEmpty();
/**
* 將資料插入棧頂
*/
void push(E e);
/**
* 移除並返回棧頂資料
*
* @return 棧頂資料(如果為空棧則返回Null)
*/
E pop();
/**
* 返回棧頂資料
*
* @return 棧頂資料(如果為空棧則返回Null)
*/
E peek();
}
(一)順序棧
用陣列來實現的棧稱為順序棧。特別地,陣列的尾部為棧頂,索引為0的位置為棧底。
順序棧類 ArrayStack 實現棧介面。順序棧和陣列一樣,需要先指明其容量,一旦確定,就不能更改。
/**
* 順序棧類,通過陣列來實現棧結構
* 提供了pop(), push(), peek()等方法
*
* @author likezhen
* @version 1.0
*/
class ArrayStack<E> implements Stack<E> {
public final static int CAPACITY = 10; //預設容量為10
private E[] data; //存放資料的陣列
private int t = -1; //空棧時計數器為-1
public ArrayStack() {
this(CAPACITY);
}
public ArrayStack(int capacity) { //可自定義棧的容量
data = (E[]) new Object[capacity];
}
@Override
public int size() {
return (t + 1);
}
@Override
public boolean isEmpty() {
return t == -1;
}
@Override
public void push(E e) throws IllegalStateException {
if (size() == data.length) throw new IllegalStateException("棧已滿");
data[++t] = e; //先自增,後賦值
}
@Override
public E peek() {
if (isEmpty()) return null;
return data[t];
}
@Override
public E pop() {
if (isEmpty()) return null;
E answer = data[t]; //先賦值
data[t] = null; //讓垃圾回收器回收
t--; //後自減
return answer;
}
}
在順序棧的測試類中,和我之前的一篇總結文章(【資料結構與演算法】之連結串列)一樣,用Person類作為待儲存的資料。進棧順序為 A->B->C,棧頂資料為C,出棧順序為 C->B->A,符合棧的後進先出原則。
/**
* 順序棧的測試類
*
* @author likezhen
* @version 1.0
*/
public class ArrayStackApp {
public static void main(String[] args) {
ArrayStack<Person> arrayStack = new ArrayStack<>(3); //空棧,容量為3
arrayStack.push(new Person("A", 1)); //進棧
arrayStack.push(new Person("B", 2)); //進棧
arrayStack.push(new Person("C", 3)); //進棧
System.out.println("--------進棧順序-------\n" +
"Person{name='A', age=1}\n" +
"Person{name='B', age=2}\n" +
"Person{name='C', age=3}");
System.out.println("--------棧頂元素--------");
System.out.println(arrayStack.peek());
System.out.println("--------出棧順序--------");
while (!arrayStack.isEmpty()) {
System.out.println(arrayStack.peek());
arrayStack.pop();
}
}
}
測試結果:
D:\Java\jdk\bin\java.exe
--------進棧順序-------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}
--------棧頂元素--------
Person{name='C', age=3}
--------出棧順序--------
Person{name='C', age=3}
Person{name='B', age=2}
Person{name='A', age=1}
Process finished with exit code 0
(二)鏈棧
用單鏈表來實現的棧稱為鏈棧。特別地,與順序棧相反,單鏈表的表頭為棧頂,表尾為棧底。原因之一是在棧的所有操作中時間複雜度都為 O(1),在單鏈表表頭增加和刪除一個結點的時間複雜度也都為 O(1),但在單鏈表的表尾刪除一個結點的時間複雜度為 O(N),所以表頭為棧頂更適合。
鏈棧類 LinkedStack 實現了棧介面,封裝了一個單鏈表物件。通過呼叫單鏈表的各種方法,即可實現鏈棧的操作。關於單鏈表的原始碼,可參考我之前總結的一篇關於連結串列的文章——【資料結構與演算法】之連結串列。
棧方法 | 單鏈表方法 | 描述 |
---|---|---|
size() | list.getSize() | 獲取棧(或連結串列)中的資料個數 |
isEmpty() | list.isEmpty() | 判斷是否為空棧(或空連結串列) |
push(E e) | list.addFirst(E e) | 在棧頂(或表頭)新增資料 |
pop() | list.removeFirst() | 移除棧頂(或表頭)的資料 |
peek() | list.first() | 獲取棧頂(或表頭)存放的資料 |
/**
* 鏈棧類,通過連結串列來實現棧結構
* 提供了pop(), push(), peek()等方法
*
* @author likezhen
* @version 1.0
*/
class LinkedStack<E> implements Stack<E> {
private SinglyLinkedList<E> list = new SinglyLinkedList<>(); //私有的單鏈表物件
public LinkedStack() {
}
@Override
public int size() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void push(E e) {
list.addFirst(e);
}
@Override
public E pop() {
return list.removeFirst();
}
@Override
public E peek() {
return list.first();
}
}
在鏈棧的測試類中,用Person類作為待儲存的資料。進棧順序為 A->B->C,棧頂資料為C,出棧順序為 C->B->A,符合棧的後進先出原則。
/**
* 鏈棧的測試類
*
* @author likezhen
* @version 1.0
*/
public class LinkedStackApp {
public static void main(String[] args) {
LinkedStack<Person> linkedStack = new LinkedStack<>(); //空棧
linkedStack.push(new Person("A", 1)); //進棧
linkedStack.push(new Person("B", 2)); //進棧
linkedStack.push(new Person("C", 3)); //進棧
System.out.println("--------進棧順序-------\n" +
"Person{name='A', age=1}\n" +
"Person{name='B', age=2}\n" +
"Person{name='C', age=3}");
System.out.println("--------棧頂元素--------");
System.out.println(linkedStack.peek());
System.out.println("--------出棧順序--------");
while (!linkedStack.isEmpty()) {
System.out.println(linkedStack.peek());
linkedStack.pop();
}
}
}
測試結果:
D:\Java\jdk\bin\java.exe
--------進棧順序-------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}
--------棧頂元素--------
Person{name='C', age=3}
--------出棧順序--------
Person{name='C', age=3}
Person{name='B', age=2}
Person{name='A', age=1}
Process finished with exit code 0
- 時間複雜度分析:
棧因為其特殊的結構,永遠只能操作一個數據,所以其進出棧等所有棧操作的時間複雜度都為 O(1),與資料量無關。 - 空間複雜度分析:
順序棧的空間複雜度為 O(N),與陣列的長度成正比。
鏈棧的空間複雜度為 O(N),與資料量成正比。