Java数据结构与算法-队列(详细实现)

前言

队列(Queue)
4.1 情景
  • 使用电脑时,机器有时会处于疑似死机状态,鼠标点什么都没有反应,当失去耐心打算关机重启的时候,突然清醒一般,把刚刚点击的所有操作都按顺序执行了一遍。这是因为操作系统的多个程序需要通过一个通道输出,而按先后次序排队等待造成的。
  • 还有就是一些系统中的客服人员,客服人员有限,当咨询人数大于客服人员总数后,就需要排队,当哪个客服人员有空闲时,会把最早排队的人进行安排。
4.2 什么是队列结构
  • 队列结构从逻辑上来看是线性结构,从存储结构上来看可以划分为两类

    • 顺序队列:使用一组地址连续的内存单元依次保存队列中的数据。可以定义一个指定大小的结构数组作为队列。
    • 链式队列:使用链表形式保存队列中各元素的值。
  • 队列结构,是一种先进先出(FIFO)的线性表,允许插入的一端

  • 队列的常见方法

    • initQueue
      • 初始化操作,建立一个空队列
    • destroyQueue
      • 若队列存在,则销毁
    • clearQueue
      • 清空队列
    • queueEmpty
      • 判断队列是否为空
    • getHead
      • 获取队头元素
    • enQueue
      • 若队列存在,插入元素入队
    • deQueue
      • 删除队头元素,并返回值
    • queueLength
      • 获取队列实际元素个数
  • 顺序队列

    • Java

      package com.fc.queue;
      
      /**
       * @ClassName SequentialQueue   顺序队列
       * @Description 保持队头始终在索引为0的位置
       * @Author Fclever
       * @Date 2021/7/2 15:59
       **/
      public class SequentialQueue<T> {
      
          /**
           * 队列默认长度10
           */
          private static final int MAXLEN = 10;
      
          /**
           * 存储数据数组
           */
          Object[] queueData;
      
          /**
           * 队尾索引
           *     队列为空,指向-1,否则,始终指向队尾元素n
           */
          int tail;
      
      
          public SequentialQueue() {
          }
      
          /**
           * 1. 初始化队列
           */
          public void initQueue() {
              // 初始化存储数组
              this.queueData = new Object[MAXLEN];
              // 设置队尾
              this.tail = -1;
          }
      
          /**
           * 2. 销毁队列
           */
      //    public void destroyQueue() {
      //
      //    }
      
          /**
           * 3. 清空队列
           */
          public void clearQueue() {
              for (int i = 0; i<=this.tail;i++){
                  this.queueData[i] = null;
              }
              this.tail = -1;
          }
      
          /**
           * 4. 判断队列是否为空
           * @return
           */
          public boolean queueEmpty() {
              return this.tail == -1;
          }
      
          /**
           * 5. 获取队头元素
           * @return
           */
          public T getHead() {
              return (T) this.queueData[0];
          }
      
          /**
           * 6. 入队
           * @param data  入队元素
           */
          public void enQueue(T data) {
              // 判断队列是否满
              if (this.tail + 1 == this.MAXLEN) {
                  throw new OutOfMemoryError();
              }
              // 插入元素
              this.queueData[++this.tail] = data;
          }
      
          /**
           * 7. 出队
           * @return 返回队头元素
           */
          public T deQueue() {
              if (this.queueEmpty()) {
                  return null;
              }
              // 返回值
              T data = (T) this.queueData[0];
              // 其他往前移动
              System.arraycopy(this.queueData, 1, this.queueData, 0, this.tail);
              this.queueData[this.tail--] = null;
              return data;
          }
      
          /**
           * 8. 获取队列实际元素个数
           * @return
           */
          public int queueLength() {
              return this.tail+1;
          }
      
          /**
           * 9. 遍历元素
           */
          public void getAll() {
              for (int i=0;i<=this.tail;i++){
                  System.out.printf("第%d个元素为:%s\n",i,this.queueData[i]);
              }
          }
      
      }
      
      
    • 测试

      package com.fc.queue;
      
      import org.junit.Test;
      
      import static org.junit.Assert.*;
      
      /**
       * @ClassName SequentialQueueTest
       * @Description
       * @Author Fclever
       * @Date 2021/7/5 13:20
       **/
      public class SequentialQueueTest {
      
          @Test
          public void testSequentialQueueTest() {
              SequentialQueue<String> sequentialQueue = new SequentialQueue<>();
              sequentialQueue.initQueue();
              sequentialQueue.enQueue("1");
              sequentialQueue.enQueue("2");
              sequentialQueue.enQueue("3");
              sequentialQueue.enQueue("4");
              sequentialQueue.enQueue("5");
              System.out.println(sequentialQueue.queueLength());
              sequentialQueue.deQueue();
              sequentialQueue.deQueue();
              sequentialQueue.getAll();
          }
      }
      
