数据结构:栈和队列(详细讲解)

news2025/1/13 7:28:50

🎇🎇🎇作者:
@小鱼不会骑车
🎆🎆🎆专栏:
《数据结构》
🎓🎓🎓个人简介:
一名专科大一在读的小比特,努力学习编程是我唯一的出路😎😎😎
在这里插入图片描述

栈和队列

    • 一. 栈的基本概念
      • 1. 栈的定义
      • 2. 栈的常见基本操作
    • 二. 栈的顺序存储结构
      • 1. 栈的顺序存储
        • top的第一种初始化方法
        • top的第二种初始化方法
      • 2. 栈的基本方法
          • (1) 初始化
          • (2) 判空+判满(top初始化为-1)
          • (3) 进栈
          • (4) 出栈
          • (5) 读取栈顶元素
      • 3. 进栈出栈变化形式
      • 4. 共享栈(双栈)
          • (1) 共享栈的概念
          • (2) 共享栈的空间结构
          • (3) 共享栈进栈
          • (4) 共享栈出栈
        • 共享栈常用场景
    • 三. 栈的链式存储结构
      • 1. 链栈
        • 链栈的优点
      • 2. 链栈的基本方法
          • (1) 链栈的入栈
          • (2) 链栈的出栈
      • 3. 对比链栈和顺序栈
    • 四. 栈的应用——递归
      • 1. 递归的定义
    • 五. 栈的应用——逆波兰表达式求值
    • 六. 栈的应用——中缀表达式转后缀表达式
  • 队列
    • 一. 队列的基本概念
      • 1. 队列的定义
      • 2. 队列的常见基本操作
    • 二. 队列的顺序存储结构
      • 1.顺序队列
      • 2.循环队列
      • 3. 循环队列的常见基本算法
        • (1)循环队列的顺序存储结构
        • (2) 循环队列判空
        • (3)求循环队列长度
        • (4)循环队列入队
        • (5)循环队列出队
    • 三. 队列的链式存储结构
      • 1. 链队列
      • 2. 链队的常见基本算法
        • (1)链队列存储类型
        • (2)链队列入队
        • (3)链队列出队
      • 链队列和循环队列的比较
    • 四. 双端队列
      • 1. 定义
    • 五. java中集合的用法

一. 栈的基本概念

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

在我们的常见生活中,我们在使用浏览器啊,写代码啊,或者说制作视频都会发现有一个返回键,例如我们在用文件夹在访问内容时,不小心点错文件,进入了一个不是自己想要查找的文件时,我们便可以通过上方的返回键来返回上一个页面。
在这里插入图片描述

这里涉及到的就是栈,也是栈经常使用的场景,当然!不同的程序他们的底层会用不同的代码来实现,但是不变的就是栈这个思想,我们只需要了解栈的这个数据结构就行。

1. 栈的定义

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
出栈:栈的删除操作叫做出栈,出数据在栈顶

在这里插入图片描述

栈在现实生活中的例子:
在这里插入图片描述

2. 栈的常见基本操作

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

二. 栈的顺序存储结构

1. 栈的顺序存储

采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置。

top的第一种初始化方法

对于top其实有两种初始化的方法,一种就是初始化为0,

public class MyStack {
    int []array;
    int top;//记录栈顶位置
    int capacity;//容量
    public MyStack(int x) {
        this.top=0;//初始化为0
        array=new int[x];//初始化一个x大小的数组
        capacity=x;
    }
    public MyStack() {
        this.top=0;//初始化为0
        array=new int[4];//默认初始化为4个大小的数组
        capacity=4;

    }

对于top 初始化为0,其实就是每次栈顶添加新元素时,都是先进行赋值,再top++,并且还需要对栈满进行判断(top初始化为0时添加元素和判断栈满的条件如下)

