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(intx){ 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; } //棧是否滿了 publicboolean 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)
棧的操作與棧中資料個數無關,因此也不需要比較和移動操作。