栈 和 队列

一、栈

1、初识栈

是一种先进后出的数据结构,可以用数组或链表来实现。Stack底层是一个数组。

栈中的方法:

  • push:压栈
  • pop:出栈
  • peek:获取栈顶元素但不删除
  • empty:判断栈是否为空
  • size:获取栈中的元素个数

2、用数组和链表实现栈 

(1)数组:

用数组实现的栈,也可以叫做顺序栈。

用数组实现栈时,压栈出栈都是对栈最后的下标(elem[size])进行操作,时间复杂度为O(1)

public class MyStack {
    private int[] elem;
    private int size;
    public MyStack(){
        this.elem = new int[10];
    }
    public boolean isFull(){
        return this.size >= this.elem.length;
    }
    public boolean isEmpty(){
        return this.size == 0;
    }
    //压栈
    public void push(int data){
       if(isFull()){
           this.elem = Arrays.copyOf(this.elem,this.elem.length*2);
       }
       this.elem[size] = data;
       this.size++;
    }
    //出栈
    public int pop(){
        if(isEmpty()){
            throw new EmptyStackException("栈为空!");
        }
        this.size--;
        return this.elem[this.size];
    }
    //获取栈顶元素
    public int peek(){
        if(isEmpty()){
            throw new EmptyStackException("栈为空!");
        }
        return this.elem[this.size-1];
    }

}

(2)链表:

用链表实现的栈,也可以叫做链式栈。

可以用单向链表或双向链表来实现栈,双向链表更适合。

单向链表:头插头删,时间复杂度为O(1)

不尾插尾删的原因:尾插需要找尾巴,时间复杂度为O(n),尾删,需要定义一个前驱,时间复杂度也是O(n)

//单向链表实现栈:头插头删
public class MyStack2 {
    static class ListNode {
        private int val;
        private ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }
    private ListNode head;
    //方法
    public void push(int data){
        ListNode node = new ListNode(data);
        if(this.head == null){
            this.head = node;
            return;
        }
        node.next = head;
        head = node;
    }
    public int pop(){
        if(empty()){
            throw new EmptyStackException("栈为空!");
        }
        int ret = this.head.val;
        this.head = this.head.next;
        return ret;
    }
    public int peek(){
        if(empty()){
            throw new EmptyStackException("栈为空!");
        }
        return head.val;
    }
    public boolean empty(){
        return this.head == null;
    }

}

双向链表:头插头删、尾插删都可以,时间复杂度为O(1)

//双向链表实现栈:尾插尾删
public class MyStack3 {
    static class ListNode{
        private int val;
        private ListNode prev;
        private ListNode next;
        public ListNode(int val){
            this.val = val;
        }
    }
    public ListNode head;
    public ListNode tail;

    //方法
    public void push(int data){
        ListNode node = new ListNode(data);
        if(this.head == null){
            this.head = node;
            this.tail = node;
            return;
        }
        ListNode cur = this.tail;
        cur.next = node;
        node.prev = cur;
        tail = node;
    }
    public int pop(){
        if(this.head == null){
            throw new EmptyStackException("栈为空!");
        }
        if(this.tail.prev == null){
            int ret = this.tail.val;
            this.tail = null;
            this.head = null;
            return ret;
        }
        int ret = this.tail.val;
        this.tail = this.tail.prev;
        this.tail.next = null;
        return ret;
    }
    public int peek(){
        if(this.head == null){
            throw new EmptyStackException("栈为空!");
        }
        return this.tail.val;
    }
    public boolean empty(){
        return this.head == null;
    }

}

3、栈的一些题目

(1)选择题:中缀表达式 转 后缀表达式(逆波兰表达式)

如:将中缀表达式 1+2*3 +(4*5+6)*7 转成 后缀表达式。

  1. 从左向右,先乘除后加减,进行加括号(1 +2 * 3 +4 * 5+ 6)* 7
  2. 把运算符挪到所在括号的后面12 3*4 5* 6)+ 7*+
  3. 去掉所有的括号 1 2 3 * + 4 5 * 6 + 7 * +

(2)逆波兰表达式求值 链接

String是引用类型,字符串比较要使用equals

将String类型的数据s转为Integer类型:Integer.parseInt(s);

是操作数(+、-、*、/ )就出栈2次,不是操作数就入栈

 public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for(int i = 0; i < tokens.length; i++){
            String s = tokens[i];
            if(s.equals( "+")  || s.equals( "-")  || s.equals( "*")  || s.equals( "/")){
                int num2 = stack.pop();
                int num1 = stack.pop();
                switch(s){
                    case "+":
                    stack.push(num1+num2);
                    break;
                    case "-":
                    stack.push(num1-num2);
                    break;
                    case "*":
                    stack.push(num1*num2);
                    break;
                    case "/":
                    stack.push(num1/num2);
                    break;
                }
            }else{
               stack.push(Integer.parseInt(s));
            }
        }
        return stack.pop();
 }

