1. 程式人生 > 其它 >【資料結構與演算法】之棧

【資料結構與演算法】之棧

技術標籤:資料結構與演算法資料結構java

【資料結構與演算法】之棧

一、什麼是棧

前言:放假在家裡看書的感覺真好呀~。棧作為一種特殊的線性表,操作較為簡單,但是有著很多的應用場景。

(一)棧的定義

棧 (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),與資料量成正比。