当前位置: 首页 > news >正文

Java数据结构:Stack(栈)Queue(队列)

一、栈(Stack)

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

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

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

就像在叠一次性杯时,第一个叠的最终是在最底部,最后一个叠的在最上面;而在拿一次性杯子的时候,都会从最上部拿一个杯子。

栈的使用

我们先看看Stack栈的源码:它是一个泛型类并继承于Vector类,那么它除了可以使用自身的方法外,也可以使用Vector类中的方法。

Stack类中的方法:

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

我们可以使用Stack泛型类进一步验证入栈和出栈的过程:

输出结果:

栈的模拟实现

从上图可以看出,Stack继承了Vector,而Vector和ArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的。因此,我们在模拟实现Stack的时候,采用顺序表的思路来实现

思路:首先创建一个MyStack类,表示模拟实现Stack的一个类,这个类中包含Stack中的方法,再写一个IStack接口,让MyStack类实现这个接口,然后实现该接口中的抽象方法。

1.IStack接口的完整框架

在这个接口中,有Stack类中的方法,例如push方法等,MyStack类会继承该接口,然后重写这些方法。

public interface IStack {//将val入栈,并返回valint push(int val);//将栈顶元素出栈并返回int pop();//判断栈是否为空boolean empty();//获取栈顶元素int peek();//获取栈中有效元素的个数int size();
}

2.MyStack类的完整框架

以顺序表的思路实现该类,我们知道顺序表一般情况下是采用数组存储的,因此,我们需要在该类中定义一个数组(这里以int类型为例),用来存放栈中的元素,另外,定义一个变量usedSize,表示栈中有效元素的个数;然后构造一个无参构造方法,用来对数组进行初始化。

不要忘记让该类实现IStack接口,然后实现抽象方法。

public class MyStack implements IStack {public int[] array;public int usedSize;public MyStack() {this.array = new int[10];}//重写接口中的抽象方法public int push(int val) {}……………………
}

现在,类和接口的整体框架已经说明完毕,现在,详细说明在MyStack类中如何重写抽象方法。

[1] push() 入栈

该方法的作用是向栈中添加元素(从栈顶入栈),并返回添加的元素val。

不过,在入栈之前,要先判断栈是否已经满了,如果满了,返回true,对栈进行扩容,然后再入栈;否则,返回false,直接将元素入栈,写一个方法isFull()来实现判满操作(如果有效元素个数usedSize的值等于array数组的长度,表示栈已经满了)。

扩容:使用Arrays类中的copyOf()方法来进行扩容操作,该方法的作用:用于复制数组并指定新数组长度。利用copyOf方法复制原数组并指定这个新数组的长度是原数组长度的2倍(以2倍数增长),最后将这个新数组赋值给原数组array,这样就表示增容成功。

(1.1) isFull() 判断栈是否已满
public boolean isFull() {return usedSize == array.length; //如果相等,表示满了,返回true,否则返回false
}
(1.2) push()
public int push(int val) {if(isFull()) {//满了,进行扩容this.array = Arrays.copyOf(this.array,2*this.array.length); }array[usedSize++] = val;//将val入栈,有效元素usedSize个数增加return val;
}
[2] empty() 判断栈是否为空

如果有效元素个数usedSize的值等于0,表示栈为空,返回true,否则返回false。

public boolean empty() {return usedSize == 0;
}
[3] pop() 出栈

该方法的作用是将栈顶元素出栈(从栈顶出栈),并返回出栈的栈顶元素。

注意:每次将栈顶元素出栈后,栈中的有效元素个数也会相应的减少,也就是说,原来的栈顶元素出栈后,该元素的前一个元素就会变成新的栈顶元素。

总结为一句话就是:将栈顶元素出栈并返回,会删除栈顶元素

在出栈之前,先对栈判空,这里写一个EmptyStackException-空栈异常类,如果栈为空,抛出异常,不能进行出栈操作。

(3.1) EmptyStackException异常类
public class EmptyStackException extends RunTimeException {public EmptyStackException() {super();} public EmptyStackException(String message) {super(message);}
}
(3.2) pop()

用变量val来接受栈顶元素(因为要返回出栈的元素,所以在删除之前保存一下),然后usedSize直接--,即表示已经删除了该栈顶元素。

例如:现在栈中有1,2,3,4 这4个元素,它们的下标分别是0~3,此时要删除栈顶元素4,即下标为3的位置的元素,先使用val接收后,usedSize--,表示删除4后,有效元素个数为3个,这3个元素对应的下标分别为0~2;下次想要增加一个5,那么usedSize++,那么有效元素个数又变回4,对应的下标为0~3,那么5就直接覆盖了4,成为新的下标为3位置上的元素。

public int pop() {if(empty()) {//如果栈为空,无需出栈操作,抛出异常throw new EmptyStackException("空栈");}int val = array[usedSize-1];usedSize--;return val;
}
[4] peek() 获取栈顶元素

该方法和pop()方法的做法一模一样,但是peek()方法只是获取栈顶元素,即只是想要知道栈顶元素是什么而已,不会删除栈顶元素。

一句话总结就是:获取栈顶元素,但是不删除栈顶元素

public int peek() {if(empty()) {throw new EmtpyStackException("空栈");}return array[usedSize-1];//返回栈顶元素
}
[5] size() 获取栈中有效元素个数

栈的有效元素个数就是usedSize的值,那么直接返回usedSize即可。

public int size() {return this.usedSize;
}

到这里,Stack的模拟实现圆满完成。

问题1:我们模拟实现的栈采用的是顺序表的思路,那么,使用单链表是否可以实现栈?

—— 采用顺序表实现的栈,叫做顺序栈,入栈和出栈的时间复杂度都是O(1)。那么采用单链表:

如果采用尾插/删法(单链表的尾节点位置就类似于栈顶,入栈或者出栈的那一固定端),那么入栈的时间复杂度为O(n),因为需要遍历链表找到尾节点,然后再进行入栈操作,而如果有last引用标记尾节点,那么入栈的时间复杂度为O(1),但是,出栈操作就一定是O(n),因为又得重新遍历链表找到尾节点,然后再进行出栈操作。

如果采用头插/删法(单链表的头节点位置就类似于栈顶,入栈或者出栈的那一固定端),那么入栈的时间复杂度为O(1),同时出栈的时间复杂度也为O(1)。因此,如果采用单链表实现栈那么可以采用头插法的形式入栈和采用头删法的形式出栈

问题2:使用双链表是否可以实现栈?

—— 采用双向链表实现栈是完全可以的,不管是采用头插法还是尾插法,入栈和出栈的时间复杂度都是O(1)

采用单链表/双链表实现的栈,叫做链式栈

我们可以查看LinkedList类中的方法,发现它是包含push、pop、peek等方法的:

示例:

栈的应用

1.改变元素的序列

  • (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

分析

A选项✔,按照 1,4,3,2 出栈的过程:当1入栈后,又出栈了,此时出栈序列的第一个元素就是1,现在栈又变回空的,接着继续按照进栈的顺序进栈,此时栈内的元素是2,3,4,2在栈底的位置,4在栈顶的位置;最后将栈中的所有元素出栈(是从栈顶出栈的),那么此时出栈序列的顺序是 4,3,2,最终的出栈序列为 1,4,3,2。

B选项✔,按照 2,3,4,1 出栈的过程:当1入栈后,就是2入栈,而2在入栈后,又出栈了,因此,出栈的序列的第一个元素是2,此后的3,4也重复了2的操作后,出栈序列的顺序为2,3,4,最后再将最开始入栈的1也进行出栈,因此,最终的出栈序列的顺序为 2,3,4,1。

C选项❌,按照 3,1,4,2 出栈是不可能的:想要让3是出栈序列的第一个元素,那么要让1,2入栈后,才能让3入栈又出栈,那么1就不可能在3之后出栈,它们之间还隔了2。

D选项✔,按照 3,4,2,1 出栈的过程:先让1,2入栈后,3入栈后又出栈,然后4再入栈,此时栈中的元素序列为1,2,4,再将它们出栈,最终出栈序列为 3,4,2,1。

  • (2)一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )

分析:按照题目顺序依次入栈,然后依次出栈,那么元素出栈的顺序就是:E、D、C、B、A、5、4、3、2、1。

2.将递归转化为循环

比如:逆序打印链表

  • 递归方式
void printList(Node head) {if(head != null) {printList(head.next);System.out.print(head.val+" ");}
}
  • 循环方式——Stack实现

还是写一个printList方法,首先对链表进行判空,然后遍历链表,使用push()方法,将链表中的节点全部保存到栈中,全部入栈之后,此时的栈不为空,使用pop()方法,将栈中的元素出栈,此时的顺序与入栈的顺序刚好相反,即为逆序。

void printList(Node head) {if(head == null) {return;}Stack<Node> stack = new Stack<>();//将链表中的节点保存到栈中Node cur = head;while(cur != null) {stack.push(cur);cur = cur.next;}//将栈中的元素出栈while(!stack.empty()) {System.out.print(stack.pop().val+" ");}
}

栈、虚拟机栈和栈帧的区别

可以用一个简单的比喻来开始:

:就像一个书立或书堆,它决定了书只能从顶部放入或取出(先进后出)。

虚拟机栈:就像是分配给某个人的一个专属书堆。每个人(线程)都有自己的书堆,互不干扰。

栈帧:就是书堆里的每一本书。每本书代表一个任务(方法),书里的页记录了做这个任务需要的具体信息(局部变量、操作数等)。

-----------------------------------------------------------------------------------------------------------------------------

1.栈
这是一个通用的数据结构概念,与编程语言和硬件无关

核心特性:后进先出

操作:只有两个基本操作:

入栈/压栈:将元素放入栈顶。

出栈/弹栈:从栈顶取出元素。

类比:就像一摞盘子,你总是把新盘子放在最上面(压栈),也总是从最上面拿走盘子(弹栈)。

2.虚拟机栈
这是一个与程序运行相关的内存区域概念,通常特指在Java虚拟机这样的托管环境中。

是什么:它是JVM为每个线程单独创建的一块私有内存区域。

生命周期:与线程相同。线程启动时创建,线程结束时销毁。

作用:用于存储方法调用时的栈帧(即它内部存储着一个一个的栈帧)。它描述了Java方法执行的内存模型:每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

3. 栈帧
这是虚拟机栈的基本组成单位,是方法级别的。

是什么:是用于支持虚拟机进行方法调用和方法执行的数据结构。

生命周期:与方法调用相同。方法被调用时创建栈帧并入栈,方法执行结束时(无论是正常返回还是异常)栈帧出栈。

内部结构:一个栈帧通常包含以下几个部分:

局部变量表:一个数字数组,用于存储方法参数和方法内部定义的局部变量。

操作数栈:一个后进先出的栈,用于执行字节码指令。方法执行过程中的计算、参数传递等操作都在这里进行。

动态链接:指向运行时常量池中该栈帧所属方法的引用。这是为了支持方法调用过程中的动态绑定(多态)。

方法返回地址:方法正常退出或异常退出时,程序计数器应该返回的位置,以便调用者方法能继续执行。

一些附加信息:虚拟机实现可能会添加一些额外信息,如对调试、性能收集的支持等信息。

二、队列(Queue)

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的原则。

入队列:进行插入操作的一端称为队尾(Tail/Rear)。

出队列:进行删除操作的一端称为队头 (Head/Front)。

就像人在排队结账一样,每次要排队时都是从队尾的位置开始排进队伍里,结账时是排在队头的人开始的。

比较:队列是遵循先进先出的原则,而栈是遵循后进先出的原则;栈是只能从固定的一端插入和删除数据,即在栈顶进行出栈和入栈,而队列是只允许在一端插入数据,在另一端删除数据,即在队尾入队列,在队头出队列,两头分工明确。

队列的使用

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

看Queue接口的源码,它继承与Collection接口,那么它除了可以使用自身的方法外,还可以使用Collection类中的方法,如size()、isEmpty()等方法。

Queue中的方法:

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

在Queue接口中,还有add()、remove()、element()方法,这些的功能分别和offer()、poll()、peek()一样,但是我们通常使用的是上述表格上的方法。

注意:如果想要使用Queue接口,即Queue在实例化时,可以实例化LinkedList的对象,因为LinkedList实现了Queue接口

LinkedList实现了 Deque 接口 ,而 Deque 接口又继承扩展了 Queue 接口:

如下图,LinkedList类中的一些方法:

我们可以使用Queue接口进一步验证入队列和出队列的过程:

输出结果:

队列的模拟实现

队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有 两种:顺序结构链式结构

思考:队列的实现使用顺序结构还是链式结构好?

首先我们先使用链式结构来实现队列,这又分为两种:单链表实现和双链表实现。

  • 单链表:如果使用单链表实现队列,那么可以先遍历链表,使用last引用对链表的尾节点进行标记,然后入队列采用尾插法,而出队列则采用头删法(删除头节点)。注意:就算有尾节点的标记,也不能将入队列和出队列的方式倒过来,即采用头插法入队,尾删法出队,因为这样的话,删除还需要找到最后一个节点的前一个节点(需要实时更新尾节点)。
  • 双链表:如果使用双链表实现队列,那么采用 尾插法入队、头删法出队 或者 头插法入队、尾删法出队 都可以。出队和入队的时间复杂度都为O(1)。

我们使用双向链表来模拟实现队列思路:创建一个MyQueue类,表示模拟实现Queue接口的一个类,在这个类中,模拟实现Queue中的方法。

MyQueue类完整框架

public class MyQueue {static class ListNode {public int val;public ListNode prev;public ListNode next;public ListNode(int val) {this.val = val;}}public ListNode first;//队头public ListNOde last;//队尾public int usedSize;//记录队列中有效元素个数//Queue接口中方法的实现public boolean isEmpty() {}//…………………………
}

接下来详细说明MyQueue类中Queue中方法的实现方式。

[1] isEmpty() 检测队列是否为空

如果队列的有效元素个数usedSize等于0,或者它的头节点/队头等于null,那么就代表队列为空。

public boolean isEmpty() {return usedSize == 0;//return first == null;
}
[2] offer() 在队尾入队列

isEmpty() 检测队列是否为空,如果为空,那么就将要入对的 node新节点的val值作为队列的第一个数据;如果不为空,就往队列的尾部添加数据。添加完成,uesdSize++,返回true。

public boolean offer(int val) {ListNode node = new ListNode(val);if(isEmpty()) {first = last = node;}else {last.next = node;node.prev = last;last = last.next;}usedSize++;return true;
}
[3] poll() 在队头出队列

如果队列为空,则返回-1(这里不需要像栈等用异常类进行判空,因为源码中的poll也没有);如果不为空,则删除队列中的队头位置数据:first往后走,在删除前先保存一下队头节点的数据,如果这个新的first不是空的话,也就是队列中不是只有一个节点的情况,则需要将first的prev引用改成指向null,然后usedSize--,返回原队头节点的数据。

public int poll() {if(isEmpty()) {return -1;}int val = first.val;first = first.next;if(first != null) { //如果此时的新的队头节点不为空,即表示队列中不是只有一个元素first.prev = null;}usedSize--;return val;
}
[4] peek() 获取队头元素

如果队列为空,则返回-1;如果不为空,则直接返回队头元素。

public int peek() {if(isEmpty()) {return -1;}return first.val;
}
[5] size() 获取队列有效元素个数

直接返回usedSize即可。

public int size() {return usedSize;
}

到这里,就完成了队列的模拟实现。

在使用或者实现队列的过程中,我们一直使用的是链式结构,接下来,尝试使用顺序结构实现,即采用数组如何实现队列

如下图,定义一个数组,使用变量front、rear来分别记录数组头和尾的位置,往数组中添加数据(入队列)后,在将数组中的一些数据出队列,此时的front位置跟着更新,再次添加数据,直到数组空间满,但是数组前面出队列后留出来的空间并没有用到:

那么,如何让数组能够识别这些空余的空间添加数据?

—— 答案就是将这个数组围成一个环/圆圈,即队头和队尾连接起来形成一个环形队列,这样的队列叫做循环队列

循环队列

环形队列通常使用数组实现。

向循环队列中存放数据,

问题1:rear 或者 front 下标从 7 位置到 0 位置怎么做到?

解决方法:通过公式  (rear+1)%array.length 做到,示例,此时rear是下标为5的位置,那么(5+1)%8=6,走到下一个位置下标为6的位置,如果rear是下标为7的位置,那么(7+1)%8=0,就可以做到从下标7走到下标0位置。

问题2:如何判断空和满?(空和满的情况front和rear都在同一个位置)

  • 循环队列为空:只要front和rear相遇,即它们在相同位置的话(只要位置相同就代表空,无论它们在队列的哪一个下标位置),就表示空。
  • 循环队列满,有三种表示方法:

1.使用一个标志位 size

  • 这种方法通过维护一个额外的变量来记录当前队列中的元素个数
  • 核心思想:用一个变量 size 来记录队列的实时长度。
  • 判断条件:
  • 队列:size == 0
  • 队列:size == capacity
  • 队列长度:直接返回 size 即可。
  • 入队操作:在 rear 移动后,执行 size++。
  • 出队操作:在 front 移动后,执行 size--。
  • 优点:逻辑非常直白,易于理解。不浪费存储空间。
  • 缺点:需要维护一个额外的变量,每次入队和出队操作都需要更新它。

2.使用一个标志位 tag

  • 这种方法通过一个布尔标志位来记录最后一次操作的类型,以此来判断状态。
  • 核心思想:用一个布尔变量 tag
  • 当执行一次 入队 操作时,我们将 tag 设置为  true,表示最后一次操作是“入队”。
  • 当执行一次 出队 操作时,我们将 tag 设置为 false,表示最后一次操作是“出队”。
  • 判断条件:
  • 队列空:(front == rear) && (tag == false)
  • (指针重合,且最后一次操作是出队,那肯定是把最后一个元素也拿走了,所以为空)
  • 队列满:(front == rear) && (tag == true)
  • (指针重合,且最后一次操作是入队,那肯定是因为放入一个元素导致 rear 追上了 front,所以为满)
  • 优点:不浪费存储空间。
  • 缺点:逻辑上比较复杂一些,需要同时判断指针和标志位。

3.浪费一个空间/保留一个位置

  • 这是最经典和直观的方法。
  • 核心思想:我们故意不使用数组中的最后一个位置当 rear 的下一个位置是 front 时,我们就认为队列已满
  • 判断条件:
  • 队列空:front == rear
  • 队列满:(rear + 1) % capacity == front
  • 队列长度:(rear - front + capacity) % capacity
  • 示例:假设队列容量 capacity 为 5(即数组长度为 5),那么实际只能存储 4 个元素。
  • 初始时 front = rear = 0,队列为空。
  • 当插入 4 个元素后,rear 指向第 4 个位置(索引3)。此时 (3 + 1) % 5 = 4,不等于 front (0),所以还能插入一个元素。
  • 插入第 5 个(实际能存的第 4 个)元素后,rear 移动到 4。此时 (4 + 1) % 5 = 0,等于 front (0),判定为队列已满。
  • 优点:逻辑清晰,实现简单,效率高。
  • 缺点:浪费了一个数组单元的存储空间。

我们采取第3种做法来表示满的情况

明白了循环队列的思路之后,我们来设计实现一个循环队列。

设计循环队列

循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间,但是使用循环队列,我们能使用这些空间去存储新的值。
该类中支持以下的操作:

  • MyCircularQueue(k): 构造器,设置队列长度为 k 。
  • Front: 从队首获取元素。如果队列为空,返回 -1 。
  • Rear: 获取队尾元素。如果队列为空,返回 -1 。
  • enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
  • deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
  • isEmpty(): 检查循环队列是否为空。
  • isFull(): 检查循环队列是否已满。
MyCircularQueue类 - 实现循环队列
public class MyCircularQueue {public int[] array;//定义数组存放数据public int front;//标记循环队列队头public int rear;//标记循环队列队尾//构造方法,初始化数组的长度public MyCircularQueue(int k) {array = new int[k+1];//是k+1的原因:在判满的时候,采取浪费一个空间的做法,那么要设置的队列长度是3的话,需要设置长度为4,确保有一个空间可浪费又不会占用要存放的数据个数}}

接下来解说该类中的方法如何实现。

[1] isFull() 判断循环队列是否已满

判满采取 浪费一个空间/保留一个位置 的做法 ,那么满的情况就是——如果rear的下一个位置等于front( (rear+1)%array.length ),就说明已经满了。

public boolean isFull() {return (rear+1)%array.length == front;
}

[2] isEmpty() 判断循环队列是否为空

如果rear 和 front 位置相同,则代表队列为空(只要位置相同就代表空,无论它们在队列的哪一个下标位置)。

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

[3] enQueue() 入队列

如果队列已满,则返回false;否则,向队尾插入数据,插入完成之后rear往后走(往后走的方式依然使用公式法),然后返回true。

public boolean enQueue(int value) {if(isFull()) {return false;}array[rear] = value;rear = (rear+1)%array.lenght;//rear往后走return true;
}

[4] deQueue() 出队列

如果队列为空,则返回false;否则,从队头删除数据,即front往后走,然后返回true。

无需担心原来front位置上的元素没有真正删除,如果后面再进行入队列操作,而且刚好要插入的位置是原front位置,那它原来存放的数据最终会被覆盖掉。

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

[5] Front() 获取队头元素

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

[6] Rear() 获取队尾元素

我们知道,尾元素是用 array[rear-1]  (队列走到最后的时候,是在浪费的那个空间的位置上,那么它实际的最后一个元素就是 rear-1 ),但是,这里有一个特殊的情况,如果尾元素的下标是0位置时,0-1就变成了-1,而下标是不允许负数的,因此,需要单独处理当尾元素在0下标位置时的情况:采用三目操作符来解决——如果rear==0,则返回队列长度-1的位置,如果不等于0,则直接减1即可。

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

以上,就是循环队列的全部内容。

三、双端队列(Deque)

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。 那就说明元素可以从队头出队和入队,也可以从队尾出队和入队

也就是说,双端队列Deque可以被当作队列和栈来使用。甚至是顺序表、链表使用(Deque中的方法支持)。

看Deque的源码,它是一个接口,继承于Queue接口:

那么,实现Deque,可以通过实例化LinkedLIst的对象实现

Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

认识另一个双端队列 ArrayDeque,它实现了 Deque 接口,是一个基于数组(循环数组)的双端队列

  • ArrayDeque作为双端队列使用:ArrayDeque类也可以实现Deque接口,是双端队列的线性实现:
ArrayDeque<Integer> deque = new ArrayDeque<>();//双端队列的线性实现
  • ArrayDeque作为队列使用:Queue除了通过LinkedLIst实现,还可以通过ArrayDeque实现:
ArrayDeque<Integer> queue = new ArrayDeque<>();
  • ArrayDeque作为栈使用:可以代替Stack类实现栈,由于 Stack 基于 Vector,性能较差,ArrayDeque 的 push() 和 pop() 方法更适合实现栈:
ArrayDeque<Integer> stack = new ArrayDeque<>();
http://www.dtcms.com/a/537492.html

相关文章:

  • Python中的列表推导式、字典推导式和集合推导式的性能和应用场景?
  • Spring全家桶面试题, 只补充细节版本
  • 做网站可以挣钱吗微信公众平台网页版登录
  • 第2章-类加载子系统
  • 网站左侧悬浮导航芜湖学校网站建设电话
  • PyTorch2 Python深度学习 - 简介以及入门
  • 定制版网站建设费用湘潭网站建设湘潭振企专业
  • 自己做的小网站如何发布网络营销案例范文
  • 单体架构中的事件驱动架构:Java应用程序的渐进式重构
  • 成都网站开发的公司吉安县规划建设局网站
  • 有了域名怎么建设网站淘宝客网站是怎么做的
  • 铁岭做网站大学网页设计与制作教程
  • 工商工事上哪个网站做西安网站制作公司排名
  • 机关单位网站建设申请公司简介模板图片
  • 【Delphi】操纵EXE文件中的主图标(MAINICON)
  • 彭州建设网站陕西省建设执业资格注册中心网站
  • 济南企业上云网站建设如何让网站自适应手机
  • nginx基础入门篇-nginx部署-Yum
  • Leetcode每日一练--43
  • Nacos 配置中心:动态配置管理
  • cpp / c++零基础两周速成教学
  • 免费微场景制作网站深圳外贸公司有哪些公司
  • Selenium定位元素的方法css和xpath的区别
  • 什么网站可以自学ps做贵宾卡互联网营销师
  • 建站公司 深圳巴西网站后缀
  • LeetCode 3. 无重复字符的最长子串解析
  • 给你一个新的网站怎么做2017网站备案抽查
  • 杭州网站做的好公司公司品牌网络推广方案
  • PyTorch Geometric 图神经网络实战利器
  • 《深入浅出统计学》学习笔记(一)