棧只有兩個操作:入棧(壓棧)和出棧
陣列模擬棧:
public class MyStack<E> {
private int size = 0;
private Object[] elementData;
public MyStack() {
elementData = new Object[10];
}
//入棧
public void push(E item) {
//判斷是否需要擴容
if (size - elementData.length >= 0) {
grow();
}
elementData[size++] = item;
}
// 檢視此堆疊頂部的物件,而不從堆疊中刪除它。
public E peek() {
if(size == 0) {
return null;
}
return (E) elementData[size - 1];
}
//出棧:刪除此堆疊頂部的物件,並將該物件返回。
public E pop() {
Object result = peek();
elementData[size - 1] = null;
size--;
return (E) result;
}
//判斷堆疊是否爲空
public boolean isEmpty() {
return size == 0;
}
//獲取堆疊的有效數據
public int size() {
return size;
}
private void grow() {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
elementData = Arrays.copyOf(elementData, newCapacity);
}
//所有元素全部出棧並封裝在List中
public List<E> allPop() {
List<E> result = new ArrayList<E>();
for(int index = size - 1; index >= 0; index--) {
result.add((E) elementData[index]);
}
return result;
}
}
只可以操作正整數的加、減、乘、除運算。
注意在最後的計算中要保證操作符中棧頂的優先順序高於棧底的優先順序。 因爲永遠是先進行優先順序高的運算。
下面 下麪用剛纔實現好的計算器來模擬一下這個簡易的計算器:
public class MyCounter {
//用map來儲存運算子對應的優先順序
private static final Map<Character, Integer> priorityMap = new HashMap<>();
static {
priorityMap.put('+', 0);
priorityMap.put('-', 0);
priorityMap.put('*', 1);
priorityMap.put('/', 1);
}
//四則運算
private static int count(char c, int num1, int num2) {
int result = 0;
switch (c) {
case '*': result = num1 * num2; break;
case '/': result = num1 / num2; break;
case '+':result = num1 + num2; break;
case '-': result = num1 - num2; break;
}
return result;
}
//優先順序比較:0表示同等優先順序 1表示c1優先順序大於c2 -1表示c1優先順序小於c2
private static int comparePriority(char c1, char c2) {
int pc1 = priorityMap.get(c1);
int pc2 = priorityMap.get(c2);
return pc1 > pc2 ? 1 : (pc1 == pc2 ? 0 : -1);
}
//判斷是否爲操作符
private static boolean isOperator(char c) {
return c =='+' || c =='-' || c == '*' || c == '/';
}
//判斷是否爲運算元
private static boolean isNum(char c) {
return c >= '0' && c <= '9';
}
private static void dealOperator(MyStack<Integer> numStack, MyStack<Character> operatorStack, char c) {
if(operatorStack.isEmpty()) {
operatorStack.push(c);
return;
}
char c1 = operatorStack.peek();
int flag = 0;
//如果當前的操作符的優先順序小於或者等於堆疊中的操作符
if((flag = comparePriority(c, c1)) == 0 || flag == -1) {
int num1 = numStack.pop();
int num2 = numStack.pop();
int result = count(c1, num2, num1);
System.out.println(num2 + " " + c1 + " " + num1 + "的結果:" + result);
numStack.push(result);
operatorStack.pop();
operatorStack.push(c);
}else {
operatorStack.push(c);
}
}
//最後的計算操作
private static int dealLast(MyStack<Integer> numStack, MyStack<Character> operatorStack) {
while(!operatorStack.isEmpty()) {
char c = operatorStack.pop();
int num1 = numStack.pop();
int num2 = numStack.pop();
int result = count(c, num2, num1);
System.out.println(num2 + " " + c + " " + num1 + "的結果:" + result);
numStack.push(result);
}
return numStack.pop();
}
public static int executeCounter(String str) {
MyStack<Integer> numStack = new MyStack<Integer>();
MyStack<Character> operatorStack = new MyStack<Character>();
String numStr = "";
int len = str.length();
for(int index = 0; index < len; index++) {
char c = str.charAt(index);
if(isNum(c)) { //處理運算元
numStr += c;
//如果是運算元還需要往後面看一位,因爲可能是多位數。但是必須保證下標不越界
if(index < len - 1 && !isOperator(str.charAt(index + 1))) {
//如果不會越界而且不是操作符,那就拼接數位繼續掃描
continue;
}
//否則---運算元入棧,並且重置字串
numStack.push(Integer.parseInt(numStr));
numStr = "";
}else if(isOperator(c)) { //處理運算子
dealOperator(numStack, operatorStack, c);
}else {
throw new RuntimeException("非法字元");
}
}
//處理最後的計算
return dealLast(numStack, operatorStack);
}
}
測試:
public static void main(String[] args) {
String str = "5+70*2*2-5+1-5+3-4";
System.out.println("計算結果:" + MyCounter.executeCounter(str));
}
結果:
上述計算過程只有一個核心點:就是遇到一個運算子,先比較其與棧頂運算子的優先順序,如果前者低則計算,如果前者高則直接入棧,這保證了我們執行最終計算的時候是按照運算子優先順序由高到低的順序進行的;如果二者相等則計算,這保證優先順序相等的情況下按順序計算。
字首表達式(又稱波蘭式):其實就是運算子位於運算元之前。
字首表達式的計算機求值:從右至左掃描表達式,遇到數位時,將數位壓入堆疊,遇到運算子時,彈出棧頂的兩個數,用運算子對它們做相應的計算(棧頂元素和次頂元素),並將結果入棧;重複上述過程直到表達式最左端,最後運算得出的值即爲表達式的結果。例如:(3+4)*5-6對應的字首表達式是 - * + 3 4 5 6
中綴表達式:
中綴表達式就是常見的運算表達式,其實就是運算子位於兩運算數的中間,如(從左至右掃描):(3+4)*5-6 的中綴表達式爲 3 + 4 * 5 - 6
中綴表達式的求值是我們人最熟悉的,但是對計算機來說卻不好操作,因此,在計算結果時,往往會將中綴表達式轉成其它表達式來操作(一般轉成後綴表達式)
後綴表達式(逆波蘭表達式):運算子位於運算元之後
後綴表達式的計算機求值從左至右掃描表達式,遇到數位時,將數位壓入堆疊,遇到運算子時,彈出棧頂的兩個數,用運算子對它們做相應的計算(次頂元素和棧頂元素),並將結果入棧;重複上述過程直到表達式最右端,最後運算得出的值即爲表達式的結果
例如: (3+4)*5-6對應的後綴表達式就是3 4 + 5 * 6 - ,針對後綴表達式求值步驟如下:
我們在對後綴表達式求值時是非常容易的,只不過我們常人書寫的都是中綴表達式,所以人們提出了將中綴表達式轉爲後綴表達式的演算法(將中綴表達式轉換爲後綴表達式纔是重點)。這個演算法的具體描述如下:
如果死記硬背那就扯淡了。不過通過閱讀人家設計出來的演算法,可以看出最終s1爲空,s2出棧的逆序爲逆波蘭表達式。我們對後綴表達式的操作是,遇到運算子之後彈出兩個運算元,執行運算計算出結果再壓棧,重複這種行爲,故可以看出:後綴表達式的每個運算子前面都要最起碼保證至少有兩個運算元。
可以看到在中綴轉後綴的時候總是從s1中往s2中「倒騰」東西,結合我們小學時候學的計算原則是先執行括號中的計算,然後按優先順序由高到低的順序先後 先後計算,如果運算級相同則按書寫順序計算。正是基於計算原則,所以他才需要這樣「倒騰」。其實我認爲可以分爲四種情形:
一箇中綴表達式轉爲後綴表達式的示意圖:
只支援正整數運算。
其實跟上面的簡易計算器是一樣道理,只不過這個是先將中綴表達式轉換爲後綴表達式,然後再操作後綴表達式計算結果。
public class AgainstPoland {
private static final Map<Character, Integer> priorityMap = new HashMap<>();
static {
priorityMap.put('+', 0);
priorityMap.put('-', 0);
priorityMap.put('*', 1);
priorityMap.put('/', 1);
}
private static int comparePriority(char c1, char c2) {
int pc1 = priorityMap.get(c1);
int pc2 = priorityMap.get(c2);
return pc1 > pc2 ? 1 : (pc1 == pc2 ? 0 : -1);
}
private static boolean isOperator(char c) {
return c =='+' || c =='-' || c == '*' || c == '/';
}
private static boolean isNum(char c) {
return c >= '0' && c <= '9';
}
private static int count(char c, int num1, int num2) {
int result = 0;
switch (c) {
case '*': result = num1 * num2; break;
case '/': result = num1 / num2; break;
case '+':result = num1 + num2; break;
case '-': result = num1 - num2; break;
}
return result;
}
//中綴表達式轉換爲後綴表達式
public static String[] toInfixExpression(String str) {
MyStack<Character> s1 = new MyStack<Character>();
MyStack<String> s2 = new MyStack<String>();
int len = str.length();
String numStr = "";
for(int index = 0; index < len; index++) {
char c = str.charAt(index);
if(isNum(c)) {
numStr += c;
if(index < len - 1 && isNum(str.charAt(index + 1))) {
continue;
}
s2.push(numStr);
numStr = "";
}else if(isOperator(c)) {
dealOperator(s1, s2, c);
}else if(c == '(') {
s1.push(c);
}else if(c == ')') {
dealRightBracket(s1, s2);
}else {
throw new RuntimeException("非法字元");
}
}
//掃描完畢之後將s1中的運算子全部出棧並壓入到s2中
return dealLast(s1,s2);
}
private static void dealRightBracket(MyStack<Character> s1, MyStack<String> s2) {
//s1一直出棧並壓入到s2中,直到匹配到第一個‘(’
Character cTop;
while((cTop = s1.pop()) != '(') {
s2.push(cTop.toString());
}
}
private static void dealOperator(MyStack<Character> s1, MyStack<String> s2,char c) {
//如果棧爲空或者當前元素的優先順序高於棧頂運算子的優先順序-----直接入棧
if(s1.isEmpty() || s1.peek() =='(' || (comparePriority(c, s1.peek()) == 1)) {
s1.push(c);
}else {
/**
* 否則,就說明運算元棧不爲空,且當前元素的優先順序小於或等於棧頂運算子的優先順序
* 將s1中的棧頂運算子出棧並壓入s2中,再遞回比較
*/
s2.push(s1.pop().toString());
dealOperator(s1, s2, c);
}
}
private static String[] dealLast(MyStack<Character> s1, MyStack<String> s2) {
while(!s1.isEmpty()) {
s2.push(s1.pop().toString());
}
int size = s2.size();
String[] sArray = new String[size];
while(!s2.isEmpty()) {
sArray[--size] = s2.pop();
}
return sArray;
}
public static int executeCounter(String str) {
String[] sArray = toInfixExpression(str);
//後綴表達式求值
MyStack<Integer> tmpStack = new MyStack<Integer>();
String s;
char c;
int num1, num2;
for (int index = 0; index < sArray.length; index++) {
s = sArray[index];
c = s.charAt(0);
if(isOperator(c)) {
num1 = tmpStack.pop();
num2 = tmpStack.pop();
tmpStack.push(count(c, num2, num1));
continue;
}
tmpStack.push(Integer.parseInt(s));
}
return tmpStack.pop();
}
}
測試:
public static void main(String[] args) {
String str = "101+((21+3)*4)-5";
System.out.println(executeCounter(str));
}
輸出192