下壓棧(LIFO)詳解


寫在前面的話:

一枚自學Java和算法的工科妹子。

  • 算法學習書目:算法(第四版) Robert Sedgewick
  • 算法視頻教程:Coursera  Algorithms Part1&2

本文是根據《算法(第四版)》的個人總結,如有錯誤,請批評指正。

 

一、下壓棧的定義

下壓棧(簡稱棧)是一種基於后進先出(LIFO)策略的集合類型。棧只有一個出口,允許新增元素(只能在棧頂上增加)、移出元素(只能移出棧頂元素)等操作。

當用例使用foreach語句迭代遍歷棧中的元素時,元素的處理順序和它們被壓人棧中的順序正好相反。

 

二、下壓棧的實現

1.定容棧(數組實現)

import java.util.Iterator;
import java.util.NoSuchElementException;

public class FixedCapacityStack<Item> implements Iterable<Item> {
    private Item[] a;    // holds the items
    private int N;       // number of items in stack

    // create an empty stack with given capacity
    public FixedCapacityStack(int capacity) {
        a = (Item[]) new Object[capacity];   // no generic array creation
        N = 0;
    }

    public boolean isEmpty()          {  return N == 0;                    }
    public void push(Item item)       {  a[N++] = item;                    }
    public Item pop()                 {  return a[--N];                    }
    
public Iterator<Item> iterator() { return new ReverseArrayIterator(); } public class ReverseArrayIterator implements Iterator<Item> { private int i = N-1; public boolean hasNext() { return i >= 0; } public Item next() { if (!hasNext()) throw new NoSuchElementException(); return a[i--]; } public void remove() { throw new UnsupportedOperationException(); } } public static void main(String[] args) { int max = Integer.parseInt(args[0]); FixedCapacityStack<String> stack = new FixedCapacityStack<String>(max); while (!StdIn.isEmpty()) { String item = StdIn.readString(); if (!item.equals("-")) stack.push(item); else if (stack.isEmpty()) StdOut.println("BAD INPUT"); else StdOut.print(stack.pop() + " "); } StdOut.println(); // print what's left on the stack StdOut.print("Left on stack: "); for (String s : stack) { StdOut.print(s + " "); } StdOut.println(); } }

 

2.動態調整大小的棧(數組實現)

import java.util.Iterator;
import java.util.NoSuchElementException;

public class ResizingArrayStack<Item> implements Iterable<Item> {
    private Item[] a;         // 聲明數組
    private int n;            // 棧中元素數量

    public ResizingArrayStack() {
        a = (Item[]) new Object[2];
        n = 0;
    }

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

    public int size() {
        return n;
    }

    // 將棧移動到一個大小為max的新數組
    private void resize(int capacity) {
        assert capacity >= n;

        Item[] temp = (Item[]) new Object[capacity];
        for (int i = 0; i < n; i++) {
            temp[i] = a[i];
        }
        a = temp;
    }


    public void push(Item item) {
        if (n == a.length) resize(2*a.length);    // 若數組已滿,則調整數組大小為原先的兩倍
        a[n++] = item;                            
    }

    public Item pop() {
        if (isEmpty()) throw new NoSuchElementException("Stack underflow");
        Item item = a[n-1];
        a[n-1] = null;                              // 避免對象游離
        n--;
        // 若棧的元素個數只有數組大小的1/4,則調整數組大小為原先的1/2
        if (n > 0 && n == a.length/4) resize(a.length/2);
        return item;
    }

    public Item peek() {
        if (isEmpty()) throw new NoSuchElementException("Stack underflow");
        return a[n-1];
    }


    public Iterator<Item> iterator() {
        return new ReverseArrayIterator();
    }

    private class ReverseArrayIterator implements Iterator<Item> {
        private int i;

        public ReverseArrayIterator() {
            i = n-1;
        }

        public boolean hasNext() {
            return i >= 0;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        public Item next() {
            if (!hasNext()) throw new NoSuchElementException();
            return a[i--];
        }
    }

    public static void main(String[] args) {
        ResizingArrayStack<String> stack = new ResizingArrayStack<String>();
        while (!StdIn.isEmpty()) {
            String item = StdIn.readString();
            if (!item.equals("-")) stack.push(item);
            else if (!stack.isEmpty()) StdOut.print(stack.pop() + " ");
        }
        StdOut.println("(" + stack.size() + " left on stack)");
    }
}

解釋對象游離:

  • 對象游離概念:保存了一個不需要的對象的引用。
  • 如何避免對象游離:Java的垃圾回收機制是回收所有無法被訪問的對象的內存。在我們對pop()的實現中,被彈出的元素的引用依然存在與數組中,使得即使該元素被使用完后也無法將其回收。所以需要將數組中的這個引用覆蓋來避免對象游離,只要將被彈出的數組位置的元素值設置為null,就可以覆蓋無用的引用,並使系統使用完被彈出的元素后回收它的內存。

 

3.鏈表實現

import java.util.Iterator;
import java.util.NoSuchElementException;

public class Stack<Item> implements Iterable<Item> {
    private Node<Item> first;     // 棧頂(最先添加的元素)
    private int n;                // 元素數量

    // 定義鏈表結點的內部類
    private static class Node<Item> {
        private Item item;
        private Node<Item> next;
    }

    public Stack() {
        first = null;
        n = 0;
    }

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

    public int size() {
        return n;
    }

   // 在表頭插入節點
    public void push(Item item) {
        Node<Item> oldfirst = first;
        first = new Node<Item>();
        first.item = item;
        first.next = oldfirst;
        n++;
    }

   // 在表頭刪除節點
    public Item pop() {
        if (isEmpty()) throw new NoSuchElementException("Stack underflow");
        Item item = first.item;        // save item to return
        first = first.next;            // delete first node
        n--;
        return item;                   // return the saved item
    }

    public Item peek() {
        if (isEmpty()) throw new NoSuchElementException("Stack underflow");
        return first.item;
    }
  
    public String toString() {
        StringBuilder s = new StringBuilder();
        for (Item item : this) {
            s.append(item);
            s.append(' ');
        }
        return s.toString();
    }       

    public Iterator<Item> iterator() {
        return new ListIterator<Item>(first);
    }

    private class ListIterator<Item> implements Iterator<Item> {
        private Node<Item> current;

        public ListIterator(Node<Item> first) {
            current = first;
        }

        public boolean hasNext() {
            return current != null;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        public Item next() {
            if (!hasNext()) throw new NoSuchElementException();
            Item item = current.item;
            current = current.next; 
            return item;
        }
    }

    public static void main(String[] args) {
        Stack<String> stack = new Stack<String>();
        while (!StdIn.isEmpty()) {
            String item = StdIn.readString();
            if (!item.equals("-"))
                stack.push(item);
            else if (!stack.isEmpty())
                StdOut.print(stack.pop() + " ");
        }
        StdOut.println("(" + stack.size() + " left on stack)");
    }
}

 

三、下壓棧不同實現方式的比較--Resizing Array (可調整大小的數組) VS. Linked List(鏈表)

1.Resizing Array (可調整大小的數組)

  • 構造含有N(N是2的冪)個元素的棧,數據結構初識為空,N次連續的push()調用需要訪問數組元素的次數:N+(4+8+......+2N)=5N-4,前面的N表示添加N個元素時push()中a[n++] = item訪問數組N次,后面(4+8+......+2N)是push()中每次數組長度加倍時,resize()方法初始化數據結構需要訪問數組次數的總和。因此,每次操作訪問數組的平均次數為常數,但最后一次操作所需的時間是線性的。這是一種均攤分析,將少量昂貴操作的成本通過各種大量廉價操作平攤。但是這種方式在許多場景下不適用,當遇到需要增加(或減少)數組大小時,導致push()壓棧(或出棧)的耗時很長;
  • 占用總的時間和內存相對較少,數組內存24+xN(x根據Item類型而定,整數4個字節)。

2.Linked List(鏈表)

  • 每次操作(出棧和壓棧)都是常數時間,操作的速度平穩;
  • 占用總的時間和內存比較大,為了維持連接,每個Stack Node需要40個字節的內存。

 

四、下壓棧的應用

實例:Dijkstra的雙棧算數表達式求值算法

  • 將操作數壓入操作數棧;
  • 將運算符壓入運算符棧;
  • 忽略左括號;
  • 在遇到右括號時,彈出一個運算符,彈出所需數量的操作數,並將運算符和操作數的運算結果壓入操作數棧。
public class Evaluate {
    public static void main(String[] args) { 
        Stack<String> ops  = new Stack<String>();
        Stack<Double> vals = new Stack<Double>();

        while (!StdIn.isEmpty()) {
            String s = StdIn.readString();
            if      (s.equals("("))               ;
            else if (s.equals("+"))    ops.push(s);
            else if (s.equals("-"))    ops.push(s);
            else if (s.equals("*"))    ops.push(s);
            else if (s.equals("/"))    ops.push(s);
            else if (s.equals("sqrt")) ops.push(s);
            else if (s.equals(")")) {
                String op = ops.pop();
                double v = vals.pop();
                if      (op.equals("+"))    v = vals.pop() + v;
                else if (op.equals("-"))    v = vals.pop() - v;
                else if (op.equals("*"))    v = vals.pop() * v;
                else if (op.equals("/"))    v = vals.pop() / v;
                else if (op.equals("sqrt")) v = Math.sqrt(v);
                vals.push(v);
            }
            else vals.push(Double.parseDouble(s));
        }
        StdOut.println(vals.pop());
    }
}

 

  

作者: 鄒珍珍(Pearl_zhen)

出處: http://www.cnblogs.com/zouzz/

聲明:本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出 原文鏈接 如有問題, 可郵件(zouzhenzhen@seu.edu.cn)咨詢.


免責聲明!

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



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