棧和隊列

目錄

1、引言
2、棧
3、隊列

引言

棧和隊列都是動態集合,可以理解為線性表或線性表實現的數據結構。它可以由數組實現,也可以由鏈表實現。

和數組鏈表等不一樣的是,棧、隊列添加、刪除數據的位置都是預先設定的。在棧中,被刪除的是最近被插入的元素,棧實現的是一種先進后出的策略。而隊列中,被刪去的總是在集合中存在時間最長的元素,隊列實現的是一種先進先出的策略。

棧和隊列是非常有用的數據結構,在計算機中很多的地方使用了棧、隊列的思想。函數執行的壓棧及出棧,消息隊列的使用等等。本文最后將介紹棧和隊列的常見使用場景,遞歸轉化。

棧的常用操作為以下四種

  • push,在棧頂插入一個元素
  • pop,彈出棧頂的元素
  • peek,查看棧頂元素
  • getSize,查看棧內元素的個數

本文中棧由數組實現。

由于入棧和出棧的操作都只集中在棧頂,所以棧實現中,必須定義變量mTop,用于標記當前棧頂位置。

插入操作,在mTop位置上插入新元素,mTop自增即可。

出棧操作,返回mTop自減后的位置的元素。

獲取棧內元素個數,即為mTop值大小。

private int mTop = 0;
private static final int MAX = 20;
private Object[] mArray = null;

public DemoStack(){
    mTop = 0;
    mArray = new Object[MAX];
}

public int getSize(){
    return mTop;
}

public boolean push(T e){
    if (mTop < MAX) {
        mArray[mTop] = e;
        mTop ++;
        return true;
    }
    System.out.println("stack is full");
    return false;
}

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

public T pop(){
    if (mTop == 0) {
        System.out.println("stack is empty");
        return null;
    }
    T e = (T) mArray[--mTop];
    return e;
}

public T peek(){
    int index = mTop - 1;
    return (T) mArray[index];
}

棧的實現比較簡單,但棧的用處非常大。

在java虛擬機中,函數的調用,會生成一個個函數調用棧,等函數執行完畢,得到函數的返回值,出棧,回到之前調用函數的地方。遞歸能簡便解決非常多的問題,但遞歸有個最大的問題就是執行效率不高,因為函數調用的次數太多了。結合java虛擬機的函數調用思想,可否優化遞歸呢?因為遞歸本質上是將某一個狀態的結果保存在棧中,再計算另外一個狀態。如果不用遞歸,也可以使用棧數據結構來保存部分結果。

棧的使用場景之一,就是遞歸轉化,使用棧保存部分結果,取結果再計算下一個狀態的結果。二叉樹的遍歷分為前序遍歷,中序遍歷,后序遍歷,遞歸實現非常簡單,也可以使用棧將遞歸轉化。

前序遍歷非常簡單,是三種遍歷中轉化最容易的一種。

  public void preTraverseByStack(Node root) {
    if (root == null) {
        return;
    }
    DemoStack<Node> stack = new DemoStack<>();
    stack.push(root);
    Node temp = null;
    while (!stack.isEmpty()) {
        temp = stack.pop();
        System.out.print(temp + "   ");
        if (temp.hasRightChild()) {
            stack.push(temp.getRightChild());
        }
        if (temp.hasLeftChild()) {
            stack.push(temp.getLeftChild());
        }
    }
    System.out.println();
}

中序遍歷開始有一定困難了,中序遍歷必須先遍歷左子樹,所以需要找到最左最下方的葉子結點。如果棧中元素值為空,則說明此元素的子元素已經被遍歷過了或者不需要處理了。

  public void middleTraverseByStack(Node root){
    if (root == null) {
        return;
    }
    DemoStack<Node> stack = new DemoStack<>();
    stack.push(root);
    Node temp = null;
    while (!stack.isEmpty()) {
        while (stack.peek() != null && stack.peek().hasLeftChild()) {
            stack.push(stack.peek().getLeftChild());
        }
        if (stack.peek() == null) {
            stack.pop();
        }
        if (!stack.isEmpty()) {
            temp = stack.pop();
            System.out.print(temp + "   ");
            stack.push(temp.getRightChild());
        }
    }
    System.out.println();
}

