数据结构-队列

数据结构-队列

定义

队列(queue)在计算机科学中,是一种先进先出的线性表。
它只允许在表的前端进行删除操作,而在表的后端进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。

基于自定义数组实现的队列

新建queue接口,用来规范所有queue子类

package com.datastructure.queue;

import java.awt.*;

/**
 * @program: test
 * @description:
 * @author: Mr.Yang
 * @create: 2019-05-03 16:44
 **/
public class LoopQueue<E> implements Queue<E> {

    private E[] data;

    //指向队列的第一个元素,初始指向0
    private int front;

    //指向队列的最后一个元素的后一个位置,初始指向0
    private int tail;

    private int size;

    public LoopQueue(int capacity){
        data = (E[]) new Object[capacity+1];
        front=0;
        tail=0;
        size=0;
    }

    public LoopQueue(){
        this(10);
    }

    /**
     * 因为容量放的时候多了个1,所以get容量的时候,需要减1
     * @return
     */
    public int getCapacity(){
        return data.length-1;
    }


    /**
     * 1.if((tail + 1) % data.length == front) 如果tail + 1 超过了data.length的大小,
     *   代表当前tail指向已经超出了容量的大小,因为是循环式,所以需要tail去循环头元素中查看值是否有被占用,
     *   如果 == front 代表循环头没有,就需要扩容了。
     * 2.举例: 元素容量为8,tail目前指向7 front 指向2
     *          if((7 + 1) % 8 == 2 )  if(0 == 2) 这里是false,因为front指向了2,所以代表 第0,1位是没有值的
     *          所以这个值需要在在第0位放(空间利用)
     * 3.data[tail] = param  tail当前指向的地方需要赋值,然后tail自增 循环体 的1,size+1
     * @param param
     */
    @Override
    public void enqueue(E param) {
        if((tail + 1) % data.length == front){
            resize(getCapacity() * 2);
        }
        data[tail] = param ;
        tail = (tail + 1) % data.length;
        size ++ ;
    }

    /**
     * 扩充队列的容量
     * 1.front代表了当前元素初始位置的指向
     * 2.newData的第i位元素,应该等于 i + front % data.length 的值
     * 3.举例:元素容量20,i 等于 0 ,front 等于 2,结果: newData[0] = data[(0 + 2) %  20]
     *         = data[2]   意思就是,newData的第一位元素,应该等于data有值的第一位元素
     *         % data.length 的原因主要是为了防止数组越界错误
     * 4.新数组赋值完成需要将 front 重新指向0,因为新数组的front指针是从0开始的。
     *   tail最后要指向等于size大小的值,
     * @param newCapacity
     */
    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;
    }

    /**
     * 1.如果队列为空抛出异常
     * 2.用ret变量来接受当前队列头的值
     * 3.接收成功之后将,队列头元素置空
     * 4.front指针指向下一个元素
     * 5.size大小-1
     * 6.如果size大小占据了容量的1/4和size为容量的1/2且不等于0的时候,对容量进行缩减,缩减为原来容量的1/2
     * 7.返回ret变量
     * @return
     */
    @Override
    public E dequeue() {
        if(isEmpty()){
            throw new IllegalArgumentException("dequeue is fail ,because queue is empty");
        }
        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;
    }

    @Override
    public E getFront() {
        if(isEmpty()){
            throw new IllegalArgumentException("queue is empty");
        }
        return data[front];
    }

    @Override
    public int getSize() {
        return size;
    }

    /**
     * 当front和tail的值相等时,队列为空,初始两个指向的是同一个值(只有初始的时候,指向的是同一个地方)
     * @return
     */
    @Override
    public boolean isEmpty() {
        return front == tail;
    }

    /**
     * 1.元素从 front位置开始循环遍历,i的值不能等于tail,
     *   也就是到tail的前一位,i = i + 1 且%data.length,
     *   因为i有可能从循环头重新开始
     * 2.( i + 1 ) % data.length != tail  如果当前i + 1 % data.length
     *   不等于tail表示不到最后一个元素,就拼接,
     * @return
     */
    @Override
    public String toString(){
        StringBuffer sb = new StringBuffer();
        sb.append(String.format("Array: size = %d , capacity = %d\n", size, getCapacity()));
        sb.append("front [");
        for (int i = front; i != tail ; i = (i + 1) % data.length) {
            sb.append(data[i]);
            if (( i + 1 ) % data.length != tail) {
                sb.append(", ");
            }
        }
        sb.append("] tail");
        return sb.toString();
    }
}

