寫在前面的話:
一枚自學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)咨詢.
