java数据结构--双端队列

news2024/11/22 15:34:59

一.概念

  双端队列的意思是可以在头部和尾部添加和删除元素,更一般的单向链表队列比起来更加的灵活,下面我们用双向循环带哨兵链表和数组来分别实现

二.定义接口Dequeue

/**
 * 双端队列
 */
public interface Dequeue<E> {

    //队头添加元素
    boolean offerTop(E e);

    //队尾添加元素
    boolean offerTail(E e);

    //队头取元素并删除该元素
    E pollTop();

    //队尾取元素并删除该元素
    E pollTail();


    //队头取元素
    E peekTop();

    //队尾取元素
    E peekTail();

    //是否为空
    boolean isEmpty();

    //是否为满
    boolean isFull();

}

三.双向循环带哨兵链表链表实现

 1.图示

  

 

(1)向队头添加节点

(2)向队尾添加节点

 

 对于移除节点,和上面的类似,代码注释中也都做了解释,这里就不在展示了。

2.代码实现 

/**
 * 双向链表实现双端队列
 * @param <E>
 */
public class LinkedDequeue<E> implements Dequeue<E> ,Iterable<E>{


    //内部节点类
    static class Node<E>{
        Node<E> pre;
        E value;
        Node<E> next;

        public Node(Node<E> pre,E value,Node<E> next){
            this.pre = pre;
            this.value = value;
            this.next = next;
        }

    }

    //容量
    int capacity;
    //队列中元素的个数
    int size;
    //哨兵
    Node<E> sentinel = new Node<>(null,null,null);

    //初始化
    public LinkedDequeue(int capacity){
        //初始化容量
        this.capacity = capacity;
        //开始时哨兵的下一个节点是本身
        sentinel.next = sentinel;
        //上一个节点也是本身
        sentinel.pre = sentinel;
    }



    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            //获取第一个元素
            Node<E> p = sentinel.next;
            @Override
            public boolean hasNext() {
                //如果p==sentinel就说明遍历完成了,没有下一个了
                return p != sentinel;
            }

            @Override
            public E next() {
               E value = p.value;
               p = p.next;
               return value;
            }
        };
    }

    /**
     * 向头部添加节点
     *  a  -> added ->  b
     *     <-       <-
     *  a是哨兵, b是哨兵的下一个节点
     * @param e
     * @return
     */
    @Override
    public boolean offerTop(E e) {
       if(isFull()){
           return  false;
       }
       Node<E> a = sentinel;
       Node<E> b = sentinel.next;
       Node<E> added = new Node<>(a,e,b);
       a.next = added;
       b.pre = added;
       size++;
       return true;
    }

    /**
     * 向尾部添加节点
     *
        a    -> added ->  b
     *       <-       <-
     * a是哨兵的上一个,b是哨兵
     *
     * @param e
     * @return
     */
    @Override
    public boolean offerTail(E e) {
        if(isFull()){
            return false;
        }
        Node<E> b = sentinel;
        Node<E> a = sentinel.pre;
        Node<E> added = new Node<>(a,e,b);
        a.next = added;
        b.pre = added;
        size++;
        return true;
    }

    /**
     * 将头部节点移除
     * @return 头部元素的值
     *
     * a removed b
     * a是哨兵,removed是哨兵的下一个,b是removed的下一个
     * a -> b
     *   <-
     */
    @Override
    public E pollTop() {
       if(isEmpty()){
           return null;
       }
       Node<E> a = sentinel;
       Node<E> removed = sentinel.next;
       Node<E> b = removed.next;
       a.next = b;
       b.pre = a;
       size--;
       return removed.value;
    }
    /**
     * 将尾部节点移除
     * @return 尾部元素的值
     *
     * a removed b
     * b是哨兵,removed是b的上一个,a是removed的上一个
     * a -> b
     *   <-
     */
    @Override
    public E pollTail() {
       if(isEmpty()){
           return null;
       }
       Node<E> b = sentinel;
       Node<E> removed = sentinel.pre;
       Node<E> a = removed.pre;
       a.next = b;
       b.pre = a;
       size--;
       return removed.value;
    }

    /**
     * 获取第一个元素的值,但是不移除
     * @return
     */
    @Override
    public E peekTop() {
        if(isEmpty()){
            return null;
        }
        return sentinel.next.value;
    }

    /**
     * 获取最后一个元素的值,但是不移除
     * @return
     */
    @Override
    public E peekTail() {
        if(isEmpty()){
            return null;
        }
        return sentinel.pre.value;
    }

    @Override
    public boolean isEmpty() {
        return  size == 0;
    }

    @Override
    public boolean isFull() {
        return  size == capacity;
    }
}