4.3 循环队列
4.3.1 顺序存储的不足之处
  • 假设一个队列有n个元素,数组下标为0的一端是队头,对于入队操作,就是在队尾追加元素,不需要移动元素,时间复杂度为O(1);但是在进行出队操作时,会把队头元素(下标为0)移除,后面元素向前移动,事件复杂度为O(n)
  • 但是这种出队方式会使得出队性能降低,如果不限制队列的元素必须存储在数组前n个单元时,出队的性能就可以进行优化了,也就是可以不把队头一定设置在下标为0的位置。
  • 引入两个指针,front指针指向队头元素,rear指针指向队尾元素的下一个位置,空队列情况下,两个指针都指向下标为0的位置
4.3.2 假溢出
  • 一个队列,使用长度为5的数组来保存数据,初始情况front=rear=0。入队三个元素,front=0,rear=3,再出队一个元素,front=1,rear=3,再入队两个元素,front=1,rear=5?数组下标最大为4,rear不可能是5,会导致数组越界问题。但是此时看一下数组的情况,还是有一个位置是空缺的,下标为0的位置,这种情况就可以被称为假溢出。
4.3.3 循环队列定义
  • 解决假溢出可以是数组后面满了,再从头开始。这种队列头尾相接的顺序存储结构称为循环队列。
  • 根据上面假溢出问题,使用循环队列,就可以将新的入队元素插入到下标为0的位置即可,此时rear=1
4.3.4 循环队列判空和判满的条件
  • 当空队列时,front=rear,那么当队列满的时候,按上面的情况,条件也变成了front=rear了,那该如何区分空和满呢?
    • 方式一:设置标志变量flag,当front=rear&&flag=0时队列为空,当front=rear&&flag=1时队列满。(flag值自定义)
    • 方式二:当队列为空,front=rear;当队列满时,约定数组保留一个元素的位置不使用,数组中还存在一个空闲单元。下图这种情况就表示队列已经满了
      • image-20210713142807587
  • 方式二更加常用,但是需要进行分析
    • front和rear相差一个位置的情况分好多情况:rear可能大于front、rear也可能小于front。
    • 假设队列最大容量为MAXLEN,那么队列满的条件为:(rear+1)%MAXLEN==front。
  • 借此,分析一下循环队列实际数据个数怎么计算
    • 当rear > front时,队列长度为rear-front
    • 当rear <= front时,队列长度为rear-front+MAXLE
    • 也可以推导出计算队列实际数据个数的公式:(rear-front+MAXLEN)%MAXLEN
