1. 程式人生 > >【從今天開始好好學資料結構02】棧與佇列

【從今天開始好好學資料結構02】棧與佇列

目錄

  • 1、理解棧與佇列
  • 2、用程式碼談談棧
  • 3、用程式碼談談佇列

我們今天主要來談談“棧”以及佇列這兩種資料結構。

回顧一下上一章中【資料結構01】陣列中,在陣列中只要知道資料的下標,便可通過順序搜尋很快查詢到資料,可以根據下標不同自由查詢,然而今天要講的“棧”以及佇列這兩種資料結構訪問是受限制的,只允許在一端讀取、插入和刪除資料,這時候對它存在的意義產生了很大的疑惑。因為會覺得,相比陣列和連結串列,棧帶給我的只有限制,並沒有任何優勢。那我直接使用陣列或者連結串列不就好了嗎?為什麼還要用這個“操作受限”的“棧”呢?事實上,從功能上來說,陣列或連結串列確實可以替代棧,但你要知道,特定的資料結構是對特定場景的抽象,而且,陣列或連結串列暴露了太多的操作介面,操作上的確靈活自由,但使用時就比較不可控,自然也就更容易出錯。

@

1、理解棧與佇列

首先,如何理解“棧”?用現實一個通俗貼切的例子,我們平時放盤子的時候,都是從下往上一個一個放;取的時候,我們是從上往下一個一個地依次取,不能從中間任意抽出,先進後出,這就是典型的“棧”結構,當某個資料集合只涉及在一端插入和刪除資料,並且滿足後進先出、先進後出的特性,我們就應該首選“棧”這種資料結構。

其次如何理解佇列?同樣用現實一個通俗貼切的例子,平時在校的時候飯堂吃飯都是排隊,而且不能插隊,先進先出,這就是典型的佇列結構

2、用程式碼談談棧

實際上,棧既可以用陣列來實現,也可以用連結串列來實現。用陣列實現的棧,我們叫作順序棧,用連結串列實現的棧,我們叫作鏈式棧。不管是順序棧還是鏈式棧,我們儲存資料只需要一個大小為n的陣列就夠了。在入棧和出棧過程中,只需要一兩個臨時變數儲存空間,所以空間複雜度是O(1)。

注意,這裡儲存資料需要一個大小為n的陣列,並不是說空間複雜度就是O(n)。因為,這n個空間是必須的,無法省掉。所以我們說空間複雜度的時候,是指除了原本的資料儲存空間外,演算法執行還需要額外的儲存空間。

空間複雜度分析是不是很簡單?時間複雜度也不難。不管是順序棧還是鏈式棧,入棧、出棧只涉及棧頂個別數據的操作,所以時間複雜度都是O(1)。

還有一點,JVM記憶體管理中有個“堆疊”的概念。棧記憶體用來儲存區域性變數和方法呼叫,堆記憶體用來儲存Java中的物件。那JVM裡面的“棧”跟我們這裡說的“棧”是不是一回事呢?如果不是,那它為什麼又叫作“棧”呢?知道的大牛請自覺評論區見面~

2.1、用陣列實現的棧:順序棧

public class MyStack {
    
    //棧的底層我們使用陣列來儲存資料
    int[] elements;

    public MyStack() {
        elements = new int[0];
    }
    
    //壓入元素
    public void push(int element) {
        // 建立一個新的陣列
        int[] newArr = new int[elements.length + 1];
        // 把原陣列中的元素複製到新陣列中
        for (int i = 0; i < elements.length; i++) {
            newArr[i] = elements[i];
        }
        // 把新增的元素放入新陣列中
        newArr[elements.length] = element;
        // 使用新陣列替換舊陣列
        elements = newArr;
    }
    
    //取出棧頂元素
    public int pop() {
        //棧中沒有元素
        if(elements.length==0) {
            throw new RuntimeException("stack is empty");
        }
        //取出陣列的最後一個元素
        int element = elements[elements.length-1];
        //建立一個新的陣列
        int[] newArr = new int[elements.length-1];
        //原陣列中除了最後一個元素的其它元素都放入新的陣列中
        for(int i=0;i<elements.length-1;i++) {
            newArr[i]=elements[i];
        }
        //替換陣列
        elements=newArr;
        //返回棧頂元素
        return element;
    }
    
