槓上數據結構 - 棧

介紹

: 是 一種只允許在一端進行插入,刪除的線性表,具有先進後出的特性。

通常,棧的操作端稱爲 棧頂,另一端稱爲 棧底。棧的插入稱爲 進棧(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 )

思想:
  1. 當讀取字符是操作數時,直接輸出到後序表達式中;
  2. 當讀取字符是開括號,如 ( [ { , 直接壓棧;
  3. 當讀取字符是閉括號時, 判斷棧是否爲空。如果棧爲空,則拋出異常,如果不爲空,則把棧中元素依次出棧輸出到後序表達式中,直到首次遇到開括號,如果沒有遇到開括號,則拋出異常;
  4. 當讀取字符是運算符時,如果棧非空,並且棧頂不是開括號,並且棧頂運算符的優先級不低於讀取的運算符優先級,循環彈出棧頂元素並輸出到後序表達式中,最後把讀取的運算符壓棧;
  5. 當中序表達式全部讀取完畢後,如果棧中仍有元素,則依次把他們彈出並輸出到後序表達式中;
/** 
* 中序表達式轉換成後序表達式 
* 
* @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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章