数据结构与算法之栈

1. 基本知识

1.1 概念

栈是一种操作受限的数据结构,只能在一端插入和删除数据,因此只支持入栈和出栈操作,后进先出是它的最大特点。栈可以通过数组实现,也可以通过链表来实现,其出栈和入栈的时间复杂度都是O(1)。

栈的结构

1.2 分类
  1. 顺序栈:数组实现
  2. 链式栈:链表实现
1.3 基本操作
  1. 入栈 push(T data)
  2. 出栈 pop()
  3. 获取栈顶数据 peek()
1.3 实现

只需要维护一个栈顶指针,通过数组实现的顺序栈和通过链表实现的链式栈。

1.3.1 顺序栈
package com.baidu.test.construct.stack;

/**
 * 顺序栈,后进先出
 */
public class ArrayStack<T> {

    // 默认容量
    private final static int DEFAULT_CAPACITY = 10;

    // 元素集合
    private Object[] datas;

    // 容量
    private int capacity;

    // 长度
    private int top;

    public ArrayStack() {
        this(DEFAULT_CAPACITY);
    }

    public ArrayStack(int capacity) {
        this.datas = new Object[capacity];
        this.capacity = capacity;
        this.top = 0;

    }

    /**
     * 入栈
     * @param data
     */
    public void push(T data) {
        ensureCapacity(top+1);
        datas[top++] = data;
    }

    /**
     * 出栈
     * @return
     */
    public T pop() {
        if (top == 0) {
            return null;
        }
        return (T) datas[--top];
    }

    /**
     * 获取栈顶元素
     * @return
     */
    public T getTop() {
        return (T) datas[top-1];
    }

    public int size() {
        return top;
    }

    public boolean empty() {
        return top == 0;
    }

    /**
     * 扩容
     * @param size
     */
    public void ensureCapacity(int size) {
        if (size > capacity) {
            System.out.println("容量不足,触发扩容...");
            capacity = capacity * 2;
            Object[] temp = new Object[capacity];
            System.arraycopy(datas, 0, temp, 0, top);
            datas = temp;
        }
    }

    public static void main(String[] args) {
        ArrayStack<Integer> stack = new ArrayStack<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        stack.push(5);
        stack.push(6);
        stack.push(7);
        stack.push(8);
        System.out.println("获取栈顶:" + stack.getTop());
        stack.push(9);
        stack.push(10);
        stack.push(11);
        stack.push(12);
        System.out.println("顺序栈长度:" + stack.size());
        System.out.println("顺序栈是否为空:" + stack.empty());
        System.out.print("遍历出栈:");
        while (!stack.empty()) {
            System.out.print(stack.pop() + ",");
        }
    }
}

1.3.2 链式栈
package com.baidu.test.construct.stack;

/**
 * 链表栈
 */
public class LinkedListStack<T> {

    // 头节点
    private Node<T> head;

    // 当前结点
    private Node<T> current;

    // 长度
    private int size;

    public LinkedListStack() {
        this.head = this.current = new Node<T>(null, null);
        this.size = 0;
    }

    /**
     * 入栈,链表尾部入栈
     * @param data
     */
    public void push(T data) {
        index(size-1);
        current.next = new Node<T>(data, current.next);
        size++;
    }

    /**
     * 出栈,链表头部出栈
     * @return
     */
    public T pop() {
        current = head;
        Node<T> node = current.next;
        current.next = current.next.next;
        size--;
        return node.data;
    }

    /**
     * 获取栈顶位置
     * @return
     */
    public T getTop() {
        return head.next.data;
    }

    public int size() {
        return size;
    }

    public boolean empty() {
        return size == 0;
    }

    private void index(int index) {
        if (index < -1 || index > size -1) {
            throw new IndexOutOfBoundsException();
        }
        if (index == -1) {
        	current = head;
            return;
        }
        current = head.next;
        int i=0;
        while (i<index) {
            current = current.next;
            i++;
        }
    }

    private static class Node<T> {
        private T data;
        private Node<T> next;

        public Node(T data, Node<T> next) {
            this.data = data;
            this.next = next;
        }
    }

