數據結構基礎(四)堆棧


在這一章我們來了解兩個很特殊的數據結構:堆棧 (Stack) 和隊列 (Queue)。這兩個數據結構類似垃圾桶和隊伍,棧是先進后出型,隊列是先進先出型。

堆棧(Stack)

概念

堆棧是一種常用的數據結構,這種數據結構的存儲方式和垃圾桶一樣,后面放進去的元素可以先取出來,而最早放入的元素會被壓在最下面,最后才能被拿出來。我們也可以把棧的儲存方式簡單理解為堆盤子,后面加入的盤子會被堆到最上面,最早堆入的盤子在最下面:

所以棧是一種后進先出(Last In First Out)的數據結構,后入的元素先出,先入的元素后出。

堆棧主要支持以下兩種操作:

  • 入棧(Push):將一個元素放入棧,用來加入數據。
  • 出棧(Pop):將一個元素彈出棧,用來刪除數據。

還有以下兩種輔助操作:

  • Peek:查看最頂部的元素。
  • isEmpty:查看棧是否為空。
Stack Representation

數組棧實現

鏈表有兩種實現方式,一種是數組,另一種是鏈表。數據的實現方式很簡單,以下是數組棧的定義:

public class Stack {
    static final int CAPACITY = 1000;
    int top;
    int stack[];

    public Stack() {
        top = -1;
        stack = new int[CAPACITY];
    }
}

在數組棧中,我們使用數組來儲存數據,其中包含一個CAPACITY來限制棧的容量,並使用指針top來記錄最頂端元素的位置,top初始化為-1,代表數組棧沒有任何元素。以下是push和pop方法的定義:

public boolean push(int val) {
    if (top >= (CAPACITY - 1)) {
        System.out.println("Stack Overflow.");
        return false;
    }
    stack[++top] = val;
    return true;
}

public int pop() {
    if (top < 0) {
        System.out.println("Stack Underflow.");
        return 0;
    }
    int element = stack[top--];
    return element;
}

在push中,我們需要檢查頂部元素是否達到容量限制,如果是,輸出“溢出棧”錯誤。否則移動top指針,加入新的元素。在pop中,也要查看棧是否為空,如果是,那么輸出“棧下溢”錯誤。否則將top指針減一。另外還有peek和isEmpty的實現:

public int peek() {
    if (top < 0) {
        System.out.println("Stack Underflow");
        return 0;
    }
    int element = stack[top];
    return element;
}

public boolean isEmpty() {
    return top < 0;
}

peek也要查看棧是否為空,如果不是,直接返回top指向的元素。isEmpty只要查看top是否小於0即可。

鏈式棧實現

除了數組棧,我們也可以使用鏈表來實現棧,以下是鏈式棧的定義:

public class ListStack {

    static class StackNode {
        int val;
        StackNode next;
        StackNode(int val) {
            this.val = val;
        }
    }

    StackNode top;

    public ListStack() {
        top = null;
    }
}

在鏈式棧中,我們先定義節點StackNode,節點中包含數值和下一個節點的指針。在鏈式棧中,我們只需要記錄top節點,在初始化時定義為null。以下是push和pop的定義:

public void push(int val) {
    StackNode newNode = new StackNode(val);
    if (top == null) {
        top = newNode;
    } else {
        StackNode temp = top;
        top = newNode;
        newNode.next = temp;
    }
    System.out.println(val + " is pushed to stack.");
}

public int pop() {
    if (top == null) {
        System.out.println("Stack is Empty.");
        return Integer.MIN_VALUE;
    }
    int popped = top.val;
    top = top.next;
    return popped;
}

在push中,我們先創建新的節點newNode,如果棧為空,那么直接將newNode賦給top。如果不為空,就將新元素的下一節點指向當前的top,並將newNode更新為top節點。在pop中,也要先檢查棧是否為空,不為空的話,記錄下top的數據作為返回值,並將top更新為自己的下一個節點。以下是鏈式棧peek和isEmpty的定義:

public int peek() {
    if (top == null) {
        System.out.println("Stack is empty.");
        return Integer.MIN_VALUE;
    }
    return top.val;
}

public boolean isEmpty() {
    return top == null;
}

在棧不為空的情況下,peek只需查看top的值即可,isEmpty也只要查看top是否是null就可以了。不管是用數組還是鏈表來實現棧,我們都只要處理頭節點top,所以棧的所有操作都為O(1)。

隊列(Queue)

概念