    //檢視棧頂元素
    public int peek() {
        //棧中沒有元素
        if(elements.length==0) {
            throw new RuntimeException("stack is empty");
        }
        return elements[elements.length-1];
    }
    
    //判斷棧是否為空
    public boolean isEmpty() {
        return elements.length==0;
    }
    
}

2.2、測試陣列實現的棧

import demo2.MyStack;

public class TestMyStack {

    public static void main(String[] args) {
        //建立一個棧
        MyStack ms = new MyStack();
        //壓入陣列
        ms.push(9);
        ms.push(8);
        ms.push(7);
        //最出棧頂元素
        System.out.println(ms.pop());
        System.out.println(ms.pop());
        System.out.println(ms.pop());
        //檢視棧頂元素
//      System.out.println(ms.peek());
        System.out.println(ms.isEmpty());
    }

}

2.3、基於連結串列實現的棧:鏈式棧

package stack;
/**
 * 基於連結串列實現的棧。
 */
public class StackBasedOnLinkedList {
  private Node top = null;

  public void push(int value) {
    Node newNode = new Node(value, null);
    // 判斷是否棧空
    if (top == null) {
      top = newNode;
    } else {
      newNode.next = top;
      top = newNode;
    }
  }

  /**
   * 我用-1表示棧中沒有資料。
   */
  public int pop() {
    if (top == null) return -1;
    int value = top.data;
    top = top.next;
    return value;
  }

  public void printAll() {
    Node p = top;
    while (p != null) {
      System.out.print(p.data + " ");
      p = p.next;
    }
    System.out.println();
  }

  private static class Node {
    private int data;
    private Node next;

    public Node(int data, Node next) {
      this.data = data;
      this.next = next;
    }

    public int getData() {
      return data;
    }
  }
}

3、用程式碼談談佇列

棧只支援兩個基本操作:入棧push()和出棧pop()。佇列跟棧非常相似,支援的操作也很有限,最基本的操作也是兩個:入隊enqueue(),放一個數據到佇列尾部;出隊dequeue(),從佇列頭部取一個元素。所以,佇列跟棧一樣,也是一種操作受限的線性表資料結構。佇列的概念很好理解,基本操作也很容易掌握。作為一種非常基礎的資料結構,佇列的應用也非常廣泛,特別是一些具有某些額外特性的佇列,比如迴圈佇列、阻塞佇列、併發佇列。它們在很多偏底層系統、框架、中介軟體的開發中,起著關鍵性的作用。比如高效能佇列Disruptor、Linux環形快取,都用到了迴圈併發佇列;Java concurrent併發包利用ArrayBlockingQueue來實現公平鎖等。

跟棧一樣,佇列可以用陣列來實現,也可以用連結串列來實現。用陣列實現的棧叫作順序棧,用連結串列實現的棧叫作鏈式棧。同樣,用陣列實現的佇列叫作順序佇列,用連結串列實現的佇列叫作鏈式佇列

3.1、陣列實現佇列:順序佇列

package queue;

// 用陣列實現的佇列
public class ArrayQueue {
  // 陣列:items,陣列大小:n
  private String[] items;
  private int n = 0;
  // head表示隊頭下標,tail表示隊尾下標
  private int head = 0;
  private int tail = 0;

  // 申請一個大小為capacity的陣列
  public ArrayQueue(int capacity) {
    items = new String[capacity];
    n = capacity;
  }

  // 入隊
  public boolean enqueue(String item) {
    // 如果tail == n 表示佇列已經滿了
    if (tail == n) return false;
    items[tail] = item;
    ++tail;
    return true;
  }

  // 出隊
  public String dequeue() {
    // 如果head == tail 表示佇列為空
    if (head == tail) return null;
    // 為了讓其他語言的同學看的更加明確,把--操作放到單獨一行來寫了
    String ret = items[head];
    ++head;
    return ret;
  }

  public void printAll() {
    for (int i = head; i < tail; ++i) {
      System.out.print(items[i] + " ");
    }
    System.out.println();
  }
}

3.2、連結串列實現的佇列:鏈式佇列

package queue;

/**
 * 基於連結串列實現的佇列
 */
public class QueueBasedOnLinkedList {

  // 佇列的隊首和隊尾
  private Node head = null;
  private Node tail = null;

