用数组实现队列(顺序队列&循环队列)
用数组实现队列(顺序队列&循环队列)
顺序队列
️
队列(先进先出)

几个问题:
- 队列方法:入队、出队
- 队列的存储:即队首队尾两个指针,
- 扩容:如果队列容量不够了,应该扩容,如果队尾没有位置了,队首有位置,应该把元素往前移
主要是上面三个问题,在代码中都有体现,上面的扩容方法借鉴了ArrayList的扩容方法。
package com.helius.structure.queue;
import java.util.Arrays;
/**
 * 用数组实现一个队列,即顺序队列
 */
public class ArrayQueue {
    // 存储数据的数组
    private Object[] elements;
    //队列大小
    private int size;
    // 默认队列容量
    private int DEFAULT_CAPACITY = 10;
    // 队列头指针
    private int head;
    // 队列尾指针
    private int tail;
    private int MAX_ARRAY_SIZE  = Integer.MAX_VALUE-8;
    /**
     * 默认构造函数 初始化大小为10的队列
     */
    public ArrayQueue(){
        elements = new Object[DEFAULT_CAPACITY];
        initPointer(0,0);
    }
    /**
     * 通过传入的容量大小创建队列
     * @param capacity
     */
    public ArrayQueue(int capacity){
        elements = new Object[capacity];
        initPointer(0,0);
    }
    /**
     * 初始化队列头尾指针
     * @param head
     * @param tail
     */
    private void initPointer(int head,int tail){
        this.head = head;
        this.tail = tail;
    }
    /**
     * 元素入队列
     * @param element
     * @return
     */
    public boolean enqueue(Object element){
        ensureCapacityHelper();
        elements[tail++] = element;//在尾指针处存入元素且尾指针后移
        size++;//队列元素个数加1
        return true;
    }
    private void ensureCapacityHelper() {
        if(tail==elements.length){//尾指针已越过数组尾端
            //判断队列是否已满 即判断数组中是否还有可用存储空间
            //if(size<elements.length){
            if(head==0){
                //扩容
                grow(elements.length);
            }else{
                //进行数据搬移操作 将数组中的数据依次向前挪动直至顶部
                for(int i= head;i<tail;i++){
                    elements[i-head]=elements[i];
                }
                //数据搬移完后重新初始化头尾指针
                initPointer(0,tail-head);
            }
        }
    }
    /**
     * 扩容
     * @param oldCapacity 原始容量
     */
    private void grow(int oldCapacity) {
        int newCapacity = oldCapacity+(oldCapacity>>1);
        if(newCapacity-oldCapacity<0){
            newCapacity = DEFAULT_CAPACITY;
        }
        if(newCapacity-MAX_ARRAY_SIZE>0){
            newCapacity = hugeCapacity(newCapacity);
        }
        elements = Arrays.copyOf(elements,newCapacity);
    }
    private int hugeCapacity(int newCapacity) {
        return (newCapacity>MAX_ARRAY_SIZE)? Integer.MAX_VALUE:newCapacity;
    }
    /**
     * 出队列
     * @return
     */
    public Object dequeue(){
        if(head==tail){
            return null;//队列中没有数据
        }
        Object obj=elements[head++];//取出队列头的元素且头指针后移
        size--;//队列中元素个数减1
        return obj;
    }
    /**
     * 获取队列元素个数
     * @return
     */
    public int getSize() {
        return size;
    }
}
测试用例
public class TestArrayQueue {
    public static void main(String[] args) {
        ArrayQueue queue = new ArrayQueue(4);
        //入队列
        queue.enqueue("helius1");
        queue.enqueue("helius2");
        queue.enqueue("helius3");
        queue.enqueue("helius4");
        //此时入队列应该走扩容的逻辑
        queue.enqueue("helius5");
        queue.enqueue("helius6");
        //出队列
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        //此时入队列应该走数据搬移逻辑
        queue.enqueue("helius7");
        //出队列
        System.out.println(queue.dequeue());
        //入队列
        queue.enqueue("helius8");
        //出队列
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        //入队列
        queue.enqueue("helius9");
        queue.enqueue("helius10");
        queue.enqueue("helius11");
        queue.enqueue("helius12");
        //出队列
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
    }
}
结果:
helius1
helius2
helius3
helius4
helius5
helius6
helius7
helius8
null
helius9
helius10
循环队列
用java实现循环队列的方法:
- 增加一个属性size用来记录目前的元素个数。目的是当head=rear的时候,通过size=0还是size=数组长度,来区分队列为空,或者队列已满。 
- 数组中只存储数组大小-1个元素,保证rear转一圈之后不会和head相等,也就是队列满的时候,rear+1=head,中间刚好空一个元素。 - 当rear=head的时候,一定是队列空了。 
队列(Queue)两端允许操作的类型不一样:
可以进行删除的一端称为队头,这种操作也叫出队dequeue;
可以进行插入的一端称为队尾,这种操作也叫入队enqueue。
队列的示意图

