杠上数据结构 - 栈

Posted 星火燎原2016

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了杠上数据结构 - 栈相关的知识,希望对你有一定的参考价值。

介绍

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

通常,栈的操作端称为 栈顶,另一端称为 栈底。栈的插入称为 进栈(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(Ultra Edit中的数据对齐

③ 数据结构之“栈”

AcWing 794.高精度除法

杠上数据结构 - 排序

杠上数据结构 - 排序

数据结构 第3章总结