Java栈和队列

一、栈(Stack)

1、栈的概念

       栈是一种特殊的线性表,它只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则储存数据,先进入的元素被压入栈底最后的数据在栈顶。

 压栈:栈的插入操作叫做进栈/压栈/入栈,入栈的元素在栈顶。

 出栈:栈的删除操作叫做出栈,出数据在栈顶。


2、栈的使用

                       方法                           功能
Stack()构造一个空的栈
E  push(E  e)将元素入栈
E pop()将栈顶元素出栈
E  peek()获取栈顶元素
int size()获取栈中有效个数大小
boolean empty()判断栈是否为空


3、栈的模拟实现

      如图可见Stack继承了Vector,Vector的大小是可以动态变化的,它和ArrayList类似,但Vector是线程安全(方法大多是同步的,在多线程环境下可以安全地访问),但在一些不需要线程安全的场景下ArrayList通常是更好的选择,它的性能更高。 

public class MyStack {
    //首先定义一个数组
    int []array;
    //size为栈中有效元素个数
    int size;
    //在构造方法中进行数组初始化
    public MyStack(){
        array=new int[5];
    }

    //入栈操作
    public void push(int val) {
        //首先判断数组是否满了
        if(isFull()){
            //满了就要进行扩容
            array= Arrays.copyOf(array,array.length*2);
        }
        array[size]=val;
        size++;
    }
    public boolean isFull(){
        //有效数组个数是否等于数组长度
        return size== array.length;
    }

    //出栈操作
    public int pop() {
        //判断数组是否为空
        if(isEmpty()){
            return -1;
        }
        int ret=array[size-1];
        size--;
        return ret;
    }
    public boolean isEmpty(){
        //判断有效元素个数是否为0
        return size==0;
    }
    public int peek() {
        if (isEmpty()){
            //此处的-1为空的意思(null)
            return -1;
        }
        return array[size-1];
    }
    public int size(){
        return size;
    }

当然,栈也可以通过单向链表和双向链表来实现 

       在单向链表中,链表的头部相当于栈顶,当进行入栈操作时,在链表头部插入一个新节点,新节点的数据就是要入栈的元素,也就成为链表的头结点(使用头插法),也就是栈顶元素,出栈操作就是删除链表的头结点。

  • 采用的是头插法,入栈和出栈的时间复杂度都为O(1);
  • 采用的是尾插法,入栈的时间复杂度为O(n),如果有last,那么时间复杂度为O(1),但是出栈时间复杂度一定是O(n);

       在双向链表中无论是使用头插法还是尾插法时间复杂度都是O(1);


 4、栈的优缺点

优点:

  1. 简单高效,在许多算法和程序中,能快速储存和获取数据,执行效率高。
  2. 数据保护,只允许在栈顶进行操作,避免了对中间和底部数据的意外修改。
  3. 内存管理方便,系统自动管理。

缺点:

  1. 数据访问受限,操作不便,不适合需要频繁随机访问数据的场景。
  2. 空间大小固定,可能会发生栈溢出错误。


二、队列(Queue)

1、队列的概念 

      队列是一种特殊的数据结构,它遵循先进先出的原则,就像现实生活中排队一样,先进入队列的元素会被先处理。

入队列:进行插入操作的一端称为队尾。        

出队列:进行删除操作的一端称为队头。


2、队列的使用

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

以上的方法有两种,它们的使用不同:

队列的使用 :

由于Queue是一个接口,不能直接实例化对象,所以在实例化时必须实例化LinkedList的对象,LinkedList实现了Queue接口。

 


 

3、队列的模拟实现

我们使用双向链表来实现:

public class implementsQueue {
    //节点的创建
    static class ListNode{
        public int val;
        //用来获取前一个节点
        public ListNode prev;
        //用来获取后一个节点
        public ListNode next;

        public ListNode(int val){
            this.val=val;
        }
    }
    //first指向队头,last指向队尾
    public ListNode first;
    public ListNode last;

    //入队操作:尾插法
    public void offer(int val) {
        ListNode node=new ListNode(val);
        //判断是否一个元素都没有
        if(first==null){
            first=node;
            last=node;
        }else{
            //这里表示不是第一次入队
            last.next=node;
            node.prev=last;
            last=node;
        }
    }

    //出队:删除第一个节点
    public int poll() {
        //还是先判断队列为空不为空
        if(first==null){
            return -1;
        }
        //用于存储队头元素的值,便于返回
        int val=first.val;
        //判断是否只有一个元素
        if(first==last){
            first=null;
            last=null;
        }else{
            //头节点直接指向后一个节点,再将它的前驱置空
            first=first.next;
            first.prev=null;
        }
        return val;
    }

    //得到队头元素的值
    public int peek(){
        if(first==null){
            return -1;
        }
        int val=first.val;
        return val;
    }

    //得到有效元素的个数
    public int size(){
        //用于计数
        int count=0;
        //为了保持头结点不变,用于遍历队列
        ListNode cur=first;
        while(cur!=null){
            count++;
            cur=cur.next;
        }
        return count;
    }

    //判断队列是否为空
    public boolean isEmpty(){
        if(first==null){
            return true;
        }
        return false;
    }
}

 


 

4、循环队列

环形队列通常用数组实现:

数组下标循环小技巧: 

1、下标最后再往后(n小于array.length): index=(index+n)%array.length

 

2、下标最前再往前(n小于array.length): index=(index+array.length-n)%array,length 

区分空和满:

     使用标记 

