杠上数据结构 - 栈

介绍

: 是 一种只允许在一端进行插入,删除的线性表,具有先进后出的特性。

通常,栈的操作端称为 栈顶,另一端称为 栈底。栈的插入称为 进栈(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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章