第二十四篇 玩转数据结构——队列(Queue)

1.. 队列基础

  • 队列也是一种线性结构;
  • 相比数组,队列所对应的操作数是队列的子集;
  • 队列只允许从一端(队尾)添加元素,从另一端(队首)取出元素;
  • 队列的形象化描述如下图:
  • 队列是一种先进先出(First In First Out)的数据结构;

2.. 队列的实现

  • 任务目标如下:
  • Queue<E>
    ·void enqueue(E)   //入队
    ·E dequeue()   //出队
    ·E getFront()  //查看队首元素
    ·int getSize()   //查看队列中元素的个数
    ·boolean isEmpty()   //查看队列是否为空
  • 需要提一下,从用户的角度来看,只要实现上述操作就好,具体底层实现,用户并不关心,实际上,底层确实有多种实现方式。
  • 我们准备在之前实现的动态数组基础上,来实现"队列"这种数据结构。
  • 先定义一个接口Interface,如下:
  • public interface Queue<E> {
        int getSize();
    
        boolean isEmpty();
    
        void enqueue(E e);
    
        E dequeue();
    
        E getFront();
    
    }
  • 实现基于Array类的ArrayQueue类,并进行测试:
  • public class ArrayQueue<E> implements Queue<E> {
        private Array<E> array;
    
        //构造函数
        public ArrayQueue(int capacity) {
            array = new Array<>(capacity);
        }
    
        //无参数构造函数
        public ArrayQueue() {
            array = new Array<>();
        }
    
        //实现getSize()方法
        @Override
        public int getSize() {
            return array.getSize();
        }
    
        //实现isEmpty方法
        @Override
        public boolean isEmpty() {
            return array.isEmpty();
        }
    
        //实现getCapacity方法
        public int getCapacity() {
            return array.getCapacity();
        }
    
        //实现enqueue方法
        @Override
        public void enqueue(E e) {
            array.addLast(e);
        }
    
        //实现dequeue方法
        @Override
        public E dequeue() {
            return array.removeFirst();
        }
    
        //实现getFront方法
        @Override
        public E getFront() {
            return array.getFirst();
        }
    
        //方便打印测试
        @Override
        public String toString() {
            StringBuilder res = new StringBuilder();
            res.append("Queue: ");
            res.append("front [");
            for (int i = 0; i < array.getSize(); i++) {
                res.append(array.get(i));
                if (i != array.getSize() - 1) {
                    res.append(", ");
                }
            }
            res.append("] tail");
            return res.toString();
        }
    
      // 测试
        public static void main(String[] args){
            ArrayQueue<Integer> queue = new ArrayQueue<>();
    
            // 测试入队
            for(int i=0;i<5;i++){
                queue.enqueue(i);
            }
            System.out.println(queue);
    
            // 测试出队
            queue.dequeue();
            System.out.println(queue);
        }
    }
  • 输出结果:
  • Queue: front [0, 1, 2, 3, 4] tail
    Queue: front [1, 2, 3, 4] tail

3.. 数组队列的时间复杂度分析:

  • ArrayQueue<E>
    ·void enqueue(E)   O(1)    均摊
    ·E dequeue()   O(n)
    ·E getFront()  O(1)
    ·int getSize()   O(1)
    ·boolean isEmpty()   O(1)