(3)将递归转换为非递归,如:逆序打印链表

逆序打印链表有 递归 和 非递归 两种做法。

递归: 

递归的终止条件

递推公式

public void display1(ListNode cur){
        if(cur == null){
            return;
        }
        display1(cur.next);//递推公式
        System.out.print(cur.val+" ");
}

 非递归:可以使用栈

public void display2(){
        Stack<ListNode> stack = new Stack<>();
        ListNode cur = this.head;
        while(cur != null){
            stack.push(cur);
            cur = cur.next;
        }
        while(!stack.empty()){
            ListNode ret = stack.pop();
            System.out.print(ret.val+" ");
        }
}

(4)括号匹配 链接

我们使用栈来做这一题,是左括号就入栈,是右括号就出栈

括号全部都匹配的情况:

  • 括号遍历完了,与此同时,栈也为空了

所以,括号不匹配的情况就有以下3种:

  1. 括号没遍历完,栈已经空了,即到右括号时没左括号与之匹配
  2. 括号遍历完了,栈里面还有括号,即右括号都没了,栈里还剩左括号没人匹配
  3. 括号没遍历完,栈里面也还有括号,即左括号匹配到了不对应的右括号
 public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for(int i = 0; i < s.length(); i++){
            char ch = s.charAt(i);
            if(ch == '(' || ch == '[' || ch == '{'){
                stack.push(ch);
            }else{
                if(stack.empty()){
                    //不匹配的第一种情况,右括号还没遍历完,栈已经空了
                    return false;
                }
                char ret = stack.pop();
                if(!(ret == '(' && ch == ')' || ret == '[' && ch == ']' || ret == '{' && ch == '}')){
                     //不匹配的第三种情况,右括号与左括号不对应
                    return false;
                }
            }
        }
        //不匹配的第二种情况,左括号还剩在栈里
        if(!stack.empty()){
            return false;
        }
        return true;
}

(5)出栈入栈次序匹配 链接

进栈过程中可以出栈,判断第二个序列是否可能为该栈的弹出顺序

做题思路:

  1. 定义i下标,遍历pushA数组,如果i < pushA.length,就往栈中放元素
  2. 然后peek一下,看栈顶的元素和pop[j]元素是否一样
  3. 一样就出栈,j++,然后判断栈是否为空,栈不为空,再peek一下,看栈顶的元素和pop[j]元素是否一样,一样继续出栈,j++
  4. 不一样或栈为空就 i++,如果i < pushA.length,就往栈中放元素如果i < pushA.length,就继续往栈中放元素。然后继续 2~4 的操作
  5. 直到 i < pushA.length不满足,pushA数组遍历完了
  6. 此时看栈是否为空,栈为空则出栈入栈次序匹配,栈不为空则次序不匹配
public boolean IsPopOrder (int[] pushV, int[] popV) {
        Stack<Integer> stack = new Stack<>();
        int j = 0;
        for(int i = 0; i < pushV.length; i++){
            stack.push(pushV[i]);
            while(!stack.empty() && stack.peek().equals(popV[j])){
                stack.pop();
                j++;
            }
        }
        //pushV走完了,栈中还有元素
        if(!stack.empty()){
            return false;
        }
        return true;
}

(6)最小栈 链接

Integer是引用数据类型,引用数据类型比较不能使用 == ,要使用 equals() !!!

解题思路:

  • 定义2个栈,
  • 普通栈中一定要放,
  • 最小栈中,
  • 当前元素 <= 最小栈的栈顶元素时,放
  • 当前元素 > 最小栈的栈顶元素时,放
class MinStack {
    private Stack<Integer> stack;
    private Stack<Integer> minStack;
    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }
    
    public void push(int val) {
        if(stack.empty()){
            stack.push(val);
            minStack.push(val);
        }else{
            stack.push(val);
            if(val <= minStack.peek()){
                minStack.push(val);
            }
        }
    }
    
    public void pop() {
        /**
        if(!stack.empty()) {
            int x = stack.pop();
            if(x == minStack.peek()) {
                minStack.pop();
            }
        }
      */
         if(!stack.empty()) {
          
          if(stack.peek().equals( minStack.peek())){
             stack.pop();
             minStack.pop();
          }else{
             stack.pop();
          }
        }

    }
    
    public int top() {
        if(stack.empty()){
            return -1;
        }
        return stack.peek();
    }
    
    public int getMin() {
        if(minStack.empty()){
            return -1;
        }
        return minStack.peek();
    }
}