4.3.5 顺序循环队列
  • 代码

    package com.fc.queue;
    
    /**
     * @ClassName CircularQueue     循环队列
     * @Description
     * @Author Fclever
     * @Date 2021/7/13 11:29
     **/
    public class CircularQueue<T> {
    
        private final int MAXLEN = 10;
    
        Object[] queueData;
    
        private int front;
    
        private int rear;
    
        public CircularQueue() {
        }
    
        /**
         * 1. 初始化队列
         */
        public void initQueue() {
            queueData = new Object[this.MAXLEN];
            this.front = 0;
            this.rear = 0;
        }
    
        /**
         * 2. 将队列清空
         */
        public void clearQueue() {
            while (this.front != this.rear) {
                this.queueData[this.front] = null;
                // 队头指针后移
                this.front = (this.front + 1) % this.MAXLEN;
            }
            // 重置指向
            this.front = this.rear = 0;
        }
    
        /**
         * 3. 判断队列是否为空
         * @return  true为空,false不为空
         */
        public boolean queueEmpty() {
            return this.front == this.rear;
        }
    
        /**
         * 4. 获取队头元素
         * @return  队头元素
         */
        public T getHead() {
            return (T) this.queueData[this.front];
        }
    
        /**
         * 5. 入队
         * @param data  待入队元素
         */
        public void enQueue(T data) {
            // 判断是否栈满   (rear+1)%maxlen == front
            if ((this.rear + 1)%this.MAXLEN == this.front) {
                throw new OutOfMemoryError();
            }
            this.queueData[this.rear] = data;
            // rear后移
            this.rear = (this.rear + 1) % this.MAXLEN;
        }
    
        /**
         * 6. 出队
         * @return  返回出队元素
         */
        public T deQueue(){
            if (this.queueEmpty()) {
                return null;
            }
            T data = (T) this.queueData[this.front];
            // 原队头元素置空
            this.queueData[this.front] = null;
            // 队头指针后移
            this.front = (this.front + 1) % this.MAXLEN;
            // 返回出队元素
            return data;
        }
    
        /**
         * 7. 获取队列实际存储数据个数
         * @return
         */
        public int queueLength() {
            return (this.rear - this.front + this.MAXLEN) % this.MAXLEN;
        }
    
        /**
         * 8. 遍历
         */
        public void getAll() {
            int index = this.front;
            while (index != this.rear) {
                System.out.println(this.queueData[index]);
                // 队头指针后移
                index = (index+ 1) % this.MAXLEN;
            }
        }
    }
    
    
  • 测试

    package com.fc.queue;
    
    import org.junit.Test;
    
    import static org.junit.Assert.*;
    
    /**
     * @ClassName CircularQueueTest
     * @Description
     * @Author Fclever
     * @Date 2021/7/13 15:35
     **/
    public class CircularQueueTest {
    
        @Test
        public void test() {
            // 创建循环队列
            CircularQueue<String> circularQueue = new CircularQueue<>();
            // 初始化
            circularQueue.initQueue();
            // 入队
            circularQueue.enQueue("1");
            circularQueue.enQueue("2");
            circularQueue.enQueue("3");
            circularQueue.enQueue("4");
            circularQueue.enQueue("5");
            circularQueue.deQueue();
            circularQueue.deQueue();
            circularQueue.enQueue("88");
            System.out.println(circularQueue.queueEmpty());
            String head = circularQueue.getHead();
            System.out.println(head);
            System.out.println(circularQueue.queueLength());
            circularQueue.getAll();
            circularQueue.clearQueue();
        }
    }
    
  • 循环队列比普通的顺序队列在时间性能上有了提高,但是面临着数组溢出问题(因为数组长度是固定的)