    public static void main(String[] args) {
        LinkedListStack<Integer> stack = new LinkedListStack<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        stack.push(5);
        stack.push(6);
        stack.push(7);
        stack.push(8);
        System.out.println("获取栈顶:" + stack.getTop());
        stack.push(9);
        stack.push(10);
        stack.push(11);
        stack.push(12);
        System.out.println("顺序栈长度:" + stack.size());
        System.out.println("顺序栈是否为空:" + stack.empty());
        System.out.print("遍历出栈:");
        while (!stack.empty()) {
            System.out.print(stack.pop() + ",");
        }
    }
}

2. 栈的应用

2.1 栈在括号匹配中的应用
package com.baidu.test.construct.stack;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

public class StackDemo {

    /**
     * 有效的括号:
     * 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
     * 有效字符串需满足:
     *      左括号必须用相同类型的右括号闭合。
     *      左括号必须以正确的顺序闭合。
     *
     * @param s
     * @return
     */
    private static boolean isValid(String s) {
        Stack<Character> stack = new Stack();
        Map<Character, Character> map = new HashMap<>();
        map.put(')', '(');
        map.put(']', '[');
        map.put('}', '{');
        int length = s.length();
        for (int i=0; i< length; i++) {
            char c = s.charAt(i);
            if (map.containsKey(c)) {
                Character pop = stack.isEmpty()? '$' : stack.pop();
                if (map.get(c) != pop) {
                    return false;
                }
            } else if (map.containsValue(c)) {
                stack.push(c);
            }
        }
        return stack.isEmpty();
    }

    public static void main(String[] args) {
        System.out.println(isValid("sfadfd[](sdfd){]"));
    }

}

2.2 最小栈的实现

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
  push(x) —— 将元素 x 推入栈中。
  pop() —— 删除栈顶的元素。
  top() —— 获取栈顶元素。
  getMin() —— 检索栈中的最小元素。

示例:
  MinStack minStack = new MinStack();
  minStack.push(-2);
  minStack.push(0);
  minStack.push(-3);
  minStack.getMin(); --> 返回 -3.
  minStack.pop();
  minStack.top(); --> 返回 0.
  minStack.getMin(); --> 返回 -2.

2.2.1 双栈实现
思路:
	1.将第一个元素入栈。
	2.新加入的元素如果大于栈顶元素,那么新加入的元素就不处理。
	3.新加入的元素如果小于等于栈顶元素,那么就将新元素入栈。
	4.出栈元素不等于栈顶元素,不操作。
	5.出栈元素等于栈顶元素,那么就将栈顶元素出栈。

图解:
	入栈 3 
	|   |    |   |
	|   |    |   |
	|_3_|    |_3_|
	stack  minStack
	
	入栈 55 大于 minStack 栈顶,不处理
	|   |    |   |
	| 5 |    |   |
	|_3_|    |_3_|
	stack  minStack
	
	入栈 2 ,此时右边的 minStack 栈顶就保存了当前最小值 2 
	| 2 |    |   |
	| 5 |    | 2 |
	|_3_|    |_3_|
	stack  minStack
	
	出栈 2,此时右边的 minStack 栈顶就保存了当前最小值 3
	|   |    |   |
	| 5 |    |   |
	|_3_|    |_3_|
	stack  minStack
	
	出栈 5,右边 minStack 不处理
	|   |    |   |
	|   |    |   |
	|_3_|    |_3_|
	stack  minStack
	
	出栈 3
	|   |    |   |
	|   |    |   |
	|_ _|    |_ _|
	stack  minStack

package com.baidu.test.construct.stack;

import java.util.Stack;

/**
 * 最小栈
 * 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
 *
 * push(x) —— 将元素 x 推入栈中。
 * pop() —— 删除栈顶的元素。
 * top() —— 获取栈顶元素。
 * getMin() —— 检索栈中的最小元素
 *
 */
public class MinStackByTwoStack {

    private Stack<Integer> stack;
    private Stack<Integer> minStack;