4.. 循环队列

  • 数组队列的出队操作的复杂度是O(n),性能很差,解决方法就是使用循环队列(Loop Queue)
  • 循环队列的示意图如下:
  • 实现循环队列的业务逻辑,并进行测试:
  • public class LoopQueue<E> implements Queue<E> {
    
        private E[] data;
        private int front, tail;
        private int size;
    
        //构造函数
        public LoopQueue(int capacity) {
            data = (E[]) new Object[capacity + 1];
            front = 0;
            tail = 0;
            size = 0;
        }
    
        //无参数构造函数
        public LoopQueue() {
            this(10); //直接调用有参数的构造函数,然后传入一个默认值
        }
    
        //实现getCapacity方法
        public int getCapacity() {
            return data.length - 1;
        }
    
        //实现isEmpty方法
        @Override
        public boolean isEmpty() {
            return front == tail;
        }
    
        //实现getSize方法
        @Override
        public int getSize() {
            return size;
        }
    
        //实现enqueue方法
        @Override
        public void enqueue(E e) {
            //判断队列是否已满
            if ((tail + 1) % data.length == front) {
                resize(getCapacity() * 2);
            }
    
            data[tail] = e;
            tail = (tail + 1) % data.length;
            size++;
        }
    
        //实现dequeue方法
        @Override
        public E dequeue() {
            //判断队列是否为空
            if (isEmpty()) {
                throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
            }
    
            E ret = data[front];
            data[front] = null;
            front = (front + 1) % data.length;
            size--;
    
            if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
                resize(getCapacity() / 2);
            }
            return ret;
        }
    
        //实现getFront方法
        @Override
        public E getFront() {
            if (isEmpty()) {
                throw new IllegalArgumentException("Queue is empty.");
            }
            return data[front];
        }
    
        //实现resize方法
        private void resize(int newCapacity) {
            E[] newData = (E[]) new Object[newCapacity + 1];
            for (int i = 0; i < size; i++) {
                newData[i] = data[(i + front) % data.length];
            }
            data = newData;
            front = 0;
            tail = size;
        }
    
        //方便打印测试
        @Override
        public String toString() {
            StringBuilder res = new StringBuilder();
            res.append(String.format("Queue: size=%d, capacity=%d\n", size, getCapacity()));
            res.append("front [");
            for (int i = front; i != tail; i = (i + 1) % data.length) {
                res.append(data[i]);
                if ((i + 1) % data.length != tail) {
                    res.append(", ");
                }
            }
            res.append("] tail");
            return res.toString();
        }
    
        //测试
        public static void main(String[] args) {
            LoopQueue<Integer> queue = new LoopQueue<>();
    
            // 测试入队
            for (int i = 0; i < 5; i++) {
                queue.enqueue(i);
            }
            System.out.println(queue);
    
            // 测试出队
            queue.dequeue();
            System.out.println(queue);
        }
    }
  • 输出结果:
  • Queue: size=5, capacity=10
    front [0, 1, 2, 3, 4] tail
    Queue: size=4, capacity=10
    front [1, 2, 3, 4] tail

5.. 循环队列的复杂度分析

  • LoopQueue<E>
    ·void enqueue(E)   O(1)    均摊
    ·E dequeue()   O(1)   均摊
    ·E getFront()  O(1)
    ·int getSize()   O(1)
    ·boolean isEmpty()   O(1)

6.. 使用简单算例测试ArrayQueue与LoopQueue的性能差异

  • import java.util.Random;
    
    public class Main {
    
        // 测试使用q运行opCount个enqueue和dequeue操作所需要的时间,单位:秒
        private static double testQueue(Queue<Integer> q, int opCount) {
            long startTime = System.nanoTime();
    
            Random random = new Random();
            for (int i = 0; i < opCount; i++) {
                q.enqueue(random.nextInt(Integer.MAX_VALUE));
            }
            for (int i = 0; i < opCount; i++) {
                q.dequeue();
            }
    
            long endTime = System.nanoTime();
            return (endTime - startTime) / 1000000000.0;
        }
    
        public static void main(String[] args) {
    
            int opCount = 100000;
    
            ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
            double time1 = testQueue(arrayQueue, opCount);
            System.out.println("ArrayQueue, time: " + time1 + " s");
    
            LoopQueue<Integer> loopQueue = new LoopQueue<>();
            double time2 = testQueue(loopQueue, opCount);
            System.out.println("LoopQueue, time: " + time2 + " s");
    
        }
    }
  • 输出结果
  • ArrayQueue, time: 2.88077896 s
    LoopQueue, time: 0.01140229 s

原文地址:https://www.cnblogs.com/xuezou/p/9277880.html

时间: 2024-11-07 16:29:00

第二十四篇 玩转数据结构——队列(Queue)的相关文章

第二十二篇 玩转数据结构——构建动态数组

1.. 数组基础 数组就是把数据码成一排进行存放. Java中,数组的每个元素类型必须相同,可以都为int类型,string类型,甚至是自定义类型. 数组的命名要语义化,例如,如果数组用来存放学生的成绩,那么命名为scores就比较合适. 索引(index)是数组中的一个重要概念,它是我们给数组中的每个元素分配的编号,从0开始,依次递增.如果数组中存放了n个元素,第一个元素的索引是0,最后一个元素的索引是n-1. 通过索引,我们可以对数组中的元素进行快速访问,例如,我们访问索引为2的元素也就是数

第二十六篇 玩转数据结构——二分搜索树