新建ArrayQueue实现类

package com.datastructure.queue;

import com.datastructure.array.Array;

/**
 * @program: test
 * @description:
 * @author: Mr.Yang
 * @create: 2019-05-03 18:19
 **/
public class ArrayQueue<E> implements Queue<E>{

    Array<E> array;


    public ArrayQueue(int capacity){
        array=new Array<E>(capacity);
    }



    public ArrayQueue(){
        array=new Array<E>();
    }


    @Override
    public void enqueue(E param) {
        array.addLast(param);
    }

    @Override
    public E dequeue() {
        return array.removeFirst();
    }

    @Override
    public E getFront() {
        return array.getFirst();
    }

    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }

    @Override
    public String toString(){
        StringBuffer sb = new StringBuffer();
        sb.append("front: ");
        sb.append("[");
        for(int i=0;i<array.getSize();i++){
            sb.append(array.get(i));
            if(i!=array.getSize()-1){
                sb.append(", ");
            }
        }
        sb.append("]  tail");
        return sb.toString();
    }
}

测试类

package com.datastructure.queue;

/**
 * @program: test
 * @description:
 * @author: Mr.Yang
 * @create: 2019-05-03 18:26
 **/
public class QueueTest {

    public static void main(String[] args) {
        ArrayQueue<Integer> integerArrayQueue = new ArrayQueue<>();
        for (int i = 0; i < 10; i++) {
            integerArrayQueue.enqueue(i);
            System.out.println(integerArrayQueue);


            if(i % 3 == 2){
                integerArrayQueue.dequeue();
                System.out.println(integerArrayQueue);
            }
        }
    }

}

测试结果

front: [0]  tail
front: [0, 1]  tail
front: [0, 1, 2]  tail
front: [1, 2]  tail
front: [1, 2, 3]  tail
front: [1, 2, 3, 4]  tail
front: [1, 2, 3, 4, 5]  tail
front: [2, 3, 4, 5]  tail
front: [2, 3, 4, 5, 6]  tail
front: [2, 3, 4, 5, 6, 7]  tail
front: [2, 3, 4, 5, 6, 7, 8]  tail
front: [3, 4, 5, 6, 7, 8]  tail
front: [3, 4, 5, 6, 7, 8, 9]  tail

可以看到测试结果是正确的,也符合队列结构的数据存取,但是因为是基于自定义数组来实现的,所以会调用数组方法的removeFirst方法,删除第一个元素的同时,会重新将后面所有元素前移,索引前移,均摊时间复杂度为O(n)。

循环队列

循环队列中有两个新词,两个指针

  • front 指向队列的第一个元素,初始指向0
  • tail 指向队列的最后一个元素的后一个位置,初始指向0
a.gif

建立一个loopqueue实现queue接口

package com.datastructure.queue;

import java.awt.*;

/**
 * @program: test
 * @description:
 * @author: Mr.Yang
 * @create: 2019-05-03 16:44
 **/
public class LoopQueue<E> implements Queue<E> {

    private E[] data;

    //指向队列的第一个元素,初始指向0
    private int front;

    //指向队列的最后一个元素的后一个位置,初始指向0
    private int tail;

    private int size;

    public LoopQueue(int capacity){
        data = (E[]) new Object[capacity+1];
        front=0;
        tail=0;
        size=0;
    }

    public LoopQueue(){
        this(10);
    }

    /**
     * 因为容量放的时候多了个1,所以get容量的时候,需要减1
     * @return
     */
    public int getCapacity(){
        return data.length-1;
    }


    /**
     * 1.if((tail + 1) % data.length == front) 如果tail + 1 超过了data.length的大小,
     *   代表当前tail指向已经超出了容量的大小,因为是循环式,所以需要tail去循环头元素中查看值是否有被占用,
     *   如果 == front 代表循环头没有,就需要扩容了。
     * 2.举例: 元素容量为8,tail目前指向7 front 指向2
     *          if((7 + 1) % 8 == 2 )  if(0 == 2) 这里是false,因为front指向了2,所以代表 第0,1位是没有值的
     *          所以这个值需要在在第0位放(空间利用)
     * 3.data[tail] = param  tail当前指向的地方需要赋值,然后tail自增 循环体 的1,size+1
     * @param param
     */
    @Override
    public void enqueue(E param) {
        if((tail + 1) % data.length == front){
            resize(getCapacity() * 2);
        }
        data[tail] = param ;
        tail = (tail + 1) % data.length;
        size ++ ;
    }

