数据结构----队列和栈

       小编会一直更新数据结构相关方面的知识,使用的语言是Java,但是其中的逻辑和思路并不影响,如果感兴趣可以关注合集。

       希望大家看完之后可以自己去手敲实现一遍,同时在最后我也列出一些基本和经典的题目,可以尝试做一下。大家也可以自己去力扣或者洛谷牛客这些网站自己去练习,数据结构光看不敲是学不好的,加油,祝你早日学会数据结构这门课程。

        如果不是天才,就请一步一步来。

目录

队列

     相关题目


队列

 概述

        计算机科学中,队列(queue)是以顺序的方式维护的一组数据集合,规定只能在一端添加数据,在另一端移除数据。习惯来说,添加的一端称为队尾,移除的一端称为队头,就如同生活中的排队买商品,后面来排队的人都要排在队尾,哪怕你是超雄也不许插队,前面的人排在队头准备结账,结账走了就好比移除操作。

  实现

        队列可以通过链表实现也可以通过数组来实现,这里我两个都实现一下。

        不管我用链表还数组来模拟实现队列,队列这种数据结构它所支持的操作是不变的,比如在队尾插入值,从队头删除值,获得队头的值还是检查队列是否为空等等,这些操作的性质和名称都是不变的,所以我们可以采用面向接口编程,这里先写好一个队列的接口,这样也方便我接下来的实现,接口也就定义了四个方法,都是队列所具备的基本操作,E是规定泛型,不局限于基本数据类型。

// 队列接口
public interface Queue<E> {//向队列尾插入元素boolean push(E value);// 从队列头获取值并移除// 如果队列为空则返回nullE poll();// 获取队列头的值E peek();// 检查队列是否为空boolean isEmpty();
}
链表实现
    结构

        这里我们采用单向环形带哨兵节点的链表来模拟实现队列,选择环形链表是弥补上一篇文章留下的环形链表的实现,带哨兵节点是有利于我们处理一些队列为空时的特殊情况。

        我们首先来看一下单向环形链表的结构,节点类和之前都是一样的,由于是单向所以也就不需要prev指针。

// 节点类
private static class Node<E> {E value;Node<E> next;public Node(E value, Node<E> next) {this.value = value;this.next = next;}}

        接下来就是哨兵节点,我们需要设置一个哨兵节点head,但是我们还需要设置一个tail变量来指向链表的尾节点,因为这样可以使我们操作队列尾的时候更加方便,不需要从头遍历链表了。又因为是环形节点,一开始tail的next指针也要指向head,这些可以在构造函数中设置,因为哨兵节点随队列对象创建而创建。

// 链表实现队列
public class LinkedListQueue<E> implements Queue<E> {private static class Node<E> {E value;Node<E> next;public Node(E value, Node<E> next) {this.value = value;this.next = next;}}Node<E> head;Node<E> tail;public LinkedListQueue() {head = new Node<>(null, null);tail = head;tail.next = head;}
}
 添加元素

        添加元素我们直接就是完善接口中的push方法就好了,对于在单向环形链表中我们添加元素,由于是在尾部添加,所以我们只需要将哨兵尾节点的next指向新节点cur,然后将新节点cur赋给tail更新哨兵尾节点就好了。

        画的有点抽象,先看黑色再对着代码看红色的步骤。

        

    //向队列尾插入元素@Overridepublic boolean push(E value) {// 1.先把cur的next指针指向head,构成环Node<E> cur = new Node<>(value, head);// 2.把tail节点和cur节点连起来tail.next = cur;// 3.更新cur节点为tail节点tail = cur;return true;}
遍历

        遍历队列实则就是遍历单向环形链表,链表的第一个节点从head的next节点开始,然后因为是环形,所以当我们再一次来到head节点是证明我们已经走完一圈了。简单直接上代码

    // 遍历public void forEach() {// 因为哨兵节点没有意义,真正的第一个节点是哨兵的后一个节点Node p = head.next;// 链表成环,再一次来到head节点证明已经走过一圈while (p != head) {System.out.println(p.value);p = p.next;}}

        实现完遍历后我们就可以测试我们的添加方法了。

    @Testpublic void test01(){LinkedListQueue<Integer> queue = new LinkedListQueue<>();queue.push(1);queue.push(2);queue.push(3);queue.push(4);queue.push(5);queue.forEach();}

         测试显示通过,打印与预期相同没有问题。

        由于队列只允许操作头和尾,所以也就没有什么根据索引插入删除之类的操作,实现起来也是简单了许多。