4、栈、虚拟机栈、栈帧有什么区别?

栈:是一种先进后出的数据结构。

虚拟机栈:是JVM的一块内存空间。

栈帧:是在调用函数的过程当中,在Java虚拟机栈上开辟的一块内存。

二、队列

1、初识队列

队列是一种先进后出的数据结构,从队尾(tail/rear)入,从队头(head/front)出

Queue是个接口,实现类是LinkedList

LinkedList底层是双向链表,可以当做链表、栈、队列 来使用。

队列中的方法:

  • offer:入队
  • poll:出队
  • peek:获取队头元素但不删除
  • isEmpty:判断队列是否为空
  • size:获取队列中的元素个数

2、用链表和数组实现队列

(1)链表:

用链表实现的队列,也可以叫做链式队列。

可以用单向链表或双向链表来实现队列,双向链表更适合。

单向链表:定义一个tail,然后 尾增头删(从tail 入队,从 head 出队),时间复杂度是O(1)

因为单向链表 头增头删,时间复杂度为O(1),

尾删,需要找尾巴节点的前驱,时间复杂度为O(n)

尾增,有tail,时间复杂度为O(1);无tail,需要找尾巴,时间复杂度为O(n)

//单向链表实现队列:尾入头出
public class MyQueue {
    static class ListNode{
        private int val;
        private ListNode next;
        public ListNode(int val){
            this.val = val;
        }
    }
    //头
    public ListNode head;
    //尾
    public ListNode tail;

    //从尾入队
    public void offer(int data) {
        ListNode node = new ListNode(data);
        if(this.head == null){
            this.head = node;
            this.tail = node;
            return;
        }
        tail.next = node;
        tail = node;
    }
    //从头出队
    public int poll(){
        if(isEmpty()){
            return -1;
        }
        int ret = this.head.val;
        if(this.head.next == null){
            this.tail = null;
        }
        this.head = this.head.next;
        return ret;
    }
    //获取队头元素但不删除
    public int peek(){
        if(isEmpty()){
            return -1;
        }
        return this.head.val;
    }
    //判断队列是否为空
    public boolean isEmpty(){
        return this.head == null;
    }
}

 双向链表:尾增头删、头增尾删都可以,时间复杂度为O(1)

//双向链表实现队列:头入尾出
public class MyQueue2 {
    static class ListNode{
        private int val;
        private ListNode prev;
        private ListNode next;
        public ListNode(int val){
            this.val = val;
        }
    }
    public ListNode head;
    public ListNode tail;
    public void offer(int data){
        ListNode node = new ListNode(data);
        if(isEmpty()){
            this.head = node;
            this.tail = node;
            return;
        }
        node.next = head;
        head.prev = node;
        head = node;
    }
    public int poll(){
        if(isEmpty()){
            return -1;
        }
        if(this.head.next == null){
            int ret = this.head.val;
            head = null;
            tail = null;
            return ret;
        }
        int ret = tail.val;
        tail = tail.prev;
        tail.next = null;
        return ret;
    }
    public int peek(){
        if(isEmpty()){
            return -1;
        }
        return this.tail.val;
    }
    public boolean isEmpty(){
        return this.head == null;
    }
}

(2)数组:

循环队列通常用数组实现。

front 指向 队头,rear指向队尾。队尾入,队头出

front 与 rear 相遇了,队列是空还是满呢?

1)添加size属性,记录队列的长度

2)浪费一个空间

如何从7下标到0下标呢? 

rear = (rear+1)%(this.elem.length);

front = (front+1)%(this.elem.length);

if(rear == 0){
            return elem[this.elem.length-1];
 }
            return this.elem[rear-1];

方法一:添加size属性,记录队列的长度

//数组实现循环队列
public class MyCircularQueue1 {

    //数组
    private int[] elem;
    //队列的长度
    private int size;
    //队头和队尾的下标
    private int front;
    private int rear;

    public MyCircularQueue1(int k) {
        //数组的长度
        this.elem = new int[k];
    }
    //循环队列是否为空
    public boolean isEmpty() {
        return this.size == 0;
    }
    //循环队列是否满了
    public boolean isFull() {
        return this.size == this.elem.length;
    }
    //入队
    public boolean enQueue(int value) {
        if(isFull()){
            return false;
        }
        this.elem[rear] = value;
        rear = (rear+1)%(this.elem.length);
        this.size++;
        return true;
    }
    //出队
    public boolean deQueue() {
        if(isEmpty()){
            return false;
        }
        front = (front+1)%(this.elem.length);
        this.size--;
        return true;
    }
    //获取队头元素但不删除
    public int Front() {
        if(isEmpty()){
            return -1;
        }
        return this.elem[front];
    }
    //获取队尾元素但不删除
    public int Rear() {
        if(isEmpty()){
            return -1;
        }
        if(rear == 0){
            return elem[this.elem.length-1];
        }
        return this.elem[rear-1];
    }

}

