介紹
棧 : 是 一種只允許在一端進行插入,刪除的線性表,具有先進後出的特性。
通常,棧的操作端稱爲 棧頂,另一端稱爲 棧底。棧的插入稱爲 進棧(push), 棧的刪除操作稱爲 出棧(pop)。
棧的存儲結構
既然棧的本質是一種線性表,那麼棧的存儲結構也有兩種:
- 順序存儲結構(順序棧)
- 鏈式存儲結構(鏈式棧)
棧順序存儲結構
棧的順序存儲結構一般使用 數組 實現。數組中的第一個元素作爲棧底,最後一個元素作爲棧頂。
public class ArrayStack<E> {
private int defaultCapacity = 10;
/**
* 存儲元素的容器
*/
private Object[] elements;
/**
* 棧中元素個數
*/
private int size;
/**
* 標示棧頂的變量
*/
private int top;
public ArrayStack() {
elements = new Object[defaultCapacity];
top = -1;
}
public ArrayStack(int capacity) {
elements = new Object[capacity];
top = -1;
}
/**
* 進棧
*
* @param element
* @return
*/
public E push(E element) {
ensureCapacity(size + 1);
elements[size] = element;
size++;
return element;
}
/**
* 出棧
*
* @return
*/
public E pop() {
if (size > 0) {
E element = (E) elements[size - 1];
size--;
return element;
}
throw new IllegalArgumentException("the stack is empty");
}
public boolean empty() {
return size == 0;
}
public int size() {
return size;
}
/**
* 確保容器大小是否可用,是否擴容
*
* @param newSize
*/
private void ensureCapacity(int newSize) {
if (newSize > elements.length) {
increaseCapacity(newSize);
}
}
/**
* 擴大容器大小, 1.5 倍擴容
*/
private void increaseCapacity(int newSize) {
int increasedSize = newSize;
increasedSize = increasedSize + increasedSize >> 1;
try {
elements = Arrays.copyOf(elements, increasedSize);
} catch (OutOfMemoryError error) {
// 擴容失敗
error.printStackTrace();
}
}
public Object[] toArray() {
return Arrays.copyOf(elements, size);
}
}
棧鏈式存儲結構
棧的鏈式結構是在 第一個節點處 插入,刪除節點。因爲如果在最後一個節點處進行插入,刪除,則需要一個一個遍歷獲取到最後一個節點才行。
public class LinkedStack<E> {
private Node<E> head;
private int size;
public LinkedStack() {
head = new Node<>();
}
public E push(E element) {
Node<E> node = new Node<>(element);
node.next = head.next;
head.next = node;
size++;
return element;
}
public boolean empty() {
return size == 0;
}
public E pop() {
if (size > 0) {
Node<E> topNode = head.next;
head.next = topNode.next;
size--;
return topNode.element;
}
throw new IllegalArgumentException("the stack is empty");
}
public int size() {
return size;
}
public Object[] toArray() {
Object[] objects = new Object[size];
Node<E> iterator = head.next;
if (iterator != null) {
int index = 0;
objects[index] = iterator;
while (iterator.next != null) {
iterator = iterator.next;
index++;
objects[index] = iterator;
}
}
return objects;
}
}
棧的應用
進制轉換
十進制數轉換成 N 進制的過程,其實就是把十進制數 除 N 得到的餘數, 餘數是從低位到高位產生,然後從高位到低位輸出即是轉換結果。
比如 十進制 13 轉換成二進制位: 1101
實現代碼:
/**
* 進制轉換
*
* @param number 要轉換的數
* @param n 要轉換稱的進制
* @return
*/
private String numberConvert(int number, int n) {
ArrayStack<String> stack = new ArrayStack<>();
StringBuilder sb = new StringBuilder();
while (number > 0) {
int mod = number % n;
if (mod > 10) {
char c = (char) ('a' + (mod - 10));
String temp = String.valueOf(c);
stack.push(temp);
} else {
stack.push(String.valueOf(mod));
}
number = number / n;
}
while (!stack.empty()) {
String num = stack.pop();
sb.append(num);
}
return sb.toString();
}
括號匹配校驗
表達式中每一個左括號都期待一個相應的右括號與之匹配,表達式中越遲出現,並且沒有得到匹配的左括號期待匹配的程度越高,如果出現的右括號不是期待出現的,則表明匹配不正確。
需要一個棧,在讀入字符過程中,如果是左括號,則直接入棧,如果是右括號且與當前棧頂左括號匹配,則將棧頂左括號出棧; 如果不匹配,則屬於不合法的情況; 如果碰到右括號時,而棧爲空,則說明沒有左括號與之匹配,同樣是非法的。讀入完所有字符,如果棧爲空的,則表達式是合法的。
/**
* 校驗表達式括號匹配是否合法
*
* @param statement
* @return
*/
private boolean checkBrackets(String statement) {
if (statement == null || statement.length() == 0) {
return false;
}
ArrayStack<Character> stack = new ArrayStack<>();
for (int i = 0; i < statement.length(); i++) {
Character character = statement.charAt(i);
switch (character) {
case ')':
if (!stack.empty() && stack.pop() == '(') {
continue;
} else {
return false;
}
case ']':
if (!stack.empty() && stack.pop() == '[') {
continue;
} else {
return false;
}
case '}':
if (!stack.empty() && stack.pop() == '{') {
continue;
} else {
return false;
}
default: // 左括號直接進棧
stack.push(character);
}
}
return stack.empty();
}
中序轉後序表達式
前序( prefix )
中序( infix )
後序( postfix )
思想:
- 當讀取字符是操作數時,直接輸出到後序表達式中;
- 當讀取字符是開括號,如 ( [ { , 直接壓棧;
- 當讀取字符是閉括號時, 判斷棧是否爲空。如果棧爲空,則拋出異常,如果不爲空,則把棧中元素依次出棧輸出到後序表達式中,直到首次遇到開括號,如果沒有遇到開括號,則拋出異常;
- 當讀取字符是運算符時,如果棧非空,並且棧頂不是開括號,並且棧頂運算符的優先級不低於讀取的運算符優先級,循環彈出棧頂元素並輸出到後序表達式中,最後把讀取的運算符壓棧;
- 當中序表達式全部讀取完畢後,如果棧中仍有元素,則依次把他們彈出並輸出到後序表達式中;
/**
* 中序表達式轉換成後序表達式
*
* @param statement
* @return
*/
private String infix2Postfix(String statement) {
if (statement == null || statement.length() == 0) {
return null;
}
// 保存運算符優先級
Map<String, Integer> map = new HashMap<>();
map.put("*", 2);
map.put("/", 2);
map.put("+", 1);
map.put("-", 1);
Stack<String> stack = new Stack<>();
StringBuilder sb = new StringBuilder();
// 爲了兼容元素爲多位數字時的轉換,先轉換成字符串數組
String[] statementArray = statement.split(" ");
if (statementArray.length == 0) {
return null;
}
for (int i = 0; i < statementArray.length; i++) {
String str = statementArray[i];
if (isOperatorNumber(str)) {
sb.append(str + " ");
continue;
}
switch (str) {
case "(":
case "[":
case "{":
stack.push(str);
break;
case ")":
case "]":
case "}":
if (stack.isEmpty()) {
throw new IllegalArgumentException("error");
} else {
while (!"(".equals(stack.peek()) && !"[".equals(stack.peek()) && !"{".equals(stack.peek())) {
if (!stack.isEmpty()) {
sb.append(stack.pop() + " ");
} else {
throw new IllegalArgumentException("error");
}
}
if ("(".equals(stack.peek()) || "[".equals(stack.peek()) || "{".equals(stack.peek())) {
// 後序表達式中沒有括號,直接出棧,不輸出
stack.pop();
}
}
break;
case "+":
case "-":
case "*":
case "/":
while (!stack.isEmpty() && !"(".equals(stack.peek()) && !"[".equals(stack.peek()) && !"{".equals(stack.peek()) && map.get(stack.peek()) >= map.get(str)) {
sb.append(stack.pop() + " ");
}
stack.push(str);
break;
}
}
while (!stack.isEmpty()) {
sb.append(stack.pop() + " ");
}
return sb.toString();
}
/*
* 判斷是否是操作數
*/
private boolean isOperatorNumber(String str) {
Pattern pattern = Pattern.compile("^[a-z0-9]+$");
return pattern.matcher(str).matches();
}
調用
System.out.print(main.infix2Postfix("a + b * c + ( d * e + f ) * g"));
System.out.println();
System.out.print(main.infix2Postfix("( 23 + 34 * 45 / ( 5 + 6 + 7 ) )"));
輸出結果:
a b c * + d e * f + g * +
23 34 45 * 5 6 + 7 + / +
後序表達式求值
/**
* 計算後序表達式值
*
* @param statement
* @return
*/
public String calculatePostfixValue(String statement) {
if (statement == null || statement.length() == 0) {
throw new IllegalArgumentException("the statement is illegal");
}
String[] array = statement.split(" ");
if (array.length == 0) {
throw new IllegalArgumentException("the statement is illegal");
}
Stack<String> stack = new Stack<>();
for (int i = 0; i < array.length; i++) {
String str = array[i];
// 如果是操作數,則入棧
if (isOperatorNumber(str)) {
stack.push(str);
} else {
double top1 = 0;
double top2 = 0;
if (!stack.isEmpty()) {
top1 = Double.parseDouble(stack.pop());
}
if (!stack.isEmpty()) {
top2 = Double.parseDouble(stack.pop());
}
double temp = 0;
switch (str) {
case "+":
temp = top1 + top2;
break;
case "-":
temp = top1 - top2;
break;
case "*":
temp = top1 * top2;
break;
case "/":
temp = top2 * 1.0f / top1;
break;
}
stack.push(String.valueOf(temp));
}
}
if (!stack.isEmpty()) {
return stack.pop();
}
return null;
}
調用
String res = main.infix2Postfix("( 1 + 17 ) / { 2 + 2 } * ( 2 + 5 )");
System.out.println("res = " + res);
System.out.println(main.calculatePostfixValue(res));
結果:
res = 1 17 + 2 2 + / 2 5 + *
31.5