队列是否为空 

        接下来我们来实现isEmpty这个方法,就是判断队列是否为空,这个方法特别简单,因为我们设置了一个tail变量来表示链表尾节点,只有当链表为空时tail才与head相等

    // 判断队列是否为空@Overridepublic boolean isEmpty() {return head == tail;}
获取队列头元素

        这个方法就是我们接口中的peek方法,这个方法要求我们返回队列头节点的值就行了,这也是非常节点,我们只要返回head的next指针指向的节点的值就行了。记得特殊处理队列为空的情况。

    //获取队列头元素@Overridepublic E peek() {if (isEmpty()) {// 队列为空返回nullreturn null;}return head.next.value;}

        测试一下也是没有问题的。 

删除队头元素

        poll方法与peek方法不同的是,poll方法还需要移除队头。删除一个链表的头节点相信你已经会了,head的next指针指向的就是待删除节点cur,我们只需要把head的next指向设置成cur的next指向的节点就好了,这样就把cur节点删除掉了。

        这时我们再考虑一下特殊情况,当队列中只有一个节点,那么cur的next指向就是head(因为是环形链表)这时我们删除也是没有问题的,但是这时我们要改变tail的值,因为只有一个节点删除之后我们要将tail设置成head,而其它情况的删除我们不用更改,因为删除第一个节点与tail指向的最后一个节点没有关系。

    //删除队列头元素@Overridepublic E poll() {if (isEmpty()) {// 如果链表为空返回nullreturn null;}// 待删除节点curNode<E> cur = head.next;// 删除cur节点head.next = cur.next;// 如果队列中只有一个节点特殊处理if (cur == tail) {tail = head;}return cur.value;}

        测试一下,没有问题。 

        用链表模拟实现队列我们也就完成了,另外有些地方会给队列设置一个size大小属性和capacity容量属性来表示队列有没有满,这里我就不带大家实现了,大家有兴趣可以自己加上去,这并没有多困难,相信你一定可以实现的。

环形数组实现
   概述

       环形数组就是一个首尾相连的数组,以前我们的数组是线性的,想象一下抽取其中一部分出来首尾相连也就构成了一个环形数组。主要优点就是不会存在空间浪费,性能更佳等。底层我们还是用一个数组来实现的,只是通过代码程序控制让它变成环形数组。

   结构

        环形数组的底层还是数组,另外由于我们模拟队列实现,所以我们还需要额外设置两个指针一个指向队列头,一个指向队列尾。另外我们还可以提供一个带参数的构造函数,由外界自己设置环形数组的大小,当然这里也可以我们自己设置,这都不是重点。

public class ArrayQueue<E> implements Queue<E> {//环形数组private E[] array;//队列头指针private int head = 0;//队列尾指针private int tail = 0;//构造函数根据给的容量参数来创建数组public ArrayQueue(int capacity) {array = (E[]) new Object[capacity + 1];}}
队列是否为空

        判断队列是否为空也是非常的简单,和链表实现一样,我们只需要判断head是否等于tail,因为只有当链表为空时head才等于tail。

    //判断队列是否为空@Overridepublic boolean isEmpty() {return head == tail;}
队列是否满

        由于我们对外提供了一个设置容量的构造方法,所以我们在使用时也得注意不能当队列满了之后我们还继续添加元素。所以我们要判断一下队列是否满,在这之前我们要先学习一下环形数组如何求索引,非常简单我们在表示索引时都模上一个环形数组的长度就可以了,公式就是(index)%arr.length。接着我们来看看如何判断环形数组是否满,环形数组只有当tail指针的下一个索引为head指针时就表示环形数组已经满了。表示出来也就是判断表达式(tail+1)%arr.length == head。