    public MinStackByTwoStack() {
        stack = new Stack();
        minStack = new Stack();
    }

    public void push(int x) {
        stack.push(x);
        if (minStack.isEmpty()) { // 第一个元素最小
            minStack.push(x);
        } else {
            int pop = minStack.peek(); // 取出最小栈栈顶数据
            if (x <= pop) { // 当前数据小于等于最小栈栈顶数据,则将该数据压入最小栈栈顶
                minStack.push(x);
            }
        }
    }

    public void pop() {
        int pop = stack.pop();
        int peek = minStack.peek();
        if (pop == peek) { // 栈顶数据等于最小栈栈顶数据时,最小栈出栈
            minStack.pop();
        }
    }

    public int top() {
        return stack.peek();
    }


    public int getMin() {
        return minStack.peek();
    }

    public static void main(String[] args) {
        MinStackByTwoStack stack = new MinStackByTwoStack();
        stack.push(2);
        stack.push(3);
        stack.push(10);
        stack.push(7);
        stack.push(8);
        stack.push(1);
        System.out.println("栈顶数据为:" + stack.top());
        System.out.println("最小栈数据为:" + stack.getMin());
        System.out.println("执行一次出栈操作");
        stack.pop();
        System.out.println("栈顶数据为:" + stack.top());
        System.out.println("最小栈数据为:" + stack.getMin());
        System.out.println("执行一次出栈操作");
        stack.pop();
        System.out.println("栈顶数据为:" + stack.top());
        System.out.println("最小栈数据为:" + stack.getMin());
    }

}

2.2.1 单栈实现
思路:
	1.只需要一个栈了。
	2.当有更小的值来的时候,我们只需要把之前的最小值入栈,当前更小的值再入栈即可。
	3.当这个最小值要出栈的时候,下一个值便是之前的最小值了。

图解:
	入栈 3 
	|   |   min = 3
	|   |     
	|_3_|    
	stack   
	
	入栈 5 
	|   |   min = 3
	| 5 |     
	|_3_|    
	stack  
	
	入栈 2 
	| 2 |   min = 2?
	| 5 |     
	|_3_|    
	stack  

	如果只用一个变量就会遇到一个问题,如果把 min 更新为 2,那么之前的最小值 3 就丢失了。怎么把 3 保存起来呢?把它在 2 之前压入栈中即可。
	
	入栈 2 ,同时将之前的 min 值 3 入栈,再把 2 入栈,同时更新 min = 2
	| 2 |   min = 2
	| 3 |  
	| 5 |     
	|_3_|    
	stack  
	
	入栈 6 
	| 6 |  min = 2
	| 2 |   
	| 3 |  
	| 5 |     
	|_3_|    
	stack  
	
	出栈 6     
	| 2 |   min = 2
	| 3 |  
	| 5 |     
	|_3_|    
	stack  
	
	出栈 2     
	| 2 |   min = 2
	| 3 |  
	| 5 |     
	|_3_|    
	stack  
package com.baidu.test.construct.stack;

import java.util.Stack;

public class MinStackWithOneStack {

    private Stack<Integer> stack;
    private int min;

    public MinStackWithOneStack() {
        this.stack = new Stack<>();
        min = Integer.MAX_VALUE;
    }

    public void push(int x) {
        if (x <= min) { // 如果当前数据小于等于最小值,则把当前最小值先于当前数组压入
            stack.push(min);
            min = x;
        }
        stack.push(x);
    }

    public void pop() {
        if (stack.pop() == min) { // 如果弹出栈顶位置等于最小值,则再一次弹出数据设为最小值
            min = stack.pop();
        }
    }

    public int top() {
        return stack.peek();
    }

    public int getMin() {
        return min;
    }

    public static void main(String[] args) {
        MinStackWithOneStack stack = new MinStackWithOneStack();
        stack.push(2);
        stack.push(3);
        stack.push(10);
        stack.push(7);
        stack.push(8);
        stack.push(1);
        System.out.println("栈顶数据为:" + stack.top());
        System.out.println("最小栈数据为:" + stack.getMin());
        System.out.println("执行一次出栈操作");
        stack.pop();
        System.out.println("栈顶数据为:" + stack.top());
        System.out.println("最小栈数据为:" + stack.getMin());
        System.out.println("执行一次出栈操作");
        stack.pop();
        System.out.println("栈顶数据为:" + stack.top());
        System.out.println("最小栈数据为:" + stack.getMin());
    }
}