四.数组带头尾指针实现

1.图示

(1)刚开始时head和tail都在同一位置,表示空

当在尾部tail添加元素时,先在tail处添加元素,然后让tail加一,

注意:这里的加一不是直接tail++,因为是循环队列,所以若到了array.length处,也就是末尾,下一次加一就要跑到索引0的位置了,一般我们之前是用的求余数运算来找到合法的索引位置,但是我们这里会用一个新的方式来运算索引

 (2)tail处添加元素

(3)head处添加元素

 我们在head处添加元素时,我们是让head先减一,然后再在head处添加,为啥呢?因为我们是循环数组,让head减一,相当于改变了头的位置,好像是在向左移动一样,向左扩展,

注意:这里的减一也是要算出合法的索引,而且如果head本来是在索引0处,那么head就要移动到array.length处,这样就好像是在向前移动

 对于头部尾部移除元素,都是类似的,在此不过多描述。

(4)代码实现

/**
 * 数组实现双端队列
 * @param <E>
 */
public class ArrayDequeue<E> implements Dequeue<E>,Iterable<E>{

    E array[];
    //头指针
    int head;
    //尾指针
    int tail;

    @SuppressWarnings("all")
    public ArrayDequeue(int capacity){
        //实际容量要多空出一个来判断空满
        array = (E[]) new Object[capacity+1];
    }

    //加一方法
    public static int inc(int i,int length){
        if(i + 1 >= length){
            return 0;
        }
        return i + 1;
    }
    //减一方法
    public static int dec(int i,int length){
        if(i - 1 < 0){
            return  length - 1;
        }
        return i - 1;
    }





    /**
     * 头部添加元素
     *
     * 先让head--,然后再在head处添加元素
     *
     * @param e
     * @return
     */
    @Override
    public boolean offerTop(E e) {
       if(isFull()){
           return false;
       }
       head = dec(head,array.length);
       array[head] = e;
       return true;
    }

    /**
     * 在尾部添加依赖
     *
     * 先在tail处添加元素,然后让tail++
     * @param e
     * @return
     */
    @Override
    public boolean offerTail(E e) {
       if(isFull()){
           return false;
       }
       array[tail] = e;
       tail = inc(tail,array.length);
       return true;
    }

    /**
     * 移除队头元素
     *
     * 先获取head处元素,然后让head++
     *
     * @return
     */
    @Override
    public E pollTop() {
       if(isEmpty()){
           return null;
       }
        E  e  =  array[head];
        array[head] = null; //help GC
        head = inc(head,array.length);

       return e;
    }

    /**
     * 移除队尾元素
     *
     * 先让tail--,然后返回tail处的值
     *
     * @return
     */
    @Override
    public E pollTail() {
        if(isEmpty()){
            return null;
        }
        tail = dec(tail,array.length);
        E e = array[tail];
         array[tail] = null; //help GC
        return e;
    }

    @Override
    public E peekTop() {
        if(isEmpty()){
            return null;
        }
        return array[head];
    }

    @Override
    public E peekTail() {
        if(isEmpty()){
            return null;
        }
        return array[dec(tail, array.length)];
    }

    /**
     * 判断是否为空
     * @return
     */
    @Override
    public boolean isEmpty() {
        return  head == tail;
    }

    /**
     * 判断是否为满
     * @return
     */
    @Override
    public boolean isFull() {
        //当 head > tail 时,
        /**
         * 需要判断 head - tail == 1
         */

        /**
         * 当 tail > head 时
         * 需要判断 tail - head == array.length - 1;
         */

        if(head > tail){
            return head - tail == 1;
        }else if( head < tail){
            return  tail - head == array.length - 1;
        }else{
            return false;
        }

    }