    //判断队列是否满@Overridepublic boolean isFull() {return (tail + 1) % array.length == head;}
添加元素 

        往队列尾部新增元素其实我们只需要在当前尾指针tail指向的地方添加元素就行了,然后不要忘了更新尾指针,更新尾指针tail时由于是环形数组,我们+1后不要忘了模上一个数组的长度

    //队尾添加元素@Overridepublic boolean push(E value) {// 队列已经满了,特殊处理if (isFull()) {return false;}array[tail] = value;tail = (tail + 1) % array.length;return true;}
遍历

        遍历队列实则就是遍历我们的环形链表,我们也是和之前遍历的套路一样从队头head开始然后一直往后走,直到与队尾指针tail相等那我们就可以停止了。每次向前走都要模上一个数组的长度这是防止索引越界。

    //遍历public void forEach() {int cur = head;while (cur != tail) {System.out.println(array[cur]);cur = (cur + 1) % array.length;}}

         我们在实现完遍历和添加元素后我们就可以测试一下了。 

    @Testpublic void test02(){ArrayQueue queue  =new ArrayQueue(10);queue.push(1);queue.push(2);queue.push(3);queue.push(4);queue.push(5);queue.forEach();}

 

        测试显示通过同时打印也是我们的预期结果。 

删除队头元素

        在头部删除节点我们只需要将头指针head向后移动一位就代表我们删除了队头元素。同时不要忘了更新head时模上一个数组长度。   

    //队头删除元素@Overridepublic E poll() {// 队列为空,特殊处理if (isEmpty()) {return null;}E value = array[head];head = (head + 1) % array.length;return value;}

        测试一下也是没有问题的。

获取队列头部元素

        这个方法实现起来更是简单,我们只需要返回头指针指向的元素就行了。

    // 获取队头元素@Overridepublic E peek() {if (isEmpty()) {return null;}return array[head];}

        测试一下没有问题。

     同样用环形数组去实现队列也可以去新增两个变量,一个size表示大小,一个capacity表示容量,然后来判断队列是否空是否满等等,这个大家可以自己去实现一下,其实大同小异。

   概述

        计算机科学中,栈stack是一种线性的数据结构,规定只能在其一端添加数据和移除数据。习惯来说,这一端称之为栈顶,另一端不能操作数据的称之为栈底,就如同生活中的一个杯子里面装满了球,只有先拿走上面的球才可以拿到下面的球。

           和队列一样我们会用链表和数组两种方式模拟实现队列,为了接下来的实现方便我们还是先定义一个栈接口,里面先定义好几个栈的基本方法。E是规定泛型,不局限于基本数据类型。

public interface Stack<E> {// 向栈顶添加元素boolean push(E value);// 从栈顶弹出元素并返回E pop();// 获取栈顶元素E peek();// 判断栈是否为空boolean isEmpty();// 判断栈是否满boolean isFull();
}
链表实现
      结构

        这里我们用带哨兵节点的单向链表来实现。那栈的结构也就包含了一个哨兵头节点head,同时这里我们再给它设置一个capacity变量表示容量,一个size变量表示大小,来补一下队列中留的作业。

public class LinkedListStack<E> implements Stack<E> {private int capacity; // 容量private int size; // 大小private Node<E> head; // 哨兵头节点public LinkedListStack(int capacity) {this.capacity = capacity;head = new Node<>(null, null);}static class Node<E> {E value;Node<E> next;public Node(E value, Node<E> next) {this.value = value;this.next = next;}}
}

        这里讲一下因为栈不同于队列,栈只可以操作栈顶这一端,所以我们不用像队列一样设置两两个哨兵节点。

 栈是否为空

        这个其实很简单,因为我们有了size变量去表示栈中元素的个数,所以当size == 0时栈就是空的,又或者是当我们哨兵节点head的next指针指向空也代表我们栈是空的。

    // 判断栈是否为空@Overridepublic boolean isEmpty() {return size == 0;//    return head.next == null; }
 栈是否满

        这个也简单,size变量是表示栈中元素的个数,capacity变量是代表我们栈的容量,也就是表示栈可以装多少个元素,所以当size == capacity 时栈就是满的