实现队列时,要注意的是假溢出现象,如上图的最后一幅图。
如图所示的假溢出现象,顺序队列可以如此,循环队列我们可以让这个尾指针指向front前面的元素,这也正符合我们想要的循环队列的定义。

解决办法:使用链式存储,这显然可以。在顺序存储时,我们常见的解决办法是把它首尾相接,构成循环队列,这可以充分利用队列的存储空间。
循环队列示意图:

在上图中,front指向队列中第一个元素,rear指向队列队尾的下一个位置。
但依然存在一个问题:当front和rear指向同一个位置时,这代表的是队空还是队满呢?大家可以想象下这种情景。
解决这种问题的常见做法是这样的:
使用一标记,用以区分这种易混淆的情形。
牺牲一个元素空间。当front和rear相等时,为空;当rear的下一个位置是front时,为满。
如下图:


下面我们给出循环队列,并采用第二种方式,即牺牲一个元素空间来区分队空和队满的代码.
几个重点:
1、front指向队头,rear指向队尾的下一个位置。
2、队为空的判断:frontrear;队为满的判断:(rear+1)%MAXSIZEfront。
上面说的rear即为代码中的的tail
/**
 * 使用数组实现循环队列
 * @author Helius
 */
public class CirculiQueue {
    //存储队列数据的数组
    private Object[] elements;
    //默认数组容量
    private int DEFAULT_CAPACITY=10;
    //队列中元素个数
    private int size;
    // 队列头指针
    private int head;
    //队列尾指针
    private int tail;
    /**
     * 默认构造函数
     */
    public CirculiQueue(){
        elements = new Object[DEFAULT_CAPACITY];
    }
    /**
     * 通过传入的容量参数构造队列
     * @param capacity
     */
    public CirculiQueue(int capacity){
        elements = new Object[capacity];
    }
    /**
     * 元素入队列
     * @param element
     * @return
     */
    public boolean enqueue(Object element){
        //判断队列是否已满
        if(head == (tail+1)%elements.length){
            //队列已满
            return false;
        }
        //将元素存入tail位置上
        elements[tail]=element;
        //尾指针后移
        /*tail++;
        if(tail==elements.length){
            tail = 0;
        }*/
        tail = (tail+1)%elements.length;
        size++;
        return true;
    }
    /**
     * 元素出队列
     * @return
     */
    public Object dequeue(){
        //判断队列是否为空
        if(head==tail){
            return null;
        }
        //获取head位置上的元素
        Object element = elements[head];
        //头指针后移
        /*head++;
        if(head==elements.length){
            head = 0;
        }*/
        head = (head+1)%elements.length;
        size--;
        return element;
    }
    /**
     * 获取队列大小
     * @return
     */
    public int getSize() {
        return size;
    }
}
这里也添加了size属性,可以稍做修改,来实现第一种方式。
用数组实现队列(顺序队列&循环队列)的更多相关文章
- 39 _ 队列5 _ 循环队列需要几个参数来确定 及其含义的讲解.swf
		上面讲解都是循环队列,如果是链表实现的话就很简单,队列只有循环队列才比较复杂 此时队列中只存储一个有效元素3,当在删除一个元素的时候,队列为空,pFont向上移动,pFont等于pRear,但是此时p ... 
- 三 基于Java数组手写循环队列
		Code: package dataStucture2.stackandqueue; /** * 手写循环队列 * * @param <E> */ public class MyLoopQ ... 