    public void push(int x) {
        //判断栈满
        if(top==capacity) {
            //扩容
        }
        array[top]=x;
        top++;
    }

如图:
在这里插入图片描述

top的第二种初始化方法

当我的top初始化为-1时,由于我们添加元素需要从0下标开始添加,所以我们需要先top++,再赋值,此时我们的top记录的就是栈顶元素的下标,那么判满的话,就需要和capacity-1进行对比

public class MyStack {
    int []array;
    int top;//记录栈顶位置
    int capacity;//容量
    public MyStack(int x) {
        this.top=-1;//初始化为-1
        array=new int[x];//初始化一个x大小的数组
        capacity=x;
    }
    public MyStack() {
        this.top=-1;//初始化为-1
        array=new int[4];//默认初始化为4个大小的数组
        capacity=4;

    }
    public void push(int x) {
        //判断栈满
        if(top==capacity-1) {
            //扩容
        }
        top++;
        array[top]=x;
    }
}

如图
此时的top就是栈顶元素对应的下标
在这里插入图片描述

2. 栈的基本方法

(1) 初始化
//两个构造方法
    public MyStack(int x) {
        this.top=-1;
        array=new int[x];//初始化一个x大小的数组
        capacity=x;
    }
    public MyStack() {
        this.top=-1;
        array=new int[4];//默认初始化为4个大小的数组
        capacity=4;

    }
(2) 判空+判满(top初始化为-1)
//判空,当我的top为-1时就是没有元素
    public boolean empty(){
        return top==-1;
    }
    //判满,当我的top+1==capacity时就代表栈满了
    public boolean full() {
        return top+1==capacity;
    }
(3) 进栈
    public void push(int x) {
        //判断栈满
        if(full()) {
        //每次栈满扩容二倍
           array=Arrays.copyOf(array,2*capacity);
            capacity*=2;
        }
        top++;
        array[top]=x;
    }
(4) 出栈
    //出栈
    public int pop() {
        if(empty()) {
            throw new ArrayEmptyException("栈空");
        }
        //先返回栈顶元素再--
        return array[top--];
    }
    //自定义异常,当栈为空时抛出异常
class ArrayEmptyException extends RuntimeException{
    public ArrayEmptyException() {
    }
	//构造方法
    public ArrayEmptyException(String message) {
        super(message);
    }
}
(5) 读取栈顶元素
    public int peek() {
        if(empty()) {
            throw new ArrayEmptyException("栈空");
        }
        //直接返回栈顶元素
        return array[top];
    }

3. 进栈出栈变化形式

我们现在已经简单了解了栈的特性,那么大家思考一下,这个最先进栈的元素只能是最后出栈嘛?

答案是不一定的!因为栈虽然限制了线性表的插入和删除的位置,但是并没有对元素的进出进行时间限制,也可以理解为,在不是所有元素都进栈的情况下,事先进去的元素也可以出栈,只要保证是栈顶元素出栈就可以。

举例来说,如果我们现在是有 3个整型数字元素1,2,3 依次进栈,会有哪些出栈次序呢?

  • 第一种(最容易理解的):1,2,3依次进栈,再依次出栈,出栈次序是3,2,1.
  • 第二种:1进栈,1出栈,2进栈,3进栈,3出栈,2出栈,出栈次序是1 ,3, 2.
  • 第三种:1进栈,1出栈,2进栈,2出栈,3进栈,3出栈,出栈次序是1 ,2 ,3.
  • 第四种:1进栈,2进栈,2出栈,1出栈,3进栈,3出栈,出栈次序是2, 1, 3.
  • 第五种:1进栈,2进栈,2出栈,3进栈,3出栈,1出栈,出栈次序是2 ,3, 1.
  • 那我们的出栈次序可以是3 1 2嘛?答案是不可以,因为3进栈后,此时栈内一定是有1,2,此时栈顶是2,并且1在2的下面,由于只能从栈顶弹出,又因为不可以直接跳过2去拿到1,所以此时不会出现 1 比 2 优先出栈的情况。

对于栈的变化,光三个元素就有五种出栈次序,那么五个元素,甚至更多的元素,那么它的出栈变化会更多,所以这个知识点我们一定要弄明白!


这里有道题,大家可以试着做一下:

  1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
    A: 1,4,3,2 B: 2,3,4,1 C: 3,1,4,2 D: 3,4,2,1

答案是:c

4. 共享栈(双栈)

其实对于共享栈,就是尽量减少内存的浪费,就像吃饭一样,煮方便面一袋不够吃,两袋吃不完,那你就可以找一个小伙伴一起吃,然后煮三袋,这样就不会吃不完了,并且还都能吃饱,这里开个玩笑,真正的用法在下面。

(1) 共享栈的概念

利用栈底位置相对不变的特征,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如下图所示
在这里插入图片描述

当我的top0==-1时,0号栈底为空,当我的top1==MaxSize时,1号栈底为空,当我的top0+1==top1时,判断为栈满,0号栈进栈时top0先加一再赋值,1号栈进栈时top1先减一再赋值,0号出栈时先保存当前元素再减一,1号栈出栈时和0号栈的出栈操作恰好相反。

(2) 共享栈的空间结构

代码如下:

public class SharedStack {

