1. 程式人生 > 實用技巧 >Java資料結構與演算法(三)--棧

Java資料結構與演算法(三)--棧

目錄

在第一章我們講了資料這個具體的資料儲存結構,它主要應用與資料的記錄。

本章主要講一個構思演算法的輔助工具--棧,用它來執行特定的任務。

回到頂部

1.棧的基本概念

棧(stack)是一種抽象的資料結構(ADT),它可以用其他的資料結構來實現,比如說前面講過的陣列。

它是一種受限訪問的資料結構,遵循先進後出(FILO)的規則,即最前進入棧的最後出來的原則。

它就像子彈夾一樣,先壓入彈夾的子彈,壓到最下面,最後射出去。

根據棧的特性,在樹的搜尋與深度優先搜尋(DFS)中,棧可以發揮重要的作用

回到頂部

2.棧的簡單實現

public class MyStack {
    //儲存
    private int[] array;
    //棧的頂部
    private int top;
    //最大容量
    private int max;

    public MyStack(int size){
        max = size;
        top = -1;
        array = new int[size];
    }

    //壓入資料
    public void push(int
x){ if((top+1)<max){ array[++top]=x; } } //彈出資料 public int pop(){ return array[top--]; } //獲取棧頂資料 public int peek(){ return array[top]; } //棧是否為空 public boolean isEmpty(){ return top==-1; } //棧是否滿了 public
boolean isFull(){ return top == max-1; } public static void main(String[] args) { MyStack stack = new MyStack(10); stack.push(3); stack.push(2); stack.push(1); System.out.println(stack.peek()); while (!stack.isEmpty()){ System.out.println(stack.pop()); } } }

看下測試結果:

上面我們用陣列實現了一個簡單的棧,用top指向棧頂元素,壓入和彈出只需要移動棧頂指標並判斷是否超過最大容量即可。

不過這個棧有個問題就是,容量有限制,我們必須初始化一個恰當大小的容量,才能完全放置資料並不會浪費容量。

這比較難做到,我們來參考java中stack的實現來優化以下這個棧

回到頂部

3.程式碼解析

我們可以參考publicclass Stack<E> extends Vector<E>這個實現類。實際JDK推薦使用Deque的實現類來實現棧。

這個Stack僅做參考:

 protected Object[] elementData;//使用Object儲存資料
//添加了擴容機制
//建立一個大小為當前容量的2倍的陣列
//將舊的資料移動到新的陣列中
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

在來看下ArrayQueue如何實現棧的:

//ArrayDeque poll的實現方法    
public E pollFirst() {
        int h = head;
        @SuppressWarnings("unchecked")
        E result = (E) elements[h];
        // Element is null if deque empty
        if (result == null)
            return null;
        elements[h] = null;     // Must null out slot
        head = (h + 1) & (elements.length - 1);
        return result;
    }

head = (h + 1) & (elements.length - 1);

一個巧妙的設計,一般情況就是head+1,在head達到陣列末端的時候,將head置0。

回到頂部

4.應用場景

利用棧的特性我們可以用棧達到逆轉字元的作用

//把前面儲存的int[]改成Object[]    
public static void main(String[] args) {
        MyStack stack = new MyStack(20);
        String str = "Hello World!";
        char[] chars = str.toCharArray();
        for(char c: chars){
            stack.push(c);
        }
        while (!stack.isEmpty()){
            System.out.print(stack.pop());
        }
    }

執行結果:

有了前面這個小例子,我們在舉個實現應用的例子。我們可能需要解析一串字元表示的表示式,

通常做法,可以將字串轉變為逆波蘭表示式(字尾表示式)去計算

比如將 (a+b)*(c+d) 轉變為 ab+cd+*

只需兩兩進行簡單計算即可

/**
     * 轉變為逆波蘭表示式
     * @param strList
     * @return
     */
    public List<String> initRPN(List<String> strList){
        List<String> returnList = new ArrayList<String>();
        //用來存放操作符的棧
        Stack stack = new Stack();
//      stack.push(LOWESTSING);
        int length = strList.size();
        for(int i=0;i<length;i++ ){
            String str = strList.get(i);
            if(isNumber(str)){
                returnList.add(str);
            }else{
                if(str.equals(OPSTART)){
                    //'('直接入棧
                    stack.push(str);
                }else if(str.equals(OPEND)){
                    //')'
                    //進行出棧操作,直到棧為空或者遇到第一個左括號   
                    while (!stack.isEmpty()) {   
                        //將棧頂字串做出棧操作   
                        String tempC = stack.pop();   
                        if (!tempC.equals(OPSTART)) {   
                            //如果不是左括號,則將字串直接放到逆波蘭連結串列的最後   
                            returnList.add(tempC);   
                        }else{   
                            //如果是左括號,退出迴圈操作   
                            break;   
                        }   
                    }   
                }else{
                    if (stack.isEmpty()) {
                        //如果棧內為空   
                        //將當前字串直接壓棧   
                        stack.push(str);   
                    }else{
                        //棧不空,比較運算子優先順序順序
                        if(precedence(stack.top())>=precedence(str)){
                            //如果棧頂元素優先順序大於當前元素優先順序則
                            while(!stack.isEmpty() && precedence(stack.top())>=precedence(str)){
                                returnList.add(stack.pop());
                            }
                        }
                        stack.push(str);
                    }
                }
            }
        }
        //如果棧不為空,則將棧中所有元素出棧放到逆波蘭連結串列的最後   
        while (!stack.isEmpty()) {
            returnList.add(stack.pop());
        }
        return returnList;
    }

 /**
* 設定優先順序順序()設定與否無所謂
* @return
*/
private int precedence(String str){
char sign = str.charAt(0);
switch(sign){
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
case '^':
case '%':
return 3;
case '(':
case ')':
// case '#':
default:
return 0;

}
}

/**
* 是否是整數或是浮點數,但預設-05.15這種也認為是正確的格式
* @param str
* @return
*/
private boolean isNumber(String str){
Pattern p = Pattern.compile("^(-?\\d+)(\\.\\d+)?$");
Matcher m = p.matcher(str);
boolean isNumber = m.matches();
return isNumber;
}

以上羅列了部分程式碼,主要程式碼就是遇到普通字元入棧,遇到操作字元,比較操作字元優先順序在進行出棧操作。

回到頂部

5.總結

根據棧先進後出的特性,我們可以實現多種功能。

棧通過提供限制性的訪問方法push()和pop(),使得程式不容易出錯。

棧只對棧頂元素進行操作,出棧和入棧的時間複雜度都為O(1)

棧的操作與棧中資料個數無關,因此也不需要比較和移動操作。