方法二:浪费一个空间

每次放元素的时候,都去检查一下,当前rear的下一个是不是front,不是就说明队列没满可以放。

public class MyCircularQueue2 {
    //数组
    private int[] elem;
    //队头和队尾的下标
    private int front;
    private int rear;

    public MyCircularQueue2(int k) {
        //数组的长度
        this.elem = new int[k];
    }
    //循环队列是否为空
    public boolean isEmpty() {
        return rear == front;
    }
    //循环队列是否满了
    public boolean isFull() {
        int ret = (rear+1)%this.elem.length;
        return ret == front;
    }
    //入队
    public boolean enQueue(int value) {
        if(isFull()){
            return false;
        }
        this.elem[rear] = value;
        rear = (rear+1)%(this.elem.length);
        return true;
    }
    //出队
    public boolean deQueue() {
        if(isEmpty()){
            return false;
        }
        front = (front+1)%(this.elem.length);
        return true;
    }
    //获取队头元素但不删除
    public int Front() {
        if(isEmpty()){
            return -1;
        }
        return this.elem[front];
    }
    //获取队尾元素但不删除
    public int Rear() {
        if(isEmpty()){
            return -1;
        }
        if(rear == 0){
            return elem[this.elem.length-1];
        }
        return this.elem[rear-1];
    }
}

3、队列的一些面试题 

(1)用队列实现栈 链接

入栈:入到不为空的队列中

出栈:出size-1个元素到空队列中,则这个队列中剩余的那个就是要出的元素 

//用队列实现栈
public class QueueAchieveStack {
    //队列
    public Queue<Integer> queue1;
    public Queue<Integer> queue2;

    public QueueAchieveStack() {
        this.queue1 = new LinkedList<>();
        this.queue2 = new LinkedList<>();
    }
    //入栈
    public void push(int x) {
        if(!queue1.isEmpty()){
            queue1.offer(x);
        }else if(!queue2.isEmpty()){
            queue2.offer(x);
        }else{
            queue1.offer(x);
        }
    }
    //出栈
    public int pop() {
        //栈为空
        if(empty()){
            return -1;
        }
        int tmp = -1;
        if(!queue1.isEmpty()) {
            while (!queue1.isEmpty()) {
                tmp = queue1.poll();
                if (!queue1.isEmpty()) {
                    queue2.offer(tmp);
                }
            }
        }else{
            while (!queue2.isEmpty()) {
                tmp = queue2.poll();
                if (!queue2.isEmpty()) {
                    queue1.offer(tmp);
                }
            }
        }
        return tmp;
    }
    //获取栈顶元素但不删除
    public int top() {
        if(empty()){
            return -1;
        }
        int tmp = -1;
        if(!queue1.isEmpty()) {
            while (!queue1.isEmpty()) {
                tmp = queue1.poll();
                queue2.offer(tmp);
            }
        }else{
            while (!queue2.isEmpty()) {
                tmp = queue2.poll();
                queue1.offer(tmp);
            }
        }
        return tmp;

    }
    //判断栈是否为空
    public boolean empty() {
        return queue1.isEmpty() && queue2.isEmpty();
    }
}

(2)用栈实现队列 链接​​​​​​​

入队:入stack1

出队:出stack2,如果stack2为空,就把stack1中的所有元素都入栈到stack2

//用栈实现队列
public class StackAchieveQueue {
    public Stack<Integer> stack1;
    public Stack<Integer> stack2;
    public StackAchieveQueue() {
        this.stack1 = new Stack<>();
        this.stack2 = new Stack<>();
    }
    //入队
    public void push(int x) {
        stack1.push(x);
    }
    //出队
    public int pop() {
        if(empty()){
            return -1;
        }
        if(stack2.empty()){
            int size = stack1.size();
            for (int i = 0; i < size; i++) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
    //获取队头元素但不删除
    public int peek() {
        if(empty()){
            return -1;
        }
        if(stack2.empty()){
            int size = stack1.size();
            for (int i = 0; i < size; i++) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.peek();
    }
    //判断队列是否为空
    public boolean empty() {
        return stack1.empty() && stack2.empty();
    }
}
  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值