后序遍歷是三種轉化中最難的,它需要先遍歷左右子樹才行,因為需要使用兩個臨時變化,記錄之前遍歷的結點和當前結點。

  public void lastTraverseByStack(Node root){
    if (root == null) {
        return;
    }
    DemoStack<Node> stack = new DemoStack<>();
    stack.push(root);
    Node cur = null;
    Node pre = null;
    while (!stack.isEmpty()) {
        cur = stack.peek();
        if ((cur.getLeftChild() == null && cur.getRightChild() == null)
                || (pre != null && (pre == cur.getLeftChild() || pre == cur.getRightChild()))) {
            System.out.print(cur + "   ");
            stack.pop();
            pre = cur;
        }else {
            if (cur.hasRightChild()) {
                stack.push(cur.getRightChild());
            }
            if (cur.hasLeftChild()) {
                stack.push(cur.getLeftChild());
            }
        }
        
    }
}

棧的使用場景還有很多,例如四則運算等等。在具體場景中,只要采用了合適的數據結構,事半功倍!

隊列

隊列的常用操作為:

  • enqueue,入隊,在隊列頭部插入元素
  • dequeue,出隊,在隊列尾部刪除元素
  • getSize,獲取隊列元素個數

因為隊列需要在隊列頭部和尾部進行元素操作,所以需要兩個指針,一個指向頭部,一個指向尾部。

隊列比棧復雜,隊列涉及循環問題。如果使用隊列裝滿元素,再刪除所有元素,此時再添加新的元素,雖然隊列的mTop值已經是最大了,但尾部仍有空間,需要需要在尾部添加元素。導致mTop值會比mTail值小。

private int mTop;
private int mTail;
private static final int MAX = 10;
private Object[] mArray;
private int mLength = 0;

public DemoQueue(){
    mTop = mTail = 0;
    mArray = new Object[MAX];
    mLength = 0;
}

public int getSize(){
    return mLength;
}

public boolean enqueue(T e){
    if (getSize() == MAX) {
        System.out.println("queue is full");
        return false;
    }
    if (mTop == MAX) {
        mTop -= MAX;
    }
    mArray[mTop] = e;
    mTop ++;
    mLength++;
    return true;
}

public T dequeue(){
    if (getSize() == 0) {
        System.out.println("queue is empty");
        return null;
    }
    int index = mTail;
    mTail++;
    if (mTail == MAX) {
        mTail -= MAX;
    }
    mLength--;
    return (T) mArray[index];
}

隊列和棧一樣是非常重要的數據結構,在日常開發中常常用到消息隊列,可能會接到某個需要,不允許漏掉任何一個消息,此時就可以使用隊列完成需求。本例中,使用隊列遍歷二叉樹,且是從上到下從左到右的水平遍歷。

  public void traverse(Node root){
    if (root == null) {
        return;
    }
    DemoQueue<Node> queue = new DemoQueue<>();
    queue.enqueue(root);
    Node temp = null;
    while (queue.getSize() > 0) {
        temp = queue.dequeue();
        System.out.print(temp + "   ");
        if (temp.hasLeftChild()) {
            queue.enqueue(temp.getLeftChild());
        }
        if (temp.hasRightChild()) {
            queue.enqueue(temp.getRightChild());
        }
    }
    System.out.println();
}

本文中所有代碼均已上傳到本人的github空間,歡迎訪問。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 棧 棧的英文單詞是Stack,它代表一種特殊的線性表,這種線性表只能在固定一端(通常認為是線性表的尾端)進行插入,...
    Jack921閱讀 1,521評論 0 5
  • 一、棧 1.1 棧的實現 棧(Stack)是限制僅在表的一端進行插入和刪除運算的線性表。java沒有棧這樣的數據結...
    yjaal閱讀 1,470評論 0 1
  • 二、棧和隊列 棧和隊列都是線性結構,它們是操作受限的線性表,即它們的操作是線性表操作的子集。因此也可以用線性表在某...
    MinoyJet閱讀 462評論 0 1
  • 前言 棧和隊列作為兩種典型的線性表,有著非常鮮明甚至可以說是相互對立的特點;棧先進后出(后進先出),隊列先進先出(...
    IAM四十二閱讀 669評論 0 2
  • 引言:2005年開始接觸身心靈成長,讀了很多這方面的書,雞湯也灌了不少,濾干凈雜質,精選10本,分享一點心得給大家...
    曉曉彩兒閱讀 7,337評論 8 13