  // 入隊
  public void enqueue(String value) {
    if (tail == null) {
      Node newNode = new Node(value, null);
      head = newNode;
      tail = newNode;
    } else {
      tail.next = new Node(value, null);
      tail = tail.next;
    }
  }

  // 出隊
  public String dequeue() {
    if (head == null) return null;

    String value = head.data;
    head = head.next;
    if (head == null) {
      tail = null;
    }
    return value;
  }

  public void printAll() {
    Node p = head;
    while (p != null) {
      System.out.print(p.data + " ");
      p = p.next;
    }
    System.out.println();
  }

  private static class Node {
    private String data;
    private Node next;

    public Node(String data, Node next) {
      this.data = data;
      this.next = next;
    }

    public String getData() {
      return data;
    }
  }

}

3.2、迴圈佇列

用陣列來實現佇列的時候會有資料搬移操作,這樣入隊操作效能就會受到影響。那有沒有辦法能夠避免資料搬移呢?我們來看看迴圈佇列的解決思路。

迴圈佇列,顧名思義,它長得像一個環。原本陣列是有頭有尾的,是一條直線。現在我們把首尾相連,扳成了一個環。

package queue;
public class CircularQueue {
  // 陣列:items,陣列大小:n
  private String[] items;
  private int n = 0;
  // head表示隊頭下標,tail表示隊尾下標
  private int head = 0;
  private int tail = 0;

  // 申請一個大小為capacity的陣列
  public CircularQueue(int capacity) {
    items = new String[capacity];
    n = capacity;
  }

  // 入隊
  public boolean enqueue(String item) {
    // 佇列滿了
    if ((tail + 1) % n == head) return false;
    items[tail] = item;
    tail = (tail + 1) % n;
    return true;
  }

  // 出隊
  public String dequeue() {
    // 如果head == tail 表示佇列為空
    if (head == tail) return null;
    String ret = items[head];
    head = (head + 1) % n;
    return ret;
  }

  public void printAll() {
    if (0 == n) return;
    for (int i = head; i % n != tail; ++i) {
      System.out.print(items[i] + " ");
    }
    System.out.println();
  }
}

好了,到這裡,總結一下佇列,佇列最大的特點就是先進先出,主要的兩個操作是入隊和出隊。跟棧一樣,它既可以用陣列來實現,也可以用連結串列來實現。用陣列實現的叫順序佇列,用連結串列實現的叫鏈式佇列。特別是長得像一個環的迴圈佇列。在陣列實現佇列的時候,會有資料搬移操作,要想解決資料搬移的問題,我們就需要像環一樣的迴圈佇列。

如果本文章對你有幫助,哪怕是一點點,請點個讚唄,謝謝~

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術...說好了來了就是盆友喔...

相關推薦

今天開始好學資料結構02佇列

目錄 1、理解棧與佇列 2、用程式碼談談棧 3、用程式碼談談佇列 我們今天主要來談談“棧”以及佇列這兩種資料結構。 回顧一下上一章中【資料結構01】陣列中,在陣列中只要知道資料的下標,便可通過順

今天開始好學資料結構01陣列

面試的時候,常常會問陣列和連結串列的區別,很多人都回答說,“連結串列適合插入、刪除,時間複雜度O(1);陣列適合查詢,查詢時間複雜度為O(1)”。實際上,這種表述是不準確的。陣列是適合查詢操作,但是查詢的時間複雜度並不為O(1)。即便是排好序的陣列,你用二分查詢,時間複雜度也是O(logn)。所以,正確的表述

今天開始好學資料結構03連結串列

目錄 今天我們來聊聊“連結串列(Linked list)”這個資料結構。 在我們上一章中【從今天開始好好學資料結構02】棧與佇列棧與佇列底層都是採用順序儲存的這種方式的,而今天要聊的連結串列則是採用鏈式儲存,連結串列可以說是繼陣列之後第二種使用得最廣泛的通用資

今天開始好學資料結構04程式設計師你心中就沒點“樹”嗎?

