算法基础篇 — 栈、队列

百科:栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。
栈可以用来在函数调用的时候存储断点,做递归时要用到栈!

示意图
栈
先进后出。

实现思路

数组实现

数据无限制输入输出的话,需要用动态数据。我们就举个固定数量的数组来实现栈先进后出。定义一个固定长度数组存储值或对象,定义一个putIndex下标 = 0记录下一个数据存放的位置,弹出的位置为putIndex-1,也就是栈顶。push一个数据,那么putIndex++,那么下次的数据存储数据下标的位置就有了。如果想要弹出,那么putIndex-1就是栈顶的数据。这里因为固定数量的数组,所以需要判定是否满了,或者全部以弹出。

	public static class MyStack {
        int[] arr;
        int putIndex = 0;
        int limit;

        MyStack (Integer limit) {
            this.arr = new int[limit];
            this.limit = limit;
        }

        public int push(int value) throws Exception {
            if (putIndex == limit) {
                throw new Exception("满了");
            }
            arr[putIndex] = value;
            putIndex ++;
            return putIndex - 1;
        }

        public int pop() throws Exception {
            if (putIndex == 0) {
                throw new Exception("没了");
            }
            int value = arr[putIndex - 1];
            putIndex --;
            return value;
        }
    }

双向链表实现

使用存储头尾的双向链表实现就很简单了,从头部插入,从头部弹出,不就实现了先进后出的栈结构了吗?而且时间复杂度都是O(1),因为始终都是头部的删除插入的常数操作。

    public static class MyStackByDoubleNode<T> {
        DoubleNodeQueueAndStack<T> doubleNode = new DoubleNodeQueueAndStack();

        public void addFromHead(T value) {
            doubleNode.addFromHead(value);
        }

        public void popFromHead() {
            doubleNode.popFromHead();
        }
    }
	public static class DoubleNodeQueueAndStack<T> {
        DoubleNode<T> head;
        DoubleNode<T> tail;

        /**
         * 换新的头部节点
         * @param value
         */
        public void addFromHead(T value) {
            DoubleNode<T> cur = new DoubleNode<>(value, null, null);
            // 新增头部
            if (head == null) {
                tail = cur;
            } else {
                // 当前节点的下一个节点为原来的头
                cur.next = head;
                // 原来的头节点的上一个节点为当前节点
                head.prev = cur;
            }
            // 头部节点重置为当前节点
            head = cur;
        }

        public void addFromBottom(T value) {
            DoubleNode<T> cur = new DoubleNode<>(value, null, null);
            if (head == null) {
                head = cur;
            } else {
                // 新的节点的上一个节点引用原来的尾节点
                cur.prev = tail;
                // 原来的尾节点的下一个节点引用新节点
                tail.next = cur;
            }
            // 尾部节点重置为当前节点
            tail = cur;
        }

        public T popFromBottom() {
            if (head == null) {
                return null;
            }
            DoubleNode<T> cur = tail;
            if (head == tail) {
                head = null;
                tail = null;
            } else {
            	// 尾节点重置为当前尾节点的上一个节点
                tail = tail.prev;
                // 新的尾节点的下一个节点指向null
                tail.next = null;
                // 旧的尾节点的上一个节点指向null
                cur.prev = null;
            }
            return cur.data;
        }

        public T popFromHead() {
            if (head == null) {
                return null;
            }
            DoubleNode<T> cur = head;
            if (head == tail) {
                head = null;
                tail = null;
            } else {
            	// 头部节点重置为下一个节点
                head = head.next;
                // 旧的头节点的下一个节点指向null
                cur.next = null;
                // 新的头节点的上一个节点指向null
                head.prev = null;
            }
            return cur.data;
        }

        @Override
        public String toString() {
            return "DoubleNodeQueue{" +
                    "head=" + head +
                    ", tail=" + tail +
                    '}';
        }
    }
	@AllArgsConstructor
    public static class DoubleNode<T> {
        // 节点数据
        T data;
        // 上一个节点
        DoubleNode<T> prev;
        // 下一个节点
        DoubleNode<T> next;

        public T getData() {
            return data;
        }

        public void setData(T data) {
            this.data = data;
        }

        public DoubleNode<T> getPrev() {
            return prev;
        }

        public void setPrev(DoubleNode<T> prev) {
            this.prev = prev;
        }

        public DoubleNode<T> getNext() {
            return next;
        }

        public void setNext(DoubleNode<T> next) {
            this.next = next;
        }

        @Override
        public String toString() {
            return "DoubleNode{" +
                    "data=" + data +
//                    ", prev=" + prev +
                    ", next=" + next +
                    '}';
        }
    }

队列

百科:队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

示意图
队列
先进先出。

实现思路

环形数组实现

参考之前实现的栈,那么我们只需要保证在一定数量内的数组,只要数组还有空间,我们就把数据放入下一个空余的位置index。弹出也是同理,只要还有可以弹出的值,就一次递增index弹出相应的先插入的数据。

	public static class MyQueue {
        int[] arr;
        int putIndex = 0;
        int popIndex = 0;
        int limit;
        int size = 0;

        MyQueue (Integer limit) {
            this.arr = new int[limit];
            this.limit = limit;
        }

        public int push(int value) throws Exception {
            if (size == limit) {
                throw new Exception("队列满了");
            }
            size ++;
            arr[putIndex] = value;
            putIndex = getNextIndex(putIndex);
            return putIndex - 1;
        }

        public int pop() throws Exception {
            if (size == 0) {
                throw new Exception("没了");
            }
            size --;
            int value = arr[popIndex];
            popIndex = getNextIndex(popIndex);
            return value;
        }

        /**
         * 获取下一个数据存储位置的下标
         * @param index 数据下标
         * @return
         */
        public int getNextIndex(int index) {
            return index < limit -1 ? index + 1 : 0;
        }
    }

双向链表实现

队列讲究先进先出嘛,就是头部进,尾部出。什么意思呢?每次插入新节点,作为头部节点,那么原来的头节点就变成了尾部节点,那么最先插入的节点不就是尾部节点了吗?我们把双向链表的头尾存储起来,每次头部插入新节点,从尾部删除节点就ok了。

	public static class MyQueueByDoubleNode<T> {
        DoubleNodeQueueAndStack<T> doubleNode = new DoubleNodeQueueAndStack();

        public void addFromHead(T value) {
            doubleNode.addFromHead(value);
        }

        public void popFromBottom() {
            doubleNode.popFromBottom();
        }
    }

拓展

使用队列实现栈

使用队列实现栈

使用栈实现队列

使用栈实现队列

包含最小值的栈

最小值的栈

	public static class MinStack {
        Stack<Integer> data = new Stack<>();
        Stack<Integer> min = new Stack<>();

        public void push(Integer value) throws Exception {
            if (min.empty()) {
                min.push(value);
            } else if (value < getMin()) {
                min.push(value);
            } else {
                min.push(getMin());
            }
            data.push(value);
        }

        public Integer pop() throws Exception {
            if (data.empty()) {
                throw new Exception("没了");
            }
            min.pop();
            return data.pop();
        }

        public Integer getMin() throws Exception {
            if (min.empty()) {
                throw new Exception("没了");
            }
           return min.peek();
        }
    }

第二种方案

最小值的栈2
这种是时间复杂度高一些,空间复杂度低一些。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值