    // 判断栈是否满。@Overridepublic boolean isFull() {return size == capacity;}
添加元素        

        在栈顶添加元素,又因为我们是用链表模拟实现栈,所以就可以转换为在链表头添加元素的问题,相信这个问题对于你来说已经易如反掌了吧。我们只需要让原来哨兵头节点head的next的值赋给待添加节点cur的next,然后我们让head的next 指向cur就好了。不要忘了栈的大小+1。

    // 在栈顶添加元素@Overridepublic boolean push(E value) {// 链表已满返回false 表示添加失败if (isFull()) {return false;}// 第一步让cur的next指向原来head的nextNode cur = new Node<E>(value, head.next);// 第二步把head的next指向curhead.next = cur;// 不要忘了栈的大小+1size++;return true;}
移除元素   

        照着上面添加元素的思路,其实移除栈顶元素,我们不难想到就是移除链表头节点。我们只需要让哨兵节点的next指向链表头节点cur的next就可以实现对cur的删除,不要忘了栈的大小-1。

    // 移除栈顶元素@Overridepublic E pop() {// 链表为空返回nullif (isEmpty()) {return null;}// 第一步拿到链表头节点curNode<E> cur = head.next;// 第二步让head的next指向cur的next实现删除节点cur。head.next = cur.next;// 不要忘了栈的大小-1size--;return cur.value;}
获取栈顶元素

         这个更是简单,我们只需要返回链表头元素的值就好了,也就是head.next.value。

    // 获取栈顶元素@Overridepublic E peek() {// 链表为空,返回nullif (isEmpty()) {return null;}return head.next.value;}
  遍历

        遍历栈我们是从栈顶遍历到栈底,那么也就是遍历整条链表,这对你来说也是简简单单好吧。   

    //遍历public void forEach() {Node<E> cur = head.next;while (cur != null) {System.out.println(cur.value);cur = cur.next;}}

        测试代码和测试结果就不贴了啊,你们自己敲完测试一下就行了。

数组实现
    结构

        用数组来模拟实现栈我们肯定是需要一个数组array的,接着呢我们还需要一个变量top表示我们的栈顶指针,这就相当于我们用链表实现的head,top在数组中的位置一直指向栈顶的后一位。  

public class ArrayStack<E> implements Stack<E> {private E[] array;private int top; // 栈顶指针public ArrayStack(int capacity) {this.array = (E[]) new Object[capacity];}
}
  栈是否为空

        因为栈顶指针一直指向我们的栈顶后一位,那当我们栈顶指针指向0时也就表示我们此时栈为空。    

    // 判断栈是否为空@Overridepublic boolean isEmpty() {return top == 0;}
   栈是否满 

          这个其实也简单,当我们栈顶指针top与数组长度arr.length相等时就表示栈是满的

    // 判断栈是否满。@Overridepublic boolean isFull() {return top == array.length;}
    添加元素

         现在我们使用的是数组模拟链表那我们添加元素就更简单了,我们只需要在数组arr中的top位置处赋上新元素的值接着让栈顶指针往后走一步,也就是+1就可以了

    // 在栈顶添加元素@Overridepublic boolean push(E value) {// 链表已满返回false 表示添加失败if (isFull()) {return false;}array[top] = value;top++;return true;}
删除元素

        对于用数组模拟实现的栈,我们不用向链表一样去删除那个节点(那块内存),因为数组的内存是连续的我们没法去删除其中一块,我们只能实现值覆盖来达到删除的目的,我们可以把栈顶指针往前移动,这样在下一次添加其它元素时会自动覆盖原来的值,这样也就意味着实现了删除。

    // 移除栈顶元素@Overridepublic E pop() {// 链表为空返回nullif (isEmpty()) {return null;}// 拿到栈顶元素的值E value = array[top - 1];top--;return value;}
获取栈顶元素

        这个和我们删除栈顶元素大同小异,只不过不用把栈顶指针往后移动。

    // 获取栈顶元素@Overridepublic E peek() {if (isEmpty()) {return null;}return array[top - 1];}
遍历 

        由于栈是从栈顶往栈底遍历,所以我们遍历时也是从数组右边往左边遍历就好了。 