隊列是很好理解的一種數據結構,顧名思義,隊列數據結構就和我們平時排隊一樣,先進入的元素先出,后進入的元素后出。隊列的兩端都是開的,一段負責插入新元素,另一端負責刪除元素。

Queue Example

隊列主要支持以下兩種操作:

  • 入隊(enqueue):增加一個新的元素
  • 出隊(dequeue):刪除一個元素

還支持其他輔助操作:

  • peek – 查看隊列最前端的元素
  • isFull – 查看隊列是否滿了
  • isEmpty – 查看隊列是否為空

數組隊列實現

隊列和堆棧一樣,也可以使用兩種實現方式,一種是使用數組,叫做順序隊列,另一種是使用鏈表實現,叫做鏈式隊列。以下我們會先實現一種更常見的數組隊列,叫做循環隊列,它和基礎的順序隊列相比較,更能有效地利用數組空間。以下是循環隊列的定義:

public class ArrayQueue {
    int front, rear, size;
    int capacity;
    int array[];

    public ArrayQueue(int capacity) {
        this.capacity = capacity;
        front = rear = size = 0;
        array = new int[capacity];
    }
}

在循環隊列中,我們需要capacity來限制隊列的長度,並創建兩個指針front和rear,front用來指向隊列的頭部,而rear指向隊列的尾部。隊列總是從頭部取出元素,從尾部插入新元素,在操作隊列時,我們只需要移動front和rear兩個指針即可。我們還需要一個額外的size變量來記錄元素的數量,front,rear和size都初始化為0 。

以下是enqueue和dequeue的定義:

public void enqueue(int item) {
    if (isFull()) return;
    array[rear] = item;
    rear = (rear + 1) % capacity;
    size++;
    System.out.println(item + " is enqueued.");
}

public int dequeue() {
    if (isEmpty()) return Integer.MIN_VALUE;
    int item = array[front];
    front = (front + 1) % capacity;
    size --;
    return item;
}

在新元素入隊的時候,我們需要先判斷隊列是否已滿(isFull的代碼在下一段)。如果未滿,那么就把元素插入rear的位置,並將rear加1,並與capacity取模,然后增加size。在出隊的時候,先要檢查隊列是否為空(isEmpty的代碼在下一段),記錄下刪除元素的值后,我們將front指針增加1,與capacity取模,然后將size減少1。以下是輔助操作的定義:

public int peek() {
    if (isEmpty()) return Integer.MIN_VALUE;
    return array[front];
}

public boolean isFull() {
    return size == capacity;
}

public boolean isEmpty() {
    return size == 0;
}

peek只要查看front指針指向的值即可,isFull要檢查size是否和容量capacity相同,isEmpty直接查看size是否等於0。

鏈式隊列實現

用鏈表實現隊列也很簡單,和數組實現相似,也需要兩個指針(front和rear)來實現。以下是ListQueue和QueueNode的定義:

public class ListQueue {

    QueueNode front;
    QueueNode rear;

    static class QueueNode {
        int value;
        QueueNode next;
        public QueueNode(int value) {
            this.value = value;
        }
    }
}

在鏈式隊列中,我們需要定義節點QueueNode,QueueNode中含有兩個值:一個是節點的數值value,另一個是指向下一個節點的next指針。在鏈式隊列ListQueue中,我們只需要兩個節點front和rear,front用來指向隊列最前端的節點,而rear用來指向尾節點。以下是兩個重要操作enqueue和deuque的實現:

public void enqueue(int value) {
    QueueNode newNode = new QueueNode(value);
    if (this.rear == null) { // Queue is empty
        this.front = this.rear = newNode;
        return;
    }
    this.rear.next = newNode;
    this.rear = newNode;
}

public int dequeue() {
    if (this.front == null) {
        System.out.println("The queue is empty.");
        return Integer.MIN_VALUE;
    }
    QueueNode frontNode = this.front;
    this.front = this.front.next;
    if (this.front == null) {
        this.rear = null;
    }
    return frontNode.value;
}

在enqueue中,我們先創建一個新的節點,如果隊列為空,那么將頭節點和尾節點同時指向新節點,結束操作。如果隊列不為空,只要尾節點的next指針指向新節點,然后將尾節點指向新節點。在dequeue中,如果頭節點front為空,直接返回默認數值。隊列不為空的情況下,記錄下front的數值作為返回值,並將頭節點更新為下一節點。

完整代碼

GitHub完整代碼

Leetcode相關練習

Stack相關:

Queue相關:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM