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

数据结构(Java)实现:栈和队列

文章目录

  • 1. 栈的模拟实现
    • 1.1 普通栈的模拟实现
    • 1.2 泛型栈的模拟实现
  • 2. 栈的介绍
  • 3. 栈的使用
  • 4. 栈的应用场景
    • 4.1 改变元素的序列
    • 4.2 将递归转换为循环
    • 4.3 使用栈解题
  • 5. 栈的链表实现
  • 6. 队列概念
  • 7. 队列的使用
  • 8. 模拟队列的实现
    • 8.1 链式队列
    • 8.2 顺序队列
  • 9. 双端队列

1. 栈的模拟实现

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

1.1 普通栈的模拟实现

在这里插入图片描述
栈的模拟实现没有顺序表和链表难,实现代码和对应的注释如下。

public class MyStack {int[] elem;int usedSize=0;public MyStack(){elem=new int[10];}//入栈public void push(int e){//判满,如果满了扩容if(isFull()){elem= Arrays.copyOf(elem,elem.length*2);}//入栈elem[usedSize]=e;usedSize++;}//判满public boolean isFull(){return usedSize==elem.length;}//出栈public int pop(){//判空,如果为空返回-1if(empty()){return -1;}//出栈后元素个数减一usedSize--;return elem[usedSize];}//查看public int peek(){//判空,如果为空返回-1if(empty()){return -1;}//peek只返回,不删除return elem[usedSize-1];}//判空public boolean empty(){return usedSize==0;}
}

测试代码

    public static void main(String[] args) {MyStack s=new MyStack();s.push(1);s.push(2);s.push(3);System.out.println(s.pop());System.out.println(s.peek());System.out.println(s.peek());}

1.2 泛型栈的模拟实现

要使栈变成泛型,仅需改变数据类型。

public class MyStack2 <E>{public Object[] elem;//泛型的本质是Object类public int usedSize = 0;public MyStack2(){elem=new Object[10];}public void push(E e){if(isFull()){elem= Arrays.copyOf(elem,elem.length*2);}elem[usedSize]=e;usedSize++;}public boolean isFull(){return usedSize==elem.length;}//出栈public E pop(){//返回类型为Eif(empty()){return null;}usedSize--;return (E)elem[usedSize];//类型转换}public E peek(){//返回类型为Eif(empty()){return null;}return (E)elem[usedSize-1];//类型转换}public boolean empty(){return usedSize==0;}
}

测试代码

    public static void main(String[] args) {MyStack2<String > s = new MyStack2<>();s.push("hello");s.push("zzuli");s.push("welcome");s.push("to");s.push("zzuli");System.out.println(s.pop());System.out.println(s.pop());System.out.println(s.peek());System.out.println(s.peek());}

2. 栈的介绍

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

3. 栈的使用

在这里插入图片描述

    public static void main(String[] args) {Stack<Integer> s=new Stack<>();s.push(1);//入栈s.push(2);//入栈s.push(3);//入栈s.push(4);//入栈s.push(5);//入栈System.out.println(s.size());//栈的数据个数System.out.println(s.pop());//出栈System.out.println(s.pop());//出栈System.out.println(s.peek());//查看栈顶元素System.out.println(s.peek());//查看栈顶元素if(s.empty()){//判空System.out.println("栈空");}else{System.out.println("栈不为空");}}

4. 栈的应用场景

4.1 改变元素的序列

在这里插入图片描述
答案:C、B

4.2 将递归转换为循环

当我们逆序打印单向链表时,可以通过递归的方式打印。

// 递归方式
void printList(Node head){//传入链表的头结点if(null != head){printList(head.next);//递归System.out.print(head.val + " ");}
}

我们也可以利用栈的方式,将链表的节点入栈,然后出栈。就可以实现逆序打印。

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

4.3 使用栈解题

  1. 括号匹配
  2. 逆波兰表达式求值
  3. 出栈入栈次序匹配
  4. 最小栈

上面四题题解链接

5. 栈的链表实现

Java集合类的Stack底层是一个数组。这种栈叫做顺序栈。那么是否可以使用链表来实现栈呢?
在这里插入图片描述
如果那单链表来实现栈,最好使用链表头部进行入栈和头栈,这样时间复杂度为O(1)。相反,如果从链表的尾部入栈和出栈,时间复杂度为O(n)(此时需要遍历链表才能实现入栈和出栈)。
在这里插入图片描述
如果使用双向链表,从链表的头或者尾进行入栈和出栈,时间复杂度均为O(1)。因此,如果我们要使用链式栈,就可以选用双线链表来操作。
代码示例

    public static void main(String[] args) {LinkedList<Integer> stack=new LinkedList<>();stack.push(1);//入栈stack.push(2);stack.push(3);stack.pop();//出栈}

通过Ctrl查看LInkedListpush方法和pop方法可以发现,其本质是链表的头插和头删。
在这里插入图片描述
在这里插入图片描述

6. 队列概念

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

7. 队列的使用

在Java中,Queue是个接口,底层是通过链表实现的
在这里插入图片描述

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

注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。
在这里插入图片描述
上面是Queue接口的一些方法。add方法和offer方法都是用来新增元素的。remove方法和poll方法都是用来弹出元素的。element方法和peek方法都是用来获取队头元素的。那他们有什么区别呢?
在这里插入图片描述

注释翻译:
add方法

如果可以立即插入到此队列中,则在不违反容量限制的情况下插入指定的元素,成功后返回 true,如果当前没有可用空间,则引发 IllegalStateException。
参数:
e – 要添加的元素
返回:
true(由 Collection.add 指定)
抛出:
IllegalStateException – 如果由于容量限制而此时无法添加元素
ClassCastException – 如果指定元素的类阻止将其添加到此队列中
NullPointerException – 如果指定的元素为 null,并且此队列不允许 null 元素
IllegalArgumentException – 如果此元素的某些属性阻止将其添加到此队列中
offer方法
如果可以立即插入到此队列中,则将其指定的元素插入到此队列中,而不会违反容量限制。当使用容量受限的队列时,此方法通常比 add 更可取,后者只能通过引发异常来无法插入元素。
参数:
e – 要添加的元素
返回:
如果元素已添加到此队列中,则为 true,否则为 false
抛出:
ClassCastException – 如果指定元素的类阻止将其添加到此队列中
NullPointerException – 如果指定的元素为 null,并且此队列不允许 null 元素
IllegalArgumentException – 如果此元素的某些属性阻止将其添加到此队列中
在这里插入图片描述

注释翻译:
remove方法

检索和删除此队列的头部。此方法与 poll() 的不同之处仅在于如果此队列为空,它会引发异常。
返回:
此队列的头部
抛出:
NoSuchElementException – 如果此队列为空
poll方法
检索并删除此队列的头部,如果此队列为空,则返回 null。
返回:
此队列的头部,如果此队列为空,则为 null

在这里插入图片描述

注释翻译:
element方法

检索但不删除此队列的头部。此方法与 peek 的唯一区别在于,如果此队列为空,它会引发异常。
返回:
此队列的头部
抛出:
NoSuchElementException – 如果此队列为空
peek方法
检索但不删除此队列的头部,如果此队列为空,则返回 null。
返回:
此队列的头部,如果此队列为空,则为 null

使用代码示例

    public static void main(String[] args) {Queue<Integer> q=new LinkedList<>();q.offer(1);//入队列q.offer(2);q.offer(3);System.out.println(q.poll());//出队列System.out.println(q.poll());System.out.println(q.element());//查看第一个队列元素System.out.println(q.element());System.out.println(q.isEmpty());//队列判空}

8. 模拟队列的实现

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

8.1 链式队列

在这里插入图片描述
我们使用双向链表来实现链式队列。实现代码如下:

public class MyQueue {static class ListNode{//队列节点public ListNode prev;public ListNode next;public int val;public ListNode(int val){this.val=val;this.prev=null;this.next=null;}}public ListNode head;public ListNode last;//入队列public void offer(int val) {ListNode node = new ListNode(val);//创建节点//尾插法入队列if(head==null){head=node;last=node;}else {last.next=node;node.prev=last;last=node;}}//出队列public int poll(){if(head==null){return -1;}//头删法出队列int ret=head.val;if(head.next==null){head=last=null;}else {head=head.next;head.prev=null;}return ret;}//查看队头元素public int peek(){if(head==null){return -1;}//如果队头不为空,返回队头的值return head.val;}//判断队列是否为空public boolean isEmpty(){//如果头节点为null,队列为空return head==null;}
}

8.2 顺序队列

使用顺序表来实现队列,如果不移动元素的位置来进行出队列和入队列就会造成一个问题:假设first为队头(可以出数据的下标),last为队尾(可以存放数据的下标),入队过程中last到顺序表的最后一个位置,而first端元素在出队列,这样就会使队列的前一段空出来了而没有存储数据。示例图如下:
在这里插入图片描述
如果将last重新回到0下标的空间,这样会使队列不连续。因此,我们通过数组来实现环形队列。
在这里插入图片描述
环形队列的连续问题解决了,但是当队尾走到顺序表的尽头,此时我们一直让fist++或者last++可能会造成数组越界,我们可以在每次入队列或者出队列时加一个判断,当first==len或者last==len时让他们的下标设置为0,。我们也可以通过表达式来设置下标,first=(first+1)%len或者last=(last+1)%len,通过这个表达式就不用担心数组越界的问题了。
在这里插入图片描述
考虑完数组越界问题后,我们如何区分队列的空与满?单纯依靠头尾相遇并不能解决这个问题,因为环形链表头尾相遇时可能为空,也可能为满。我们有下面三种解决方案。题目:力扣循环队列。

  1. 使用usedSize,当usedSize==len时就为满。
class MyCircularQueue {public int[] elem;//存放队列元素public int first;//队列头public int last;//队列尾public int usedSize;//元素个数//构造方法public MyCircularQueue(int k) {elem=new int[k];this.usedSize=0;this.first=0;this.last=0;}//入队列 public boolean enQueue(int value) {if(isFull()){//判满,如果队列满了返回falsereturn false;}//元素入队列elem[last]=value;//队尾位置往后移一个last=(last+1)%elem.length;//元素个数加1usedSize++;//成功入队列,返回truereturn true;}//出队列public boolean deQueue() {if(isEmpty()){//判空,如果队列为空返回falsereturn false;}//出队列直接让对头往后移动一个first=(first+1)%elem.length;//元素个数减1usedSize--;//出队列成功,返回truereturn true;}//看队头元素public int Front() {if(isEmpty()){//判空,如果为空返回-1return -1;}//不为空,first下标即为队头元素return elem[first];}//看队尾元素public int Rear() {if(isEmpty()){//判空,如果为空返回-1return -1;}int index;if(last==0) {//如果last为0,队尾元素下标就为elem.length-1index = elem.length-1;}else {//否则,队尾元素下标为last-1index = last-1;}//返回对应下标的元素return elem[index];}//判空public boolean isEmpty() {//如果usedSize为0,队列尾空return usedSize==0;}//判满public boolean isFull() {//如果usedSize等于数组长度队列为满return usedSize==elem.length;}
}
  1. 使用一个boolean类型的标记,。
class MyCircularQueue {public int[] elem;//存放队列元素public int first;//队列头public int last;//队列尾public boolean flag;//标记//构造方法public MyCircularQueue(int k) {elem=new int[k];this.flag=false;//flag为false时,队列不满this.first=0;this.last=0;}//入队列public boolean enQueue(int value) {if(isFull()){//判满,如果队列满了返回falsereturn false;}//元素入队列elem[last]=value;//队尾位置往后移一个last=(last+1)%elem.length;if(last==first){//入队列时,last走完一圈回到first队列就满了flag=true;}//成功入队列,返回truereturn true;}//出队列public boolean deQueue() {if(isEmpty()){//判空,如果队列为空返回falsereturn false;}//出队列直接让对头往后移动一个first=(first+1)%elem.length;flag=false;//出队列时,队列就不满,标志设为false//出队列成功,返回truereturn true;}//看队头元素public int Front() {if(isEmpty()){//判空,如果为空返回-1return -1;}//不为空,first下标即为队头元素return elem[first];}//看队尾元素public int Rear() {if(isEmpty()){//判空,如果为空返回-1return -1;}int index;if(last==0) {//如果last为0,队尾元素下标就为elem.length-1index = elem.length-1;}else {//否则,队尾元素下标为last-1index = last-1;}//返回对应下标的元素return elem[index];}//判空public boolean isEmpty() {//如果last==first且flag为false,队列为空return last==first&&!flag;}//判满public boolean isFull() {//如果队列为满,flag为truereturn flag;}
}
  1. 浪费一个空间,当first+1==last时,为满。
class MyCircularQueue {public int[] elem;//存放队列元素public int first;//队列头public int last;//队列尾//构造方法public MyCircularQueue(int k) {//因为浪费了一个空间,所以为了保证能够存够k个元素就需要开辟k+1个空间elem=new int[k+1];this.first=0;//队头this.last=0;//队尾}//入队列public boolean enQueue(int value) {if(isFull()){//判满,如果队列满了返回falsereturn false;}//元素入队列elem[last]=value;//队尾位置往后移一个last=(last+1)%elem.length;//成功入队列,返回truereturn true;}//出队列public boolean deQueue() {if(isEmpty()){//判空,如果队列为空返回falsereturn false;}//出队列直接让对头往后移动一个first=(first+1)%elem.length;//出队列成功,返回truereturn true;}//看队头元素public int Front() {if(isEmpty()){//判空,如果为空返回-1return -1;}//不为空,first下标即为队头元素return elem[first];}//看队尾元素public int Rear() {if(isEmpty()){//判空,如果为空返回-1return -1;}int index;if(last==0) {//如果last为0,队尾元素下标就为elem.length-1index = elem.length-1;}else {//否则,队尾元素下标为last-1index = last-1;}//返回对应下标的元素return elem[index];}//判空public boolean isEmpty() {//头等于尾时为空return last==first;}public boolean isFull() {//浪费一个元素,尾的下一个为头就视为满return (last+1)%elem.length==first;}
}

9. 双端队列

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。
那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。
在这里插入图片描述
Deque是一个接口,使用时必须创建LinkedList的对象。
在这里插入图片描述
在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口。

//下面这两个类也可以当做链表栈使用,也可以当做队列使用
Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

下面是Deque的一些方法。
在这里插入图片描述
栈和队列相关练习题链接


http://www.mrgr.cn/news/13738.html

相关文章:

  • QGraphicsView类介绍
  • WxPython可视化编辑器
  • 【软考】【多媒体应用设计师】媒体与技术
  • 类与ES6类之间的继承
  • stm32启动文件
  • 您应该让 ChatGPT 控制您的浏览器吗?
  • 【Java设计模式】Builder模式:在Java中清晰构建自定义对象
  • 【ceph学习】ceph如何进行数据的读写(2)
  • 恶意软件分析与防御:网络安全技术的新篇章
  • Element-plus组件库基础组件使用
  • 解决Spring的代理失效问题
  • 零基础学习Redis(8) -- list类型命令使用
  • ubuntu jammy vagrant 国内源
  • [设计模式之抽象工厂模式—— 家具工厂]
  • 基于深度学习的游客满意度分析与评论分析【情感分析、主题分析】
  • Java设计模式之建造者模式详细讲解和案例示范
  • HTML沙漏爱心
  • SQL进阶技巧:如何查询最近一笔有效订单? | 近距离有效匹配问题
  • 云计算概述
  • Spring MVC常用注解及用法