    int[]array;//定义一个数组成员
    int top0;//记录0号栈的栈顶
    int top1;//记录1号栈的栈顶
    //构造方法,可以自己设置大小的数组
    public SharedStack(int x) {
        array=new int[x];
        top0=-1;
        top1=x;
    }
    public SharedStack() {
        //由于共享栈不好扩容,所以直接开辟50个大小的数组
        array=new int[50];
        top0=-1;
        top1=4;
    }

}
(3) 共享栈进栈

由于是双端栈,所以我们需要对它调用不同的栈进行不同的写法,也就是需要判断是0号栈还是1号栈,分别写出对于的push.

    public void push(int x,int stackNumber) {
        //判断栈是否满了
        if(top0+1==top1) {
            exit(0);
        }
        //通过stackNumber来控制调用0号栈或1号栈
        if(stackNumber==0) {
            top0++;
            array[top0]=x;
        } else  {
            top1--;
            array[top1]=x;
        }
    }
(4) 共享栈出栈
    public int pop(int stackNumber) {
    //判断调用几号栈
        if(stackNumber==0) {
        //判空
            if(top0==-1) {
                System.out.println("栈空");
            exit(0);
            } else {
                return array[top0--];
            }
        }else if(stackNumber==1){
            //判空
            if(top1==array.length) {
               System.out.println("栈空");
                exit(0);
            }else {
                return array[top1++];
            }
        }
        System.out.println("stackNumber输入错误");
        //输入的stackNumber错误所以返回-1
        //我们认为-1就是输入错误
        return -1;
    }

共享栈常用场景

一般的常用场景是,两个栈的空间需求有相反关系时,也就是一个栈增长,一个栈缩小的情况,例如你去买菜,你买了一斤白菜,那么卖家就少了一斤白菜,就这样我进,你出,才会使两栈空间的存储方法有更大的意义,否则如果我一直买菜,但是卖家也在一直进菜,那么很快就会因为栈满而溢出了。

当然,这个只是针对两个数据类型相同的栈设计的一个技巧,如果是不相同的栈,那么这么做反而会使问题变得更加复杂,所以大家要注意!

三. 栈的链式存储结构

1. 链栈

我们不仅可以通过顺序表实现栈,也是可以通过链表来实现的,但是有个前提,因为我们的顺序表实现的栈,它的插入删除时间复杂度是O(1),那么如果想通过链表来实现栈,那么我们就需要考虑时间复杂度能否达到O(1),我们如果是通过双向链表来实现栈的话,因为双向链表本身含有尾结点的指针,所以它的插入删除的时间复杂度是O(1),那么我们可以通过单链表来实现嘛?

答案也是可以的 !

我们可以通过头插头删来实现栈,由于我们单链表的头插,头删的时间复杂度都是O(1),并且我们的头插头删也满足了栈的先进后出的特性. 在链栈没有结点时,我们规定此时的head指向null.

在这里插入图片描述

链栈的优点

由于我们是通过链表来实现的栈所以可以称为链栈链栈几乎不会存在栈满的现象除非内存已经没有可以使用的空间了,如果真的发生,那么说明此时计算机已经内存被占满处于即将死机崩溃的情况,而不是这个链栈是否溢出的问题。

链栈的优点

  • 便于多个栈共享存储空间,提高内存利用率。
  • 几乎不会存在栈满的情况

2. 链栈的基本方法

(1) 链栈的入栈

在这里插入图片描述

链栈的结构代码

public class MyLinkedStack {

    static class Node{
    //单链表需要的next和val
        public Node next;
        public int val;	
	//构造方法
        public Node(int val) {
            this.val = val;
        }
    }
    //成员变量head
    public Node head;
   }

压栈/入栈

    public void push(int x) {
    //创建一个新节点
        Node node=new Node(x);
            //不管我的head是否为空,都可以将node的下一个结点指向head
            node.next = head;
            //head成为新结点
        	head=node;
    }
(2) 链栈的出栈

用变量n保存要删除结点得值,头节点指向下一个结点,返回n.
在这里插入图片描述

    public int pop() {
    //判空,如果为空返回-1
        if(empty()) {
            return -1;
        }
        //记录头节点的值
        int n=head.val;
        //头指针指向下一个结点
        head=head.next;
        return n;

    }

3. 对比链栈和顺序栈

链栈的进栈push和出栈pop操作都很简单,时间复杂度均为O(1)。

对比一下顺序栈与链栈,它们在时间复杂度上是一样的,均为O(1)。对于空间性能,顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费的问题,但它的优势是存取时定位很方便,而链栈则要求每个元素都有指针域,这同时也增加了一些内存开销,但对于栈的长度无限制。所以它们的区别和线性表中讨论的一样,如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些

6
6

四. 栈的应用——递归

1. 递归的定义

在这里插入图片描述
我们在写递归时需要注意的就是边界条件,一个递归必须要具有的就是边界条件,如果没有,那么递归将会一直进行下去,直到内存被栈满,最后程序崩溃。

我们用求数字的阶乘举例,例如我们想要求5的阶乘,那么我们可以写一个函数.

    public static int func(int n) {
        //递归打印n的阶层
        if(n==1) {
            return 1;
        }
        //每次返回当前的n*前一个值
        return n*func(n-1);
    }
    public static void main(String[] args) {
        System.out.println(func(5));
    }

如果用画图解释就是:

在这里插入图片描述
必须注意递归模型不是能是循环定义的,其必须满足下面的条件

  • 递归表达式(递归体)
  • 边界条件(递归出口)

递归的优点就是能够将原始问题转化为属性相同单规模较小的问题。
在递归调用的过程中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储,递归次数过多容易造成栈溢出等。而其效率不高的原因是递归调用过程中包含很多重复的计算。

如下图:
在这里插入图片描述

如图可知,程序每往下递归一次,就会把运算结果放到栈中保存,直到程序执行到临界条件,然后便会把保存在栈中的值按照先进后出的顺序一个个返回,最终得出结果。

五. 栈的应用——逆波兰表达式求值

逆波兰表达式求值也可以叫做后缀表达式求值,我们把平时所用的标准四则运算表达式,也就是例如 " ( A + B )* C / ( D - E ) "叫做中缀表达式,因为所有的运算符号都在俩数字之间。

表达式求值是程序设计语言编译中一个最基本的问题,它的实现是栈应用的一个典型范例,中缀表达式不仅依赖运算符的优先级,而且还要处理括号。

相反:对于后缀表达式,它的运算符在操作数的后面,在后缀表达式中已经考虑了运算符的优先级,没有括号,只有操作数和运算符。例如上述讲到的中缀表达式( A + B )* C / ( D - E ),它对应的后缀表达式就是A B + C * D E - /。

后缀表达式计算规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈,进项运算,运算结果进栈,一直到最终获得结果。

后缀表达式 A B + C * D E - /求值的过程需要 步,如下表所示:

在这里插入图片描述

六. 栈的应用——中缀表达式转后缀表达式

前面已经对中缀表达式进行了大概了解,也就是所有的运算符号都在两数字的中间,现在我们的问题就是中缀到后缀的转化。

规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。

例:将中缀表达式a + b − a ∗ ( ( c + d ) / e − f ) + g 转化为相应的后缀表达式。

分析:需要根据操作符的优先级来进行栈的变化,我们用icp来表示当前扫描到的运算符ch的优先级,该运算符进栈后的优先级为isp,则运算符的优先级如下表所示[isp是栈内优先( in stack priority)数,icp是栈外优先( in coming priority)数]。

我们在表达式后面加上符号‘#’,表示表达式结束。具体转换过程如下:

在这里插入图片描述

即相应的后缀表达式为a b + a c d + e / f − ∗ − g +。

从刚才的推导中你会发现,要想让计算机具有处理我们通常的标准(中缀)表达式的能力,最重要的就是两步:

  1. 将中缀表达式转化为后缀表达式(栈用来进出运算的符号)
  2. 将后缀表达式进行运算得到结果(栈用来进出运算的数字)

队列

一. 队列的基本概念

1. 队列的定义

队列(Queue) 只是允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。

如图:
在这里插入图片描述

进出队列如图

进队列:

在这里插入图片描述

出队列:

在这里插入图片描述
我们一般在医院看见的出号机就是通过队列来实现的,按顺序取号,先取号的就会先被叫到,这有个好处就是,省去了复杂的排队,在你领完号之后就可以找个地方休息了,坐等叫号就行。

队头(Front):允许删除的一端,又称队首。
队尾(Rear):允许插入的一端。
空队列:不包含任何元素的空表。

2. 队列的常见基本操作

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

二. 队列的顺序存储结构

1.顺序队列

队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针 front 指向队头元素,队尾指针 rear 指向队尾元素的下一个位置

队列的顺序存储类型可以描述为:

public class MyQueue {
    int []array;
    int front;//记录队头
    int rear;//记录队尾

    public MyQueue(int n) {
        //控制一次开辟多少内存
        array=new int[n];
    }
    public MyQueue() {
        //默认开辟四个内存
        array=new int[4];
    }

}

初始状态:(队列为空):front=rear=0
进栈操作:队不满时,先给队尾下标对应的数组赋值,再队尾指针加1。
出栈操作:队不为空,先取出队头的元素,再队头指针减1。

进栈操作如图:

在这里插入图片描述
出栈操作:

在这里插入图片描述

假溢出:队列出现“上溢出”,然而却又不是真正的溢出,所以是一种“假溢出”。

大家看下图:在我的队尾指针=5时,说明队列已经满了,但是在下标为0和1的位置还是空闲的,我们称这种现象为“ 假溢出 ”(所以一般的队列会使用循环队列或者链表实现)

在这里插入图片描述

2.循环队列

解决假溢出的方法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。

当队首指针front = array.length-1后,再前进一个位置就自动到0,这可以利用除法取余运算(%)来实现。

下面是循环队列需要用到的一些公式,后续介绍。


初始时:front =rear=0。
判空:front=rear
判满:(rear+1)%array.length
队首指针进1:front = (front + 1) % array.length
队尾指针进1:rear = (rear + 1) % array.length
队列长度:(rear - front + array.length) % array.length


循环队列如图(下图中下标应该是从0开始,小鱼不小心写成了1,但是对于判断下面的推论没什么影响):

在这里插入图片描述

我们需要思考一个问题:如何判断这个循环队列是空队列,还是满队列?

我们可以看到,在 rear==front 时,即使空队列又是满队列,这就很麻烦,该如何解决呢?

  • 方法(1):我们可以创建一个成员变量 size,只有当 size==队列长度时 ,才判满,当 size==0 为空队列。

  • 方法(2) :我们也可以牺牲一个空间

  • 方法(3):类型中增设tag 数据成员,以区分是队满还是队空。tag 等于0时,若因删除导致 front = = rear ,则为队空;tag 等于 1 时,若因插入导致 front == rear ,则为队满。

这里着重讲解第二种方法(在这里将下标更正为0)!

在这里插入图片描述

就是当我们的尾指针+1==头指针时(此时的判断不完全正确),说明满了,虽然这样会浪费一个空间,但是对于程序运行的效率和降低书写代码的难度都是有不错的效果的!

正确判满的公式应该是:(rear+1)%array.length==front

上图举例。此时我的rear=7,但是7+1==8并不等于front,但是这个队列明明确确的已经满了,解决方法就是 (rear + 1 ) % 数组长度,当我的rear=7时,(7+1)%8=0,又因为此时的 front=0,所以此时循环队列是满的。

再举个例子:

在这里插入图片描述

如上图:此时rear=1,并且此时的队列是满的,那我们就套公式(1+1)%8=2,此时front=2,说明队列是满的!

其实%的主要作用就是,每次当我的 rear 为数组最后一个元素的下标时,当他需要再前进一个位置时,便让他重新回到0下标。

判空rear== front
判满(rear+1)%array.length == front


求队列长度:上述的公式是 (rear - front + array.length) % array.length
怎么理解呢?依旧是看图,这里分为普通情况和特殊情况

普通情况&特殊情况:
在这里插入图片描述

特殊情况指的就是当我的差为负数时,通过 (rear-front+array.length)%array.length 这个公式,对于差是正数时没有影响,但是对于差是负数时,便可以求出正确的元素个数。

3. 循环队列的常见基本算法

(1)循环队列的顺序存储结构

class SqQueue{