  • 初始状态:队列为空时,front=rear=0,标志位isFull=false
  • 每次入队检查rear是否与front重合,如果重合设置isfull=true,表示队列已满,否则,正常入队并移动rear
  • 出队时,移动front指针,设置isfull=false。
class MyCircularQueue {
    public int[]elem;
    public int front;
    public int rear;
    public boolean isfull=false;
    public MyCircularQueue(int k) {
        this.elem=new int[k];
    }

    public boolean enQueue(int value) {
        if(isFull()){
            return false;
        }
        elem[rear]=value;
        rear=(rear+1)%elem.length;
        if(rear==front){
            isfull=true;
        }
        return true;
    }

    public boolean deQueue() {
        if(isEmpty()){
            return false;
        }
        front=(front+1)%elem.length;
        isfull=false;
        return true;
    }

    public int Front() {
        if(isEmpty()){
            return -1;
        }
        return elem[front];
    }

    public int Rear() {
        if(isEmpty()){
            return -1;
        }
        int index=-1;
        if(rear==0){
            index=elem.length-1;
        }else{
            index=rear-1;
        }
        return elem[index];
    }

    public boolean isEmpty() {
        return !isfull&&front==rear;
    }

    public boolean isFull() {
        return isfull;
    }
}

 


 

5、双端队列

      Deque(双端队列),是一种特殊的线性表,它允许在队列的两端进行插入和删除操作。

 

Deque是一个接口,使用时必须创建LinkedList对象 


 

6、队列的优缺点       

优点:

  1. 先进先出特性,不会出现混乱。
  2. 操作简单,降低了开发的难度和出错的概率。
  3. 内存管理高效,连续存储的,可利用数组实现,链式存储适合处理动态变化的数据量。

缺点:

  1. 空间限制,可能会出现空间浪费或者溢出的情况。
  2. 操作局限性,对中间元素的访问和操作比较困难。
  3. 性能问题,频繁的入队和出队操作,可能会1导致性能下降,链式存储的队列存在节点指针的额外开销,遍历队列时效率相对较低。


    ### Java双端队列Deque的实现与使用 #### 1. 接口定义与继承关系 `Deque` 是 `java.util` 包中的一个接口,表示双端队列(Double-ended Queue),支持在队列两端进行元素的插入移除操作。该接口继承自 `Queue` 接口,并扩展了其功能[^2]。 #### 2. 常见实现类 `Deque` 的主要实现类包括: - **ArrayDeque**: 基于数组的双端队列,性能较高,适合大多数场景。 - **LinkedList**: 虽然常被用作链表,但也实现了 `Deque` 接口,能够充当双端队列。 - **LinkedBlockingDeque**: 提供线程安全的支持,在多线程环境中常用[^3]。 #### 3. 主要方法 以下是 `Deque` 接口中的一些核心方法及其作用: | 方法名 | 描述 | |-------------------|----------------------------------------------------------------------| | `addFirst(E e)` | 将指定元素插入到此双端队列的开头。 | | `addLast(E e)` | 将指定元素插入到此双端队列的结尾。 | | `offerFirst(E e)` | 尝试将指定元素插入到此双端队列的开头(可选操作)。 | | `offerLast(E e)` | 尝试将指定元素插入到此双端队列的结尾(可选操作)。 | | `removeFirst()` | 移除此双端队列的第一个元素并返回它;如果为空则抛出异常。 | | `removeLast()` | 移除此双端队列的最后一个元素并返回它;如果为空则抛出异常。 | | `pollFirst()` | 获取并移除此双端队列的第一个元素;如果为空则返回 null。 | | `pollLast()` | 获取并移除此双端队列的最后一个元素;如果为空则返回 null。 | | `peekFirst()` | 返回第一个元素而不移除它;如果为空则返回 null。 | | `peekLast()` | 返回最后一个元素而不移除它;如果为空则返回 null。 | 这些方法使得 `Deque` 成为了一个多用途的数据结构[^1]。 #### 4. 使用示例 以下是一些常见的 `Deque` 使用案例: ##### (1) 创建双端队列 可以通过不同的实现类创建 `Deque` 对象: ```java // 使用 ArrayDeque 创建双端队列 Deque<Integer> arrayDeque = new ArrayDeque<>(); // 使用 LinkedList 创建双端队列 Deque<Integer> linkedListDeque = new LinkedList<>(); ``` ##### (2) 添加元素 向双端队列的头部或尾部添加元素: ```java arrayDeque.addFirst(1); // 向头部添加元素 arrayDeque.addLast(2); // 向尾部添加元素 ``` ##### (3) 删除元素 从双端队列的头部或尾部删除元素: ```java Integer firstElement = arrayDeque.removeFirst(); // 删除并获取头部元素 Integer lastElement = arrayDeque.pollLast(); // 删除并获取尾部元素(可能返回 null) ``` ##### (4) 查看元素 查看但不移除双端队列的头或尾元素: ```java Integer peekFirst = arrayDeque.peekFirst(); // 查看头部元素 Integer peekLast = arrayDeque.peekLast(); // 查看尾部元素 ``` ##### (5) 替代 Stack 功能 由于 `Deque` 支持的操作方法 (`push`, `pop`, `peek`),可以用它替代传统的 `Stack` 类: ```java Deque<String> stack = new ArrayDeque<>(); stack.push("A"); // 元素入 String top = stack.pop(); // 元素出 String peekTop = stack.peek(); // 查看顶元素 ``` #### 5. 特殊用途 除了基本的队列功能外,`Deque` 还可以用于模拟其他数据结构的行为: - **普通队列**:仅在一端插入、另一端取出元素。 - **双向队列**:可以在两端自由插入移除元素。 - ****:通过 `push/pop/peek` 操作实现 LIFO 行为[^4]。 --- ###
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值