2.2.3 基于链表实现
思路:
	在 Node 节点中增加一个 min 字段,这样的话每次加入一个节点的时候,我们同时只要确定它的 min 值即可。
package com.baidu.test.construct.stack;

public class MinStack {

    private Node head;

    public MinStack() {
        this.head = new Node(null, null, null);
    }

    public void push(int x) {
        if (head.next == null) {
            head.next = new Node(x, head.next, x);
        } else {
            head.next = new Node(x, head.next, Math.min(head.next.min, x));
        }
    }

    public void pop() {
        if (head.next != null) {
            head.next = head.next.next;
        }
    }

    public int top() {
        return head.next.data;
    }

    public int getMin() {
        return head.next.min;
    }

    private static class Node {
        private Integer data;
        private Node next;
        private Integer min; // 最小值

        public Node(Integer data, Node next, Integer min) {
            this.data = data;
            this.next = next;
            this.min = min;
        }
    }

    public static void main(String[] args) {
        MinStack stack = new MinStack();
        stack.push(2);
        stack.push(3);
        stack.push(10);
        stack.push(7);
        stack.push(8);
        stack.push(1);
        System.out.println("栈顶数据为:" + stack.top());
        System.out.println("最小栈数据为:" + stack.getMin());
        System.out.println("执行一次出栈操作");
        stack.pop();
        System.out.println("栈顶数据为:" + stack.top());
        System.out.println("最小栈数据为:" + stack.getMin());
        System.out.println("执行一次出栈操作");
        stack.pop();
        System.out.println("栈顶数据为:" + stack.top());
        System.out.println("最小栈数据为:" + stack.getMin());
    }
}

2.3 实现简单计算器
思路:
	1.利用两个栈实现,一个栈保存原始字符,一个栈保存括号中的元素,
	2.括号中的数据计算完毕压入原始栈,
	3.遍历原始栈得到最终结果

	该问题因为有括号,因此比较复杂,如果没有括号的话,就可以简单:
	1.两个栈,一个数据栈,一个操作符栈。
package com.baidu.test.construct.stack;

import java.util.Stack;

/**
 * 实现一个基本的计算器来计算一个简单的字符串表达式的值。
 *
 * 字符串表达式可以包含左括号 ( ,右括号 ),加号 + ,减号 -,非负整数和空格
 */
public class Calculator {

    public static int calculate(String s) {
        if (s == null && s.length() == 0) {
            throw new RuntimeException("非法数据");
        }
        Stack<String> stack = new Stack<>();
        int length = s.length();
        for(int i=0; i<length; i++) {
            char c = s.charAt(i);
            if (c == ' ') {
                continue;
            }
            if (')' != c) {
                stack.push(c+"");
            } else {
                calculate(stack);
            }
        }

        calculate(stack);
        return Integer.parseInt(stack.pop());
    }

    private static void calculate(Stack<String> stack) {
        int sum = 0;
        Stack<String> tempStack = new Stack<>();
        while (!stack.isEmpty()) {
            String pop = stack.pop();
            if ("(".equals(pop)) {
                break;
            }
            tempStack.push(pop);
        }

        int x = Integer.parseInt(tempStack.pop());
        while (!tempStack.isEmpty()) {
            String p  = tempStack.pop();
            int y  = Integer.parseInt(tempStack.pop());
            sum = calculate(x, p , y);
            x = sum;
        }
        stack.push(sum + "");
    }

    private static int calculate(int x, String p, int y) {
        switch (p) {
            case "+":
                return x + y;
            case "-":
                return x - y;
            default:
                return 0;
        }
    }

    public static void main(String[] args) {
        System.out.println(calculate("(1+(4+5+2)-3)+(6+8)"));
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值