    //遍历public void forEach() {for (int i = top - 1; i >= 0; i--) {System.out.println(array[i]);}}

        自己敲完测试一下就行了。 

相关题目

        20. 有效的括号 - 力扣(LeetCode)

933. 最近的请求次数 - 力扣(LeetCode)

2073. 买票需要的时间 - 力扣(LeetCode)

234. 回文链表 - 力扣(LeetCode)

1614. 括号的最大嵌套深度 - 力扣(LeetCode)

150. 逆波兰表达式求值 - 力扣(LeetCode)

带着决心起床,带着满意入睡。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# winform 三层架构 增删改查 修改数据(修改篇)

ss一.留言 本专栏三层架构已经更新了 添加 登录 显示&#xff0c;还差修改以及删除&#xff0c;本篇更新修改&#xff0c;主要操作为点击修改某一条数据&#xff0c;然后跳转页面进行修改。 二.展示 我们先看DAL代码 /// <summary>/// 修改/// </summary>/// &l…

算法基础知识——11种距离度量

简介&#xff1a;个人学习分享&#xff0c;如有错误&#xff0c;欢迎批评指正。 前言&#xff1a;距离的作用 数据聚类&#xff1a;距离度量在聚类算法&#xff08;如K-means、层次聚类&#xff09;中用于衡量数据点之间的相似性或差异性&#xff0c;帮助确定聚类的中心和边界…

haproxy总结与实验

一、负载均衡 1.1 简述负载均衡 在高并发的业务场景下&#xff0c;解决单个节点压力过大&#xff0c;导致Web服务响应过慢&#xff0c;特别是严重的情况下导致服务瘫痪&#xff0c;无法正常提供服务的问题&#xff0c;而负载均衡的目的就是为了维护系统稳定可靠。负载均衡&…

Redis -LFU(Least Frequently Used,最少使用频率)缓存淘汰算法

在 Redis 的 LFU&#xff08;Least Frequently Used&#xff0c;最少使用频率&#xff09;缓存淘汰算法中&#xff0c;lru 字段被拆分成两部分&#xff1a;高 16 位存储 ldt&#xff08;Last Decrement Time&#xff09;&#xff0c;低 8 位存储 logc&#xff08;Logistic Coun…

Postgresql导入矢量数据

前期准备 工具&#xff1a;PgAdmin&#xff0c;postgis-bundle Postgres安装和postgis安装可以百度别的教程。 创建数据库添加扩展 如图&#xff0c;使用PgAdmin创建名为shp的数据库&#xff0c;并在扩展item中添加postgis扩展。 添加扩展方法可以用查询工具输入以下sql语句&…

LeetCode 热题 HOT 100 (024/100)【宇宙最简单版】

【哈希表】No. 0128 最长连续序列【中等】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&am…

ES6模块化简明笔记

1、什么是模块化 详见看上一篇笔记CommonJs模块化简明笔记 2、为什么需要模块化 详见看上一篇笔记CommonJs模块化简明笔记 3、导入和导出的概念 详见看上一篇笔记CommonJs模块化简明笔记 4、模块导出&#xff08;暴露&#xff09; 4.1 导出&#xff08;暴露&#xff09;方式1&…

C++ list的基本使用

目录 1.list简要介绍 2. list的构造 3. list中迭代器的使用 (1). 双向迭代器与随机访问迭代器使用区别 4.判空、获取元素个数 5. list头、尾元素的访问 6. 插入与删除操作 (1). 头插头删&#xff0c;尾插尾删 (2). 插入&#xff0c;删除与清空 (3). 交换 7. list容器迭代…

前端面试常考的HTML标签知识!!!

语义标签 标签名语义描述header网页头部网页的主要头部区域nav网页导航网页的导航链接区域footer网页底部网页的底部区域aside网页侧边栏网页的侧边栏区域section网页区块网页的独立区块 | article | 网页文章 | 网页的独立文章区域 | 字符实体 作用&#xff1a;在网页中显…

DataWhale夏令营——AIGC技术

一、任务流程 第一步——开通阿里云PAI-DSW试用 1.进入阿里云社区 阿里云社区&#xff1a;阿里云免费试用 - 阿里云 (aliyun.com) 2. 登录或者注册自己的阿里云账号&#xff1a; 3. 点击立即试用 领取成功之后关闭页面即可。 第二步——进入魔搭社区授权 魔搭社区&#…

HTML标记与文档结构

1.1 HTML标记基础 内容标记包括闭合标签和非闭合标签。 1.1.1 文本用闭合标签 闭合标签基本格式和属性&#xff1a; <标签名 属性1"属性值" >文本内容</标签名> 闭标签比开标签多一个斜杠。 1.1.2 引用内容够用自闭和标签 <标签名 属性1"…

Android逆向题解 攻防世界难度5- APK逆向

jeb打开apk 一眼就能看出来&#xff0c;没啥难度&#xff0c;这个难度还不如上一个难度4的题。 直接还原即可 public static void main(String[] args) throws NoSuchAlgorithmException {String userName "Tenshine";MessageDigest messageDigest0 MessageDigest…

【C++】基于多态实现员工管理系统

代码 1、主程序&#xff1a; #include<iostream> using namespace std; #include"workerManager.h"#include"worker.h" #include"employee.h" #include"manager.h" #include"boss.h"int main() {WorkerManager wm;i…

Java语言程序设计——篇十三(1)

&#x1f33f;&#x1f33f;&#x1f33f;跟随博主脚步&#xff0c;从这里开始→博主主页&#x1f33f;&#x1f33f;&#x1f33f; 欢迎大家&#xff1a;这里是我的学习笔记、总结知识的地方&#xff0c;喜欢的话请三连&#xff0c;有问题可以私信&#x1f333;&#x1f333;&…

C#语言基础速成Day07

“知止而后有定&#xff0c;定而后能静&#xff0c;静而后能安&#xff0c;安而后能虑&#xff0c;虑而后能得。” 目录 前言文章有误敬请斧正 不胜感恩&#xff01;||Day07 C#常见数据结构&#xff1a;1. 集合&#xff08;Collection&#xff09;1.1 **List<T>**1.2 **H…

前端(三):Ajax

一、Ajax Asynchronous JavaScript And XML&#xff0c;简称Ajax&#xff0c;是异步的JavaScript和XML。 作用&#xff1a;数据交换&#xff0c;通过Ajax可以给服务器发送请求&#xff0c;并获取服务器响应的数据。异步交互&#xff1a;可以在不重新加载整个页面的情况下&…

本地环境VMware使用代理解决 Docker 镜像拉取问题

引言 本文将分享我在 Windows 10 环境下&#xff0c;通过 VMware 运行的 CentOS 7.8 虚拟机中配置 Docker 代理&#xff0c;成功解决了镜像拉取问题的经验。 问题描述 在尝试启动一个依赖 Docker 的 GitHub 项目时&#xff0c;拉取 Docker 镜像的失败。尝试配置了几个国内源…

Spring Boot优缺点

Spring Boot 是一款用于简化Spring应用开发的框架&#xff0c;它集成了大量常用的框架和工具&#xff0c;大大简化了Spring项目的配置和部署。下面是Spring Boot的优缺点&#xff1a; 优点&#xff1a; 简化配置&#xff1a;Spring Boot自动配置功能可以根据应用的依赖自动配…

Spring Boot - 在Spring Boot中实现灵活的API版本控制(上)

文章目录 为什么需要多版本管理&#xff1f;在Spring Boot中实现多版本API的常用方法1. URL路径中包含版本号2. 请求头中包含版本号3. 自定义注解和拦截器 注意事项 为什么需要多版本管理&#xff1f; API接口的多版本管理在我们日常的开发中很重要&#xff0c;特别是当API需要…

2.mysql数据库-DML-DQL-DCL

1. DML-操作数据 1.1 DML-添加数据 给指定字段添加数据 INSERT INTO 表名 (字段名1&#xff0c;字段名2,…) values (值1,值2…) 给全部字段添加数据 INSERT INTO 表名 values(值1,值2,…) 批量添加数据 INSERT INTO 表名 (字段名1&#xff0c;字段名2,…) values (值1,值2……