    /**
     * 遍历
     * @return
     */
    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {

            int p = head;

            @Override
            public boolean hasNext() {
                return p != tail;
            }

            @Override
            public E next() {
                E e = array[p];
                p = inc(p,array.length);
                return e;
            }
        };
    }
}

五.及时释放元素

我们在之前的队列和栈的移除元素操作时,每次都是直接跳过元素,相当于移除了该元素,其实这里有误区:

1.如果元素类型为基本类型,比如Int类型,那么一个数字占四个字节,直接跳过和设置为0都占用内存空间,设置为0意义不大,所以直接跳过就可以了。

2.如果元素类型为引用类型,比如Student学生对象,那么如果你直接跳过该元素,其实他并没有被释放,也就是不能被GC回收,这时需要我们手动释放元素,我们可以让array[x]=null;来释放内存空间

另外说一点,在jdk中有一个类LinkedList,这个类可用当栈,也可以当队列,也实现了双端队列的功能

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

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

相关文章

MSSQL 配置ORACLE ​链接服务器

在有些场景&#xff0c;我们需要整合其他异构数据库的数据。我们可以使用代码去读取&#xff0c;经过处理后&#xff0c;再将数据保存到MSSQL数据库中。如果数据量比较大&#xff0c;但处理的逻辑并不复杂的情况下&#xff0c;这种方式就不是最好的办法。这时可以使用使用链接服…

C++笔记之表驱动法-全局静态结构体变量的应用实例ColorMAP

C笔记之表驱动法-全局静态结构体变量的应用实例ColorMAP code review! 代码 #include <ros/ros.h> #include <visualization_msgs/Marker.h>struct RGBA{RGBA(){red.r 1; green.r 0; blue.r 0;red.g 0; green.g 1; blue.g 0;red.b 0; green.b 0; blue.b…

Netty入门指南之NIO Buffer详解

作者简介&#xff1a;☕️大家好&#xff0c;我是Aomsir&#xff0c;一个爱折腾的开发者&#xff01; 个人主页&#xff1a;Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏&#xff1a;Netty应用专栏_Aomsir的博客-CSDN博客 文章目录 参考文献前言ByteBu…

终究还是翻车了,人肉运维100 次后

翻车现场 5年前的一个晚上&#xff0c;我接到数据组同事的消息&#xff0c;要求将A用户的磁盘快照共享给B用户。我对这个线上运维工作早已轻车熟路&#xff0c;登录线上服务器仅用了2分钟就完成了。 我继续忙着其他事情&#xff0c;3分钟后&#xff0c;我正要打开新的控制台页…

【多线程】synchronized的特性

文章目录 synchronized 的特性互斥可重入synchronized的使用加锁过程 synchronized 的特性 互斥 synchronized 会起到互斥效果&#xff0c;某个线程执行到某个对象的 synchronized 中时&#xff0c;其他线程如果也执行到同一个对象 synchronized 就会阻塞等待。进入 synchron…

【源码篇】基于SSM+JSP实现的网上花店系统

系统介绍 基于SSMJSP实现的网上花店系统采用了时下流行的 Java 程序设计语言进行开发&#xff0c;系统开发的工具采用 Idea 开发工具&#xff0c;普通用户可以在该系统完成注册、登录、购买等一系列操作&#xff0c;致力于为用户提供一个方便快捷的在线购花平台。 前台系统功…

Oracle(14) Managing Password Security and Resources

目录 一、基础知识 1、Profiles 配置文件 2、Password Management 密码管理 3、Enabling Password Mgmt 启用密码管理 4、Password Verification 密码验证 ​编辑5、User-Provided Passwd Func 用户提供的密码功能 6、Verif Func: VERIFY_FUNCTION验证函数介绍 7、Reso…

Web时代下,软件系统的持续进步,是否能完全替代人力节省成本?