    int []array;
    int front;//头指针
    int rear;//尾指针
    public SqQueue(int n) {
        //控制一次开辟多少内存
        array=new int[n];
    }
    public SqQueue() {
        //默认开辟四个内存
        array=new int[4];
    }
    
}

(2) 循环队列判空

    public boolean isEmpty() {
    //当相等时为空
        return rear==front;
    }

(3)求循环队列长度

    public int QueueLength() {
    //当
        return (rear-front+array.length)%array.length;
    }

(4)循环队列入队

	//判满
    public boolean full() {
    //公式如上
        return (rear+1)%array.length==front;
    }
    public void offer(int x) {
        if(full()) {
            return;
        }
        //先插入
        array[rear]=x;
        //当rear为数组的最后一个元素时,如果进一,就让他重新回到0结点
        rear=(rear+1)%array.length;
    }

(5)循环队列出队


    public int poll() {
        //如果为空还要删除就直接终止程序
        if(isEmpty()) {
            exit(0);
        }
        int n=array[front];
        //如果是数组的最后一个元素,那么进一就重新回到0下标
        front=(front+1)%array.length;
        return n;
    }

三. 队列的链式存储结构

1. 链队列

队列的链式存储结构表示为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表,只不过它只能尾进头出而已.

并且链队列的插入和删除的时间复杂度都是O(1),也可以通过双向链表实现。

如图:

在这里插入图片描述

2. 链队的常见基本算法

(1)链队列存储类型

class LinkQueue {
    
    static class Node{
        Node next;
        int val;
        public Node(int val) {
            this.val = val;
        }
    }
    public Node front;//头指针
    public Node rear;//尾指针
    
}

(2)链队列入队

在这里插入图片描述

    public void offer(int x) {
        Node node=new Node(x);
        if(front==null) {
            front=node;
        } else {
            rear.next=node;
        }
        rear=node;
    }

(3)链队列出队

出队操作时,就是头结点出队,将头结点的后继改为新的头节点结点,若新头节点指向 null ,此时链表为空需要让 rear 也指向 null , 避免野指针异常!
在这里插入图片描述

    public int poll() {
        if(front==null) {
            //如果头节点为空就说明没有结点
            return -1;
        }
        int n=front.val;
        front=front.next;
        //若头指针为空说明没有结点,避免空指针异常使rear也指向null
        if(front==null) {
            rear=null;
        }
        return n;
    }

链队列和循环队列的比较

对于循环队列与链队列的比较,可以从两方面考虑,从时间上,他们的基本操作都是常数时间O(1),不过循环队列是事先申请好空间,使用期间不释放,而对于链队列,每次申请和释放结点也会存在一些时间开销,如果入队频繁,则两者还是有细微差异,对于空间上来说,循环队列必须有一个固定的长度,所以就有了存储元素个数和空间上的浪费,而链表不存在这个问题,尽管它需要一个指针域,会产生一些空间上的开销,但是也可以接受,所以在空间上,链队更加的灵活。

总的来说:在可以确定队列最大值的情况下,建议使用循环队列,如果你无法预估队列的长度时,则用链表。

四. 双端队列

1. 定义

双端队列(deque) 是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。
如下图所示。其元素的逻辑结构仍是线性结构。将队列的两端分别称为前端和后端,两端都可以入队和出队。

在这里插入图片描述

五. java中集合的用法

在我们需要使用栈的时候我们可以通过如下代码来使用java中封装好的。