- javascript实现数据结构与算法系列:队列 -- 链队列和循环队列实现及示例
		1 队列的基本概念 队列(Queue):也是运算受限的线性表.是一种先进先出(First In First Out ,简称FIFO)的线性表.只允许在表的一端进行插入,而在另一端进行删除. 队首(fr ... 
- c++实验5 顺序/链式队列
		链式队列及循环队列 1.循环队列的实现(请采用模板类及模板函数实现) [实现提示] 同时可参见教材p65-p67页的ADT描述及算法实现及ppt)函数.类名称等可自定义,部分变量请加上学号后3位.也可 ... 
- 循环队列+堆优化dijkstra最短路 BZOJ 4152: [AMPPZ2014]The Captain
		循环队列基础知识 1.循环队列需要几个参数来确定 循环队列需要2个参数,front和rear 2.循环队列各个参数的含义 (1)队列初始化时,front和rear值都为零: (2)当队列不为空时,fr ... 
- 《Java数据结构与算法》笔记-CH4-5不带计数字段的循环队列
		第四章涉及三种数据存储类型:栈,队列,优先级队列 1.概括:他们比数组和其他数据存储结构更为抽象,主要通过接口对栈,队列和优先级队列进行定义.这些 接口表明通过他们可以完成的操作,而他们的主要实现机制 ... 
- 数据结构:循环队列(C语言实现)
		生活中有非常多队列的影子,比方打饭排队,买火车票排队问题等,能够说与时间相关的问题,一般都会涉及到队列问题:从生活中,能够抽象出队列的概念,队列就是一个能够实现"先进先出"的存储结 ... 
- 循环队列的C语言实现
		生活中有很多队列的影子,比如打饭排队,买火车票排队问题等,可以说与时间相关的问题,一般都会涉及到队列问题:从生活中,可以抽象出队列的概念,队列就是一个能够实现“先进先出”的存储结构.队列分为链式队列和 ... 
- LeetCode 622——设计循环队列
		1. 题目 设计你的循环队列实现. 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环.它也被称为"环形缓冲器". 循环队列 ... 
- TypeScript算法与数据结构-队列和循环队列
		本文涉及的源码,均在我的github.有两部分队列和循环队列.有问题的可以提个issue,看到后第一时间回复 1. 队列(Queue) 队列也是一种线性的数据结构, 队列是一种先进先出的数据结构.类似 ... 
随机推荐
- Flink深入浅出: 应用部署与原理图解(v1.11)
			往期推荐: Flink深入浅出:内存模型 Flink深入浅出:JDBC Source从理论到实战 Flink深入浅出:Sql Gateway源码分析 Flink深入浅出:JDBC Connector源 ... 
- 利用 Python 插入 Oracle 数据
			# coding=utf-8 ''''' Created on 2020-01-05 @author: Mr. Zheng ''' import json; import urllib2 import ... 
- linxu 命令
			top | grep java 统计 java 进程使用的资源比率 nohub java -jar test.war & 后台运行 test.war 程序,标准输出到 test.war 程序目 ... 
- RxJS入门2之Rxjs的安装
			RxJS V6.0+ 安装 RxJS 的 import 路径有以下 5 种: 1.创建 Observable 的方法.types.schedulers 和一些工具方法 import { Observa ... 
- c++的一些习惯
			1.显示转换:类型说明符(表达式),如a = int(z) 2.枚举类型enum:专门用来解决对数据的合法性检查问题,如一星期七天,如果用int/char等数据类型描述时,则需要对数据的合法性检查.声 ... 
- C#实现迭代器
			迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示.C#中使用IEnumerator接口实现,Java中使用Iterator接口实现,其中原理都差 ... 
- Cesium资料
			CesiumLab论坛:https://github.com/cesiumlab/cesium-lab-forum/issues简书上的Cesium实验室文集:https://www.jianshu. ... 
- Js电子时钟
			简单版电子时钟,需要以下几个步骤 1. 封装一个函数 返回当前的时分秒 2. 使用定时器使当前以获取到的系统时间走动,每间隔一面调用 3. 把获取到的时间放到span盒子里,添加样式 效果展示 实现 ... 
- Linux安装软件时90%的人会遇到这个报错,如何解决?
			提示 Could not get lock /var/lib/dpkg/lock 报错? 有些小伙伴在使用 apt 包管理器更新或安装软件时,可能会遇到过诸如以下的错误提示: E: Could not ... 
- C++学习---队列的构建及操作
			一.循环队列 #include <iostream> using namespace std; #define MAXQSIZE 100 typedef struct { int* bas ... 