Web时代下&#xff0c;软件系统的持续进步&#xff0c;是否能完全替代人力节省成本&#xff1f; 随着全球经济的蓬勃发展&#xff0c;众多经济学家纷纷提出了新的管理理念&#xff0c;例如在20世纪50年代&#xff0c;西蒙提出管理依赖信息和决策的思想&#xff0c;但在同时期的…

2023年云计算的发展趋势如何?

混合云的持续发展&#xff1a;混合云指的是将公有云和私有云进行结合&#xff0c;形成一种统一的云计算环境。随着企业对数据隐私和安全性的要求越来越高&#xff0c;以及在数据存储和处理方面的需求不断增长&#xff0c;混合云正在逐渐成为主流。预计未来混合云将会继续保持高…

,多数据源+Mybatisplus + Sharding JDBC同一库中分表

水平分表是在同一个数据库内&#xff0c;把同一个表的数据按一定规则拆到多个表中,多数据源采用 mybatis-plus的dynamic-datasource 分库分表采用sharding-jdbc 数据库连接池管理是alibaba的druid-spring-boot-starter 同一个数据库内分表 目录 1.数据库表 2.配置 3.引入的…

蓝桥等考C++组别六级004

第一部分&#xff1a;选择题 1、C L6 &#xff08;15分&#xff09; 关于switch语句&#xff0c;以下说法正确的是&#xff08; &#xff09;。 A. break语句只能用于switch语句。 B. switch语句中可以使用多个default语句。 C. switch语句中只能使用一个break语句。 D. …

优雅的并发编程-CompletableFuture

目录 了解CompletableFuture CompletableFuture 是 Java 8 引入的一个类&#xff0c;用于支持异步编程和非阻塞操作。它提供了一种简单而强大的方式来处理异步任务&#xff0c;可以轻松地实现并行、非阻塞的操作&#xff0c;并且提供了丰富的方法来处理任务的完成状态、异常情…

iOS加固原理与常见措施:保护移动应用程序安全的利器

​ 目录 iOS加固原理与常见措施&#xff1a;保护移动应用程序安全的利器 前言 一、iOS加固的原理 1. 代码混淆 2. 加密算法 3. 防调试技术 4. 签名校验 二、iOS加固的常见措施 1. 代码混淆 2. 加密算法 3. 防调试技术 4. 签名校验 三、iOS加固的效果和注意事项 参…

什么是代理IP池?如何判断IP池优劣?

代理池充当多个代理服务器的存储库&#xff0c;提供在线安全和匿名层。代理池允许用户抓取数据、访问受限制的内容以及执行其他在线任务&#xff0c;而无需担心被检测或阻止的风险。代理池为各种在线活动&#xff08;例如网页抓取、安全浏览等&#xff09;提高后勤保障。 读完…

一个“Hello, World”Flask应用程序

如果您访问Flask网站&#xff0c;会看到一个非常简单的示例应用程序&#xff0c;只有5行代码。为了不重复那个简单的示例&#xff0c;我将向您展示一个稍微复杂一些的示例&#xff0c;它将为您编写大型应用程序提供一个良好的基础结构。 应用程序将存在于包中。在Python中&…

三大基础排序 -选择排序、冒泡排序、插入排序

排序算法 文章目录 冒泡排序算法步骤动图代码优化总结 选择排序算法步骤动图代码总结 插入排序算法步骤动图代码总结 排序算法&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。一般默认排序是按照由小到大即…

【JVM】运行时数据区、程序计数器

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 JVM 一、 运行时数据区二、 程序计数器程序…

Console 线连接路由器交换机等设备

Console 线连接路由器交换机等设备 Console 线几乎是每一个网工必备的&#xff0c;学会使用 Console 线去连接真实的设备也是非常重要的。这里我们就使用 XShell 软件来演示设备的连接和管理配置。 文章目录 Console 线连接路由器交换机等设备一、Console 线二、连接设备 Cons…

2023-11-Rust

学习方案&#xff1a;Rust程序设计指南 1、变量和可变性 声明变量&#xff1a;let 变量、const 常量 rust 默认变量一旦声明&#xff0c;就不可变(immutable)。当想改变 加 mut&#xff08;mutable&#xff09; 。 const 不允许用mut &#xff0c;只能声明常量&#xff0c;…