1.. 二叉树 跟链表一样,二叉树也是一种动态数据结构,即,不需要在创建时指定大小. 跟链表不同的是,二叉树中的每个节点,除了要存放元素e,它还有两个指向其它节点的引用,分别用Node left和Node right来表示. 类似的,如果每个节点中有3个指向其它节点的引用,就称其为"三叉树"... 二叉树具有唯一的根节点. 二叉树中每个节点最多指向其它的两个节点,我们称这两个节点为"左孩子"和"右孩子",即每个节点最多有两个孩子. 一个孩子都没有

Egret入门学习日记 --- 第二十四篇(书中 9.12~9.15 节 内容)

第二十四篇(书中 9.12~9.15 节 内容) 开始 9.12节 内容. 重点: 1.TextInput的使用,以及如何设置加密属性. 操作: 1.TextInput的使用,以及如何设置加密属性. 创建exml文件,拖入组件,设置好id. 这是显示密码星号处理的属性. 创建绑定类. 实例化,并运行. 但是焦点在密码输入框时,密码是显示的. 暂时不知道怎么设置 “焦点在密码框上时,还是显示为 * 号” 的方法. 至此,9.12节 内容结束. 开始 9.13节 . 这个,和TextInput的使用

Android UI开发第二十四篇——Action Bar

Action bar是一个标识应用程序和用户位置的窗口功能,并且给用户提供操作和导航模式.在大多数的情况下,当你需要突出展现用户行为或全局导航的activity中使用action bar,因为action bar能够使应用程序给用户提供一致的界面,并且系统能够很好根据不同的屏幕配置来适应操作栏的外观.你能够用ActionBar的对象的API来控制操作栏的行为和可见性,这些API被添加在Android3.0(API 级别 11)中. Action bar的主要目的是: 1.  提供一个用于识别应用

python全栈开发基础【第二十四篇】(利用threading模块开线程、join与守护线程、GIL与Lock)

一多线程的概念介绍 threading模块介绍 threading模块和multiprocessing模块在使用层面,有很大的相似性. 二.开启多线程的两种方式 创建线程的开销比创建进程的开销小,因而创建线程的速度快. #开启进程的第一种方式 from multiprocessing import Process from threading import Thread import os import time def work(): print('<%s> is running'%os.g

Python之路【第二十四篇】Python算法排序一

什么是算法 1.什么是算法 算法(algorithm):就是定义良好的计算过程,他去一个或一组的值为输入,并产生出一个或一组至作为输出.简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果. 2.算法的意义 假设计算机无限快,并且计算机存储容器是免费的,我们还需要各种乱七八糟的算法吗?如果计算机无限快,那么对于某一个问题来说,任何一个都可以解决他的正确方法都可以的! 当然,计算机可以做到很快,但是不能做到无限快,存储也可以很便宜但是不能做到免费. 那么问题就来了效率:解决同一个问题的各

第二十四篇 自作的高仿画板(1)

唉,今天是一个繁忙而又充实的一天,因为今天的我第一次坐在电脑桌旁这么久,感觉手都有点酸,但是真的觉得收获还是蛮多的,,,, 好了,接下来就直接看代码吧 ,效果自己执行看一下吧: 第一步,新建一个主函数(我用记事本写的 不是很规范,还望多多包涵): public class Test { public static void main(String []args){ DraWing  draWing=new DraWing(); draWing.ShowMainJFrame(); } } 第二布,

Python之路【第二十四篇】:Python学习路径及练手项目合集

Python学习路径及练手项目合集 Wayne Shi· 2 个月前 参照:https://zhuanlan.zhihu.com/p/23561159 更多文章欢迎关注专栏:学习编程. 本系列Python技术路径中包含入门知识.Python基础.Web框架.基础项目.网络编程.数据与计算.综合项目七个模块.路径中的教程将带你逐步深入,学会如何使用 Python 实现一个博客,桌面词典,微信机器人或网络安全软件等.完成本路径的基础及项目练习,将具备独立的Python开发能力. 完整的Python学

第二十四篇 jQuery 学习6 删除元素

jQuery 学习6 删除元素 上节课我们做了添加元素,模拟的是楼主发的文章,路人评论,那么同学们这节课学了删除之后,去之前的代码上添加一个删除,模拟一个楼主删除路人的评论. jQuery的删除方法: remove() - 删除被选元素(及其子元素) empty() - 从被选元素中删除子元素 以上引用w3c教程 为了同学们更好的扩展,老师就不在上节课的基础上模拟删除,而是写出删除的功能代码和注意事项,同学们就自己多尝试,来瞧瞧代码先: <!DOCTYPE html> <html>