3.棧

1.棧的一個實際需求

在這裏插入圖片描述
請問計算機底層是如何運算得到結果的?計算機怎麼理解這個算式的(對計算機而言,它接收到的就是一個字符串)。

2.棧的介紹

1.棧(Stack)是一個先入後出(FILO-First In Last Out)的有序列表。
2.棧(stack)是限制線性表中元素的插入和刪除只能在線性表的同一端進行的一種特殊線性表。允許插入和刪除的一端爲變化的一端,稱爲棧頂(Top),另一端爲固定的一端,稱爲棧底(Bottom)。
3.根據棧的定義可知,最先放入棧中元素在棧底,最後放入的元素在棧頂,而刪除元素剛好相反,最後放入的元素最先刪除,最先放入的元素最後刪除
4.圖解方式說明出棧(pop)和入棧(push)的概念
在這裏插入圖片描述

3.棧的實現

1.用數組模擬棧的使用,由於棧是一種有序列表,所以可以使用數組的結構來儲存棧的數據內容:

package stack;

public class ArrayStack {
    private int maxSize;//棧的容量
    private int[] stack;//存放數據的數組
    private int top = -1;//棧頂,初始化爲-1

    /**
     * 構造器
     * @param maxSize
     */
    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        this.stack = new int[maxSize];
    }

    /**
     * 入棧
     * @param value
     */
    public void push(int value){
        if (isFull()){
            throw new RuntimeException("棧滿");
        }
        top++;
        stack[top] = value;
    }

    /**
     * 出棧
     * @return
     */
    public int pop(){
        if (isEmpty()){
            throw new RuntimeException("棧空");
        }
        int value = stack[top];
        top--;
        return value;
    }

    /**
     * 從棧頂開始遍歷數據
     */
    public void show(){
        if (isEmpty()){
            throw new RuntimeException("棧空");
        }
        int cur = top;
        while (cur!=-1){
            System.out.println(stack[cur--]);
        }
    }

    public boolean isFull(){
        return this.top == this.maxSize - 1;
    }

    public boolean isEmpty(){
        return this.top == -1;
    }

    public static void main(String[] args) {
        ArrayStack stack = new ArrayStack(5);
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        stack.show();
        System.out.println("出棧:"+stack.pop());
        stack.show();
    }

}

2.如何使用鏈表來模擬棧呢?

熟悉鏈表操作的話應該容易想到:每次入棧,即向單向鏈表中添加節點時,直接添加到鏈表最前面,達到最後入棧的在鏈表中最先訪問的目的。有關鏈表相關操作,可參考我的另一篇記錄:4.鏈表

4.棧實現綜合計算器

在實現之前,首先理解一下前綴表達式、中綴表達式、後綴表達式的概念:

1.前綴表達式又稱波蘭式,前綴表達式的運算符位於操作數之前。比如:- × + 3 4 5 6;
2.中綴表達式就是常見的運算表達式,如:(3+4)×5-6;
3.後綴表達式又稱逆波蘭表達式,與前綴表達式相似,只是運算符位於操作數之後,比如:3 4 + 5 × 6 - 。

人類最熟悉的一種表達式1+2,(1+2) ×3,3+4+5等都是中綴表示法。對於人們來說,也是最直觀的一種求值方式,先算括號裏的,然後算乘除,最後算加減。但是,計算機處理中綴表達式卻並不方便。而逆波蘭表達式在編譯技術中有着普遍的應用,因此在開發中,我們一般需要將中綴表達式轉成後綴表達式