目錄 樹(Tree) 二叉樹(Binary Tree) 前面我們講的都是線性表結構,棧、佇列等等。今天我們講一種非線性表結構,樹。樹這種資料結構比線性表的資料結構要複雜得多,內容也比較多,首先我們先從樹(Tre

OJ.2132資料結構實驗之佇列二:一般算術表示式轉換成字尾式

                            資料結構實驗之棧與佇列二:一般算術表示式轉換成字尾式                              Time Limit: 1000 ms                                 

資料結構實驗之佇列二:一般算術表示式轉換成字尾式(SDUT 2132)

題目連結 #include <bits/stdc++.h> using namespace std; typedef long long ll; int ok(char ch, char sh) { if(sh == '(')return 1; if((ch ==

資料結構實驗之佇列六:下一較大值(二)(SDUT 3333)

#include <bits/stdc++.h> using namespace std; int a[1000006]; int b[1000006]; int sta[100006]; int main() { int t,n,i,j,top; while(~sc

資料結構實驗之佇列五:下一較大值(一)(SDUT 3332)

#include <bits/stdc++.h> using namespace std; int a[1005]; int main() { int t,n,i,j; while(~scanf("%d",&t)) { while(t-

資料結構實驗之佇列三:字尾式求值(SDUT 2133)

題解:把每一步計算的答案再存在棧裡面,直到計算結束。           如果是運算元 那麼直接入棧;如果是運算子,那麼把棧裡面最頂部的兩個運算元拿出來進行運算,運算結果再放入到棧裡面,計算完所有的(#

資料結構實驗之佇列四:括號匹配(SDUT 2134)

#include <bits/stdc++.h> using namespace std; typedef long long ll; char s[100]; char a[100]; int main() { int i,j,k,f,top,len; while(

資料結構實驗之佇列五:下一較大值(一)

資料結構實驗之棧與佇列五:下一較大值(一) Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Problem Description 對於包含n(1<=n<=1000)個整數的序列,對於序列中的每一元素,

資料結構實驗之佇列五:下一較大值(一,二)

資料結構實驗之棧與佇列五:下一較大值(一,二) Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Problem Description 對於包含n(1<=n<=1000)個整數的序列,對於序

資料結構實驗之佇列七:出序列判定

Problem Description 給一個初始的入棧序列,其次序即為元素的入棧次序,棧頂元素可以隨時出棧,每個元素只能入棧依次。輸入一個入棧序列,後面依次輸入多個序列,請判斷這些序列是否為所給入棧序列合法的出棧序列。 例如序列1,2,3,4,5是某棧的壓入順序,序列4

資料結構實驗之佇列六:下一較大值(二)(因為資料量大所以用來操作)

資料結構實驗之棧與佇列六:下一較大值(二) Time Limit: 150 ms Memory Limit: 8000 KiB Problem Description 對於包含n(1<=n<=100000)個整數的序列,對於序列中的每一元素,在序列中查詢

資料結構實驗之佇列八:的基本操作

Problem Description 堆疊是一種基本的資料結構。堆疊具有兩種基本操作方式,push 和 pop。push一個值會將其壓入棧頂,而 pop 則會將棧頂的值彈出。現在我們就來驗證一下堆疊的

資料結構實驗之佇列九:行編輯器

Problem Description 一個簡單的行編輯程式的功能是:接受使用者從終端輸入的程式或資料,並存入使用者的資料區。 由於使用者在終端上進行輸入時,不能保證不出差錯,因此,若在編輯程式中,“每接受一個字元即存入使用者資料區”的做法顯然不是最恰當的。較好

資料結構實驗之佇列十:走迷宮

Problem Description 一個由n * m 個格子組成的迷宮,起點是(1, 1), 終點是(n, m),每次可以向上下左右四個方向任意走一步,並且有些格子是不能走動,求從起點到終點經過每個

資料結構實驗之佇列六:下一較大值(二)

Time Limit: 150 ms Memory Limit: 8000 KiB Problem Description 對於包含n(1<=n<=100000)個整數的序列,對於序列中的每一元素,在序列中查詢其位置之後第一個大於它的值,如果找到,輸出所找到的

資料結構實驗之佇列一:進位制轉換(SDUT 2131)

題目連結 題解: 特判一下n==0的時候。 #include <bits/stdc++.h> using namespace std; int a[1000]; int main() {

資料結構實驗之佇列一:進位制轉換

Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Problem Description 輸入一個十進位制非負整數,將其轉換成對應的 R (2 <= R <= 9) 進位制數,並