  Stack<Integer> stack=new Stack<>();

在这里插入图片描述

从上图中可以看到,Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的。

在这里插入图片描述

由于ArrayDeque和LinkedList都实现了Deque这个接口,所以可以通过这两个类来进行双端队列的实现。

在这里插入图片描述
在这里插入图片描述

Deque q1 = new ArrayDeque<>();//双端队列的线性实现
Deque q2 = new LinkedList<>();//双端队列的链式实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/145721.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(8)Qt中的自定义信号

目录 自定义信号需要遵循的规则 信号的发送 自定义信号的基本实现 使用一个父子窗口切换的小案例 Qt框架提供的信号在某些特定场景下是无法满足我们的项目需求的&#xff0c;因此我们还设计自己需要的的信号&#xff0c;同样还是使用connect()对自定义的信号槽进行连接。 自…

制造业ERP管理系统解决方案之销售管理

在企业的生存发展中&#xff0c;销售管理起到了重要的作用&#xff0c;它决定着企业发展的提速和效益的提升。做好销售管理工作&#xff0c;不仅可以推动企业资金有效运转&#xff0c;还可以使企业在稳定中高效发展&#xff0c;使企业价值最大化。而在制造企业销售管理中&#…

Leetcode.1658 将 x 减到 0 的最小操作数

题目链接 Leetcode.1658 将 x 减到 0 的最小操作数 题目描述 给你一个整数数组 nums 和一个整数 x 。每一次操作时&#xff0c;你应当移除数组 nums 最左边或最右边的元素&#xff0c;然后从 x 中减去该元素的值。请注意&#xff0c;需要 修改 数组以供接下来的操作使用。 如…

SHELL脚本学习 --- 第八次作业(安全脚本)

SHELL脚本学习 — 第八次作业 题目要求&#xff1a; 将密码输入错误超过4次的IP地址通过firewalld防火墙阻止访问 思路&#xff1a; 首先需要找到ssh密码输入错误超过四次的IP地址&#xff0c;需要到日志文件中找。 该日志文件为/var/log/secure。 找到之后通过正则匹配到密码输…

JavaEE高阶---SpringBoot 统⼀功能处理

一&#xff1a;什么是SpringBoot 统⼀功能处理 SpringBoot统一功能处理是AOP的实战环节。我们主要学习三方面内容&#xff1a; 统一用户登录权限验证&#xff1b;统一数据格式返回&#xff1b;统一异常处理。 二&#xff1a;统一用户登录权限验证 Spring 中提供了具体的实现…

通过后端代理实现Web搜索功能

大家好&#xff0c;才是真的好。 前面我们都在说使用Domino自带的视图搜索功能&#xff0c;这一篇也是&#xff0c;不过不是视图搜索&#xff0c;而是整个Notes数据库搜索&#xff0c;然后再将结果返回给浏览器网页呈现。 前提和前面两篇都是一样的&#xff0c;即Notes应用需…

Java 如何不使用 volatile 和锁实现共享变量的同步操作

前言 熟悉 Java 并发编程的都知道&#xff0c;JMM(Java 内存模型) 中的 happen-before(简称 hb)规则&#xff0c;该规则定义了 Java 多线程操作的有序性和可见性&#xff0c;防止了编译器重排序对程序结果的影响。 按照官方的说法&#xff1a; 当一个变量被多个线程读取并且至…

「数据密集型系统搭建」原理篇|数据类型不怕精挑细选

本篇围绕MySQL数据库的底层存储模型、列类型来聊聊数据库表设计及建模中要注意的事项&#xff0c;剖析最根源的底层物理存储文件&#xff0c;用最真实的数据剖析来证明和解答开发过程中的疑惑。 在一些技术谈资、面试沟通过程中&#xff0c;MySQL特别是我们常用的Innodb存储引擎…

JavaScript 作用域

文章目录JavaScript 作用域JavaScript 作用域JavaScript 局部作用域JavaScript 全局变量JavaScript 变量生命周期函数参数HTML 中的全局变量你知道吗?JavaScript 作用域 作用域可访问变量的集合。 JavaScript 作用域 在 JavaScript 中, 对象和函数同样也是变量。 在 JavaScr…

ONES X 海银财富|以敏捷流程管理,创新金融服务平台

近日&#xff0c;ONES 签约财富管理行业领跑者——海银财富&#xff0c;助力海银财富落地敏捷流程管理&#xff0c;打造从需求到交付的一体化平台&#xff0c;快速接受业务方的反馈&#xff0c;进行金融平台的迭代与优化。海银财富管理有限公司&#xff08;以下简称海银财富&am…

拆机详解:1968年军用集成电路计算机 高级货赢在做工

halo大家好&#xff0c;这里是一天更两篇的Eric。 今天我在网上偶然看到一个拆军用计算机的&#xff0c;正好给你们分享一下。这可是1970年左右为了F4战斗机敌我识别系统打造的&#xff0c;虽说比之前说的Macintosh更加的挤也更大&#xff0c;不过做工够扎实。 上图&#xff…

centos8安装RabbitMQ和erlang

RabbitMQ 消息队列MQ RabbitMQ简称MQ是一套实现了高级消息队列协议的开源消息代理软件&#xff0c;简单来说就是一个消息中间件。是一种程序对程序的通信方法&#xff0c;其服务器也是以高性能、健壮以及可伸缩性出名的Erlang语言编写而成为什么使用MQ 在项目中&#xff0c;…

QSyntaxHighlighter

一、描述 此类用于自定义语法高亮显示规则&#xff0c;是用于实现 QTextDocument 文本高亮显示的基类。 要自定义语法高亮显示&#xff0c;必须子类化 QSyntaxHighlighter 并重新实现 highlightBlock()。此函数将在合适的时机自动被调用。 highlightBlock() 函数将格式设置应…

SOFA Weekly|SOFANews、本周贡献 issue 精选

SOFA WEEKLY | 每周精选 筛选每周精华问答&#xff0c;同步开源进展欢迎留言互动&#xff5e;SOFAStack&#xff08;Scalable Open Financial Architecture Stack&#xff09;是蚂蚁集团自主研发的金融级云原生架构&#xff0c;包含了构建金融级云原生架构所需的各个组件&#…

Rasa 3.x 学习系列-Rasa [3.4.0] - 2022-12-14新版本发布

Rasa 3.x 学习系列-Rasa [3.4.0] - 2022-12-14新版本发布 任何人都可以学习Rasa之优秀Rasa学习资源推荐 欢迎同学们报名Gavin老师的Rasa系列课程,任何人都可以学习Rasa之优秀Rasa学习资源推荐: 1.NLP on Transformers高手之路137课 2 .Rasa 3.X 智能对话机器人案例开发硬核…

五、k8s pod详解

文章目录1 pod介绍1.1 pod 定义2 pod配置2.1 基本配置2.2 镜像拉取2.3 启动命令2.4 环境变量2.5 端口设置2.6 资源配额3 Pod生命周期3.1 创建和终止3.2 初始化容器3.3 钩子函数3.4 容器探测3.5 重启策略4 Pod调度4.1 定向调度4.2 亲和性调度5 污点和容忍5.1 污点&#xff08;Ta…

做短视频必须了解的6个问题,你知道几个答案呢?

做短视频必须了解的6个问题&#xff0c;你知道几个答案呢&#xff1f; 最近好多朋友问了视频运营和创作的问题&#xff0c;把其中六个有代表性的问题和答案汇总在一起&#xff0c;公开给大家&#xff0c;希望对大家有所帮助。 1、账号被限流了怎么办&#xff1f; 随手拍十条…

聚观早报 | 小米同时研发两款车;谷歌计划向印度最高法院上诉

小米同时研发两款车&#xff1a;谷歌计划向印度最高法院上诉&#xff1b;苹果AR/MR头显部件延迟发货&#xff1b;2022年特斯拉在德汽车销量激增&#xff1b;纽约市教育部门禁止访问 ChatGPT 小米同时研发两款车小米第一款车为中型溜背式轿车&#xff08;内部代号 Modena 摩德纳…

欧科云链任煜男:推动区块链创新,切忌陷入“过度金融化”的桎梏

FTX破产轰动全球&#xff0c;揭示的是监管出现问题。FTX作为行业龙头&#xff0c;持有多国牌照却依然“暴雷”&#xff0c;未来到底应如何监管加密资产行业&#xff1f;针对这一问题&#xff0c;欧科云链控股(01499.HK)公司执行董事、董事局主席兼行政总裁任煜男近日在接受香港…

【菜菜的CV进阶之路 - 深度学习环境搭建】windows+ubuntu20.04双系统安装

新学期&#xff0c;配了台新电脑&#xff08;双路2080ti5800x64GB内存500GB固态2TB机械&#xff09;&#xff0c;师兄忙于毕设&#xff0c;没给装ubuntu&#xff0c;自己装一下咯~ 一、前期准备 1、空U盘一个 2、Ubuntu 20.04 LTS镜像 3、启动U盘制作软件&#xff08;我用的…