    /**
     * 扩充队列的容量
     * 1.front代表了当前元素初始位置的指向
     * 2.newData的第i位元素,应该等于 i + front % data.length 的值
     * 3.举例:元素容量20,i 等于 0 ,front 等于 2,结果: newData[0] = data[(0 + 2) %  20]
     *         = data[2]   意思就是,newData的第一位元素,应该等于data有值的第一位元素
     *         % data.length 的原因主要是为了防止数组越界错误
     * 4.新数组赋值完成需要将 front 重新指向0,因为新数组的front指针是从0开始的。
     *   tail最后要指向等于size大小的值,
     * @param newCapacity
     */
    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;
    }

    /**
     * 1.如果队列为空抛出异常
     * 2.用ret变量来接受当前队列头的值
     * 3.接收成功之后将,队列头元素置空
     * 4.front指针指向下一个元素
     * 5.size大小-1
     * 6.如果size大小占据了容量的1/4和size为容量的1/2且不等于0的时候,对容量进行缩减,缩减为原来容量的1/2
     * 7.返回ret变量
     * @return
     */
    @Override
    public E dequeue() {
        if(isEmpty()){
            throw new IllegalArgumentException("dequeue is fail ,because queue is empty");
        }
        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;
    }

    @Override
    public E getFront() {
        if(isEmpty()){
            throw new IllegalArgumentException("queue is empty");
        }
        return data[front];
    }

    @Override
    public int getSize() {
        return size;
    }

    /**
     * 当front和tail的值相等时,队列为空,初始两个指向的是同一个值(只有初始的时候,指向的是同一个地方)
     * @return
     */
    @Override
    public boolean isEmpty() {
        return front == tail;
    }

    /**
     * 1.元素从 front位置开始循环遍历,i的值不能等于tail,
     *   也就是到tail的前一位,i = i + 1 且%data.length,
     *   因为i有可能从循环头重新开始
     * 2.( i + 1 ) % data.length != tail  如果当前i + 1 % data.length
     *   不等于tail表示不到最后一个元素,就拼接,
     * @return
     */
    @Override
    public String toString(){
        StringBuffer sb = new StringBuffer();
        sb.append(String.format("Array: size = %d , capacity = %d\n", size, getCapacity()));
        sb.append("front [");
        for (int i = front; i != tail ; i = (i + 1) % data.length) {
            sb.append(data[i]);
            if (( i + 1 ) % data.length != tail) {
                sb.append(", ");
            }
        }
        sb.append("] tail");
        return sb.toString();
    }
}

测试代码

package com.datastructure.queue;

/**
 * @program: test
 * @description:
 * @author: Mr.Yang
 * @create: 2019-05-03 18:26
 **/
public class QueueTest {



    public static void main(String[] args) {
        LoopQueue<Integer> integerArrayQueue = new LoopQueue<>();
        for (int i = 0; i < 10; i++) {
            integerArrayQueue.enqueue(i);
            System.out.println(integerArrayQueue);


            if(i % 3 == 2){
                integerArrayQueue.dequeue();
                System.out.println(integerArrayQueue);
            }
        }
    }
}

测试结果

Array: size = 1 , capacity = 10
front [0] tail
Array: size = 2 , capacity = 10
front [0, 1] tail
Array: size = 3 , capacity = 10
front [0, 1, 2] tail
Array: size = 2 , capacity = 5
front [1, 2] tail
Array: size = 3 , capacity = 5
front [1, 2, 3] tail
Array: size = 4 , capacity = 5
front [1, 2, 3, 4] tail
Array: size = 5 , capacity = 5
front [1, 2, 3, 4, 5] tail
Array: size = 4 , capacity = 5
front [2, 3, 4, 5] tail
Array: size = 5 , capacity = 5
front [2, 3, 4, 5, 6] tail
Array: size = 6 , capacity = 10
front [2, 3, 4, 5, 6, 7] tail
Array: size = 7 , capacity = 10
front [2, 3, 4, 5, 6, 7, 8] tail
Array: size = 6 , capacity = 10
front [3, 4, 5, 6, 7, 8] tail
Array: size = 7 , capacity = 10
front [3, 4, 5, 6, 7, 8, 9] tail

打印结果跟自定义数组的结果是一样的,但是因为引用了指针这个概念,删除的时候索引不会重排,均摊时间复杂度为O(1)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容