中綴表達式轉後綴表達式主要用到了棧進行運算符處理和排序輸出:

  1. 初始化兩個棧:運算符棧 s1 和儲存中間結果的棧 s2;
  2. 從左至右掃描中綴表達式;
  3. 遇到操作數時,直接將其壓入 s2;
  4. 遇到運算符時,比較其與 s1 棧頂運算符的優先級:
    ① 如果 s1 棧空,則直接將此運算符入棧;
    ② 若優先級比棧頂運算符的高,也將運算符壓入 s1;
    ③ 否則,將 s1 棧頂的運算符彈出並壓入到 s2 中,並再與 s1 中新的棧頂運算符相比較,直到運算符優先級小於棧頂元素優先級後,操作符再入棧;
  5. 操作符是 ( 則無條件入棧s1;
  6. 操作符爲 ),則將s1依次出棧後入棧到s2,直到匹配到第一個(爲止,此操作符直接捨棄,(直接出棧捨棄;
  7. 將 s1 中剩餘的運算符依次彈出並壓入 s2;
  8. 依次彈出 s2 中的元素並輸出,結果的逆序即爲中綴表達式對應的後綴表達式。

對於上述算法過程,示例演示將中綴表達式1+((2+3)*4)-5轉換爲後綴表達式的過程如下:
在這裏插入圖片描述
因此結果爲 :1 2 3 + 4 × + 5 –

代碼實現(java):

package stack;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;

/**
 * 支持 + - * / ( )
 * 支持多位數,支持小數,
 * 兼容處理, 過濾任何空白字符,包括空格、製表符、換頁符
 */
public class Calculator {

	/**
	 * 匹配 + - * / ( ) 運算符
	 */
	static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";

	static final String LEFT = "(";
	static final String RIGHT = ")";
	static final String ADD = "+";
	static final String MINUS= "-";
	static final String TIMES = "*";
	static final String DIVISION = "/";

	/**
	 * 加減 + -
	 */
	static final int LEVEL_01 = 1;
	/**
	 * 乘除 * /
	 */
	static final int LEVEL_02 = 2;

	/**
	 * 括號
	 */
	static final int LEVEL_HIGH = Integer.MAX_VALUE;


	static Stack<String> stack = new Stack<>();
	static List<String> data = Collections.synchronizedList(new ArrayList<String>());

	/**
	 * 去除所有空白符
	 * @param s
	 * @return
	 */
	public static String replaceAllBlank(String s ){
		// \\s+ 匹配任何空白字符,包括空格、製表符、換頁符等等, 等價於[ \f\n\r\t\v]
		return s.replaceAll("\\s+","");
	}

	/**
	 * 判斷是不是數字 int double long float
	 * @param s
	 * @return
	 */
	public static boolean isNumber(String s){
		Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");
		return pattern.matcher(s).matches();
	}

	/**
	 * 判斷是不是運算符
	 * @param s
	 * @return
	 */
	public static boolean isSymbol(String s){
		return s.matches(SYMBOL);
	}

	/**
	 * 匹配運算等級
	 * @param s
	 * @return
	 */
	public static int calcLevel(String s){
		if("+".equals(s) || "-".equals(s)){
			return LEVEL_01;
		} else if("*".equals(s) || "/".equals(s)){
			return LEVEL_02;
		}
		return LEVEL_HIGH;
	}

	/**
	 * 匹配
	 * @param s
	 * @throws Exception
	 */
	public static List<String> doMatch (String s) throws Exception{
		if(s == null || "".equals(s.trim())) throw new RuntimeException("data is empty");
		if(!isNumber(s.charAt(0)+"")) throw new RuntimeException("data illeagle,start not with a number");

		s = replaceAllBlank(s);

		String each;
		int start = 0;

		for (int i = 0; i < s.length(); i++) {
			if(isSymbol(s.charAt(i)+"")){
				each = s.charAt(i)+"";
				//棧爲空,(操作符,或者 操作符優先級大於棧頂優先級 && 操作符優先級不是( )的優先級 及是 ) 不能直接入棧
				if(stack.isEmpty() || LEFT.equals(each)
						|| ((calcLevel(each) > calcLevel(stack.peek())) && calcLevel(each) < LEVEL_HIGH)){
					stack.push(each);
				}else if( !stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())){
					//棧非空,操作符優先級小於等於棧頂優先級時出棧入列,直到棧爲空,或者遇到了(,最後操作符入棧
					while (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek()) ){
						if(calcLevel(stack.peek()) == LEVEL_HIGH){
							break;
						}
						data.add(stack.pop());
					}
					stack.push(each);
				}else if(RIGHT.equals(each)){
					// ) 操作符,依次出棧入列直到空棧或者遇到了第一個)操作符,此時)出棧
					while (!stack.isEmpty() && LEVEL_HIGH >= calcLevel(stack.peek())){
						if(LEVEL_HIGH == calcLevel(stack.peek())){
							stack.pop();
							break;
						}
						data.add(stack.pop());
					}
				}
				start = i ;    //前一個運算符的位置
			}else if( i == s.length()-1 || isSymbol(s.charAt(i+1)+"") ){
				each = start == 0 ? s.substring(start,i+1) : s.substring(start+1,i+1);
				if(isNumber(each)) {
					data.add(each);
					continue;
				}
				throw new RuntimeException("data not match number");
			}
		}
		//如果棧裏還有元素,此時元素需要依次出棧入列,可以想象棧裏剩下棧頂爲/,棧底爲+,應該依次出棧入列,可以直接翻轉整個stack 添加到隊列
		Collections.reverse(stack);
		data.addAll(new ArrayList<>(stack));

		System.out.println(data);
		return data;
	}

	/**
	 * 算出結果
	 * @param list
	 * @return
	 */
	public static Double doCalc(List<String> list){
		Double d = 0d;
		if(list == null || list.isEmpty()){
			return null;
		}
		if (list.size() == 1){
			System.out.println(list);
			d = Double.valueOf(list.get(0));
			return d;
		}
		ArrayList<String> list1 = new ArrayList<>();
		for (int i = 0; i < list.size(); i++) {
			list1.add(list.get(i));
			if(isSymbol(list.get(i))){
				Double d1 = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i));
				list1.remove(i);
				list1.remove(i-1);
				list1.set(i-2,d1+"");
				list1.addAll(list.subList(i+1,list.size()));
				break;
			}
		}
		doCalc(list1);
		return d;
	}

	/**
	 * 運算
	 * @param s1
	 * @param s2
	 * @param symbol
	 * @return
	 */
	public static Double doTheMath(String s1,String s2,String symbol){
		Double result ;
		switch (symbol){
			case ADD : result = Double.valueOf(s1) + Double.valueOf(s2); break;
			case MINUS : result = Double.valueOf(s1) - Double.valueOf(s2); break;
			case TIMES : result = Double.valueOf(s1) * Double.valueOf(s2); break;
			case DIVISION : result = Double.valueOf(s1) / Double.valueOf(s2); break;
			default : result = null;
		}
		return result;

	}

	public static void main(String[] args) {
		//String math = "9+(3-1)*3+10/2";
		String math = "12.8 + (2 - 3.55)*4+10/5.0";
		try {
			doCalc(doMatch(math));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

5.棧的其他應用場景:

1.子程序的調用:在跳往子程序前,會先將下個指令的地址存到堆棧中,直到子程序執行完後再將地址取出,以回到原來的程序中。
2.處理遞歸調用:和子程序的調用類似,只是除了儲存下一個指令的地址外,也將參數、區域變量等數據存入堆棧中。
3.表達式的轉換(中綴表達式轉後綴表達式)與求值(實際解決)。
4.二叉樹的遍歷。
5.圖形的深度優先(depth 一 first)搜索法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章