Java数据结构之栈和队列

一、栈(Stack)

(一)概念

:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In FirstOut)的原则
压栈:栈的插入操作,叫做进栈/压栈/入栈,入数据在栈顶
出栈:栈的删除操作叫做出栈,出数据在栈顶。
栈的结构

(二)栈的使用

方法功能
Stack()构造一个空的栈
E push(E e)将e入栈,并返回e
E pop()将栈顶元素出栈并返回
E peek()获取栈顶元素但不弹出
int size()获取栈中有效元素个数
boolean empty()检测栈是否为空

(三)栈的模拟实现

class MyStack{
    private int[] arr;
    private int usedSize = 0;
    private static final int DEFAULT_QUANTITY = 10;

    public MyStack() {
        this.arr = new int[DEFAULT_QUANTITY];
    }

    //push方法,向栈中增加元素
    public int push(int data) {
        if(isFull()) {
            arr = Arrays.copyOf(arr,arr.length * 2);
        }
        arr[usedSize] = data;
        usedSize++;
        return data;
    }

    //判断是否栈满
    public boolean isFull() {
        return usedSize == arr.length;
    }

    //让最上面的元素出栈
    public int pop() {
        if(empty()) {
            throw new MyEmptyStackException("栈为空!");
        }
        usedSize--;
        return arr[usedSize];
    }

    //查看最上面的元素但不出栈
    public int peek() {
        if(empty()) {
            throw new MyEmptyStackException("栈为空!");
        }
        return arr[usedSize - 1];
    }

    //判断栈是否为空
    public boolean empty() {
        return usedSize == 0;
    }


}

我们可以从集合那一节的图中看出,Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的

(四)问题思考

1. 栈,虚拟机栈,栈帧有什么区别?

  1. 栈:是一种先进后出的数据结构
  2. 虚拟机栈:是JVM的一块内存空间
  3. 栈帧:是在调用方法(函数)的过程当中,在Java虚拟机栈上开辟的一块内存

2.单链表能否实现栈,如果可以,为什么?

目前实现的栈,底层是数组实现,所以也可以叫做顺序栈。那么,单链表能否实现栈,如果可以,为什么?

首先可以实现。
我们可以采用头插法来入栈,每次出栈时删除的也是头节点,时间复杂度也是O(1),但仍没有顺序栈方便,毕竟顺序栈不用修改指向

二、队列(Queue)

(一)概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾(Tail/Rear)
出队列:进行删除操作的一端称为队头(Head/Front)
队列结构

(二)队列的使用

在Java中,Queue是个接口,底层是通过链表实现

方法功能
boolean offer(E e)入队列
E poll()出队列
E peek()获取队头元素但不出队
int size()获取队列中有效元素个数
boolean isEmpty()检测队列是否为空

除上述介绍的offer方法可以增加元素之外,本身Queue还有一个add方法也可以增加元素,二者使用场景不同,add主要用在队列长度受限制的时候,此时如果队列满但调用add就会抛出异常, 而offer主要用在队列长度不受限制的时候,offer不会抛出异常,因为根本没有队列满的概念

注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口

(三)队列模拟实现

队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有两种:顺序结构链式结构,那么思考队列的实现是使用顺序结构好还是链式结构好?
我们将两种方法都写明,并分别画图介绍两种方法

1.顺序表的写法

class MyCircularQueue {

    private int[] arr;
    private int usedSize;
    private int front;
    private int rear;

    public MyCircularQueue(int k) {
        arr = new int[k];
    }
    
    public boolean enQueue(int value) {
        if(isFull()) {
            return false;
        }
        arr[rear] = value;
        rear = (rear + 1) % arr.length;
        usedSize++;
        return true;
    }
    
    public boolean deQueue() {
        if(isEmpty()) {
            return false;
        }
        front = (front + 1) % arr.length;
        usedSize--;
        return true;
    }
    
    public int Front() {
        if(isEmpty()) {
            return -1;
        }
        return arr[front];
    }
    
    public int Rear() {
        if(isEmpty()) {
            return -1;
        }
        int temp = (rear - 1 + arr.length) % arr.length;
        return arr[temp];
    }
    
    public boolean isEmpty() {
        if(usedSize == 0) {
            return true;
        }
        return false;
    }
    
    public boolean isFull() {
        if(usedSize == arr.length) {
            return true;
        }
        return false;
    }
}

2.链表的写法

public class MyLinkedQueue {
    public static class ListNode{
        int val;
        ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }
    ListNode head = new ListNode(0);
    private int usedSize;
    private ListNode front = head.next;
    private ListNode rear = head.next;

    //用链表作为队列的元素,不再存在队列满的情况,只有堆满的情况
    //入队
    public boolean enQueue(int value) {
        ListNode Node = new ListNode(value);
        if(isEmpty()) {
            head.next = Node;
            front = Node;
            rear = Node;
        }else {
            rear.next = Node;
            rear = rear.next;
        }
        usedSize++;
        return true;
    }

    //出队,删除队首元素
    public boolean deQueue() {
        if(isEmpty()) {
            return false;
        }
        if(front == rear) {
            head.next = null;
            front = null;
            rear = null;
        }else {
            head.next = front.next;
            front = head.next;
        }
        usedSize--;
        return true;
    }

    //返回队首元素
    public ListNode Front() {
        if(isEmpty()) {
            return null;
        }
        return front;
    }

    //返回队尾元素
    public ListNode Rear() {
        if(isEmpty()) {
            return null;
        }
        return rear;
    }

    public boolean isEmpty() {
        return usedSize == 0;
    }
}

(四)循环队列

循环队列就是通过顺序表实现的成果
front作为队列头
rear作为队列尾
循环队列结构

数组下标循环的技巧
(front/rear + array.length) % array.length

1.如何区分空与满

  1. 通过添加 size 属性记录
    直接判断if(size >= array.length)即可

  2. 保留一个位置
    我们采取舍弃一个位置的方法,让rear永远指向队尾的下一个位置。那么当队空时rear == front,队不为空时,rear都是front之后,随着入队出队的操作,最终当(rear + array.length) % array.length + 1 == front,就表示队满
    循环队列判断队满队空

  3. 使用标记
    即当第一次相遇的时候置为 什么,第二次相遇的时候置为 什么

(五)顺序结构和链式结构比较

队列采用顺序结构还是链式结构我们从它的存储方式来看

  1. 顺序存储在物理逻辑上都是连续的,链式存储只是在物理上是连续的,相比较而言顺序存储在主观上似乎更符合队列的数据结构
  2. 顺序结构的入队出队需要计算位置坐标,链式结构入队出队操作需要修改指向,链表修改指向的操作我们更加熟练
  3. 顺序结构的空间一般在实例化的时候就要给出,并且之后很难扩容,要么会造成浪费,要么就是出现队满的情况。而链式结构就没有队满的概念
  4. 综合来看,链式结构是最优的选择

三、双端队列(Deque)

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。
那就说明元素可以从队头出队和入队,也可以从队尾出队和入队
Deque是一个接口,使用时必须创建LinkedList的对象
双端队列结构
由于在实际开发中Deque使用的并不是非常多,所以不再做过多说明

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

求索1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值