4.3.6 链式循环队列
  • 队列的链式存储方式,采用单链表,尾进头出,简称为链队列。设置队头指针指向头结点,队尾指针指向终端节点。

  • Java代码

    package com.fc.queue;
    
    /**
     * @ClassName ChainQueue    链队列
     * @Description
     * @Author Fclever
     * @Date 2021/7/13 16:42
     **/
    public class ChainQueue<T> {
    
        // 队头指针
        private Node<T> front;
    
        // 队尾指针
        private Node<T> rear;
    
        // 实际元素个数
        private int length;
    
        public ChainQueue() {
        }
    
        /**
         * 1. 初始化
         */
        public void initQueue() {
            // 队头和队尾指向相同
            this.front = this.rear =  new Node<>();
            this.length = 0;
        }
    
        /**
         * 2. 清空队列
         */
    //    public void clearQueue() {
    //        Node<T> p = this.front;
    //        Node<T> q = this.front.next;
    //        while (q != null) {
    //            p = q;
    //            q = q.next;
    //            p = null;
    //        }
    //        q = null;
    //    }
    
        /**
         * 3. 判断队列是否为空
         * @return  是否为空标志
         */
        public boolean queueEmpty() {
            return this.front == this.rear;
        }
    
        /**
         * 4. 获取队头元素
         * @return  队头
         */
        public Node<T> getHead(){
            return this.front.next;
        }
    
        /**
         * 5. 入队
         * @param data
         */
        public void enQueue(T data) {
            // 构建结点
            Node<T> node = new Node<>(data, null);
            /**
             * 当队列为空时,front=rear都指向头结点,只需要改变
             * rear的next指向即可,此时front也跟着改变
             */
            // 直接在队尾插入
            this.rear.next = node;
            this.rear = node;
            this.length++;
        }
    
        /**
         * 6. 出队
         * @return
         */
        public Node<T> deQueue(){
            // 队列为空,直接返回
            if (this.queueEmpty()){
                return null;
            }
            // 队头元素
            Node<T> node = this.front.next;
            // 判断队头元素是否是队尾**
            if (this.rear == node) {
                // 将队尾指向头结点
                this.rear = this.front;
            }
            this.front.next = node.next;
            this.length--;
            return node;
        }
    
        /**
         * 7. 获取队列实际长度
         * @return
         */
        public int queueLength() {
            return this.length;
        }
    
        /**
         * 8. 遍历
         */
        public void getAll() {
            if (!this.queueEmpty()) {
                Node<T> p = this.front.next;
                while (p != null) {
                    System.out.println(p.data);
                    p = p.next;
                }
            }
        }
    }
    
    
  • 测试

    package com.fc.queue;
    
    import org.junit.Test;
    
    import javax.print.DocFlavor;
    
    import static org.junit.Assert.*;
    
    /**
     * @ClassName ChainQueueTest
     * @Description
     * @Author Fclever
     * @Date 2021/7/14 10:03
     **/
    public class ChainQueueTest {
    
        @Test
        public void test() {
            // 创建
            ChainQueue<String> chainQueue = new ChainQueue<>();
            // 初始化
            chainQueue.initQueue();
            // 入队
            chainQueue.enQueue("1");
            chainQueue.enQueue("2");
            chainQueue.enQueue("3");
            chainQueue.enQueue("4");
            chainQueue.enQueue("5");
            //
            System.out.println(chainQueue.getHead().data);
            System.out.println(chainQueue.queueLength());
            chainQueue.deQueue();
            chainQueue.deQueue();
            chainQueue.deQueue();
            System.out.println(chainQueue.queueEmpty());
            chainQueue.getAll();
        }
    
    }
    
4.3.7 顺序队列和链队列
  • 从时间上来看,基本操作都是O(1),循环队列是实现申请好空间,使用期间不释放;链队列,每次需要时申请和释放结点。
  • 从空间上来看,循环队列长度固定,有了存储元素个数和空间浪费的问题;链队列不存在这个问题,虽然需要指针域,会产生一定的空间开销。
  • 队列长度固定,使用顺序循环队列;队列长度无法预估,使用链队列。

.enQueue(“5”);
//
System.out.println(chainQueue.getHead().data);
System.out.println(chainQueue.queueLength());
chainQueue.deQueue();
chainQueue.deQueue();
chainQueue.deQueue();
System.out.println(chainQueue.queueEmpty());
chainQueue.getAll();
}

}


###### 4.3.7 顺序队列和链队列

- 从时间上来看,基本操作都是O(1),循环队列是实现申请好空间,使用期间不释放;链队列,每次需要时申请和释放结点。
- 从空间上来看,循环队列长度固定,有了存储元素个数和空间浪费的问题;链队列不存在这个问题,虽然需要指针域,会产生一定的空间开销。
- <font color=red>队列长度固定,使用顺序循环队列;队列长度无法预估,使用链队列。</font>

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
算法与数据结构它们分别涵盖了以下主要内容: 数据结构(Data Structures): 逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法: 算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法与数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。
逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法: 算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法与数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值