数据结构 ---[队列 (Queue)]

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 ---[队列 (Queue)]相关的知识,希望对你有一定的参考价值。



队列


队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)

只能从一端(队尾)添加元素,从另一端(队首)取出元素

先进先出(FIFO—first in first out)


自定义队列的基本操作实现


返回值类型方法注释
voidenqueue( )元素入队
Edequeue( )元素出队
intgetSize( )查看元素个数
EgetFront( )查看队首元素
booleanisEmpty( )判断队列是否为空

自定义队列基本操作的接口

/**
 * 自定义队列的基本操作接口.
 * @param <E> 泛型,数据类型;
 * @author 小智
 * @create 2021-07-25 12:43
 */
public interface MyQueue<E> {
    /**
     * 获取队列中的实际元素个数;
     */
    int getSize();

    /**
     * 判断队列是否为空;
     */
    boolean isEmpty();

    /**
     * 入队(添加元素);
     * @param element 需要添加的元素;
     */
    void enqueue(E element);

    /**
     * 出队(删除元素);
     */
    E dequeue();

    /**
     * 获取队首元素;
     */
    E getFront();
}

自定义队列的基本操作实现类

/**
 * 自定义队列的基本操作实现类;
 * @author 小智
 * @create 2021-07-25 12:47
 */
public class MyQueueImpl<E> implements MyQueue<E> {
    /**
     * 队列的容器;
     */
    private MyselfArray<E> myselfArray;

    /**
     * 无参构造;
     */
    public MyQueueImpl() {
        this(20);
    }

    /**
     * 有参构造;
     */
    public MyQueueImpl(int capacity) {
        myselfArray = new MyselfArray<>(capacity);
    }

    /**
     * 获取队列中的实际元素个数;
     * @return
     */
    @Override
    public int getSize() {
        return myselfArray.getSize();
    }

    /**
     * 判断队列是否为空;
     * @return
     */
    @Override
    public boolean isEmpty() {
        return myselfArray.isEmpty();
    }

    /**
     * 入队(添加元素);
     * @param element 需要添加的元素;
     */
    @Override
    public void enqueue(E element) {
        try {
            myselfArray.addTail(element);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 出队(删除元素);
     * @return
     */
    @Override
    public E dequeue() {
        try {
            return myselfArray.removeHead();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取队首元素;
     * @return
     */
    @Override
    public E getFront() {
        try {
            return myselfArray.getHead();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 输出队列;
     */
    @Override
    public String toString() {
        StringBuilder sbl=new StringBuilder();
        sbl.append("{队首:");
        if(this.isEmpty()){
            return null;
        }
        //只要队列不为空,就让元素出队;
        while(!this.isEmpty()){
            sbl.append(this.dequeue()+";");
        }
        String substring = sbl.substring(0, sbl.lastIndexOf(";"));
        return substring+" 队尾}";
    }
}

dequeue()操作的时间复杂度为O(n)

由于在出队( 删除元素 )时,数组后面的元素要进行前移,
为解决前移问题,可使用front记录队首位置,使用tail记录等待入队的元素位置;
即循环队列实现.

循环队列
为充分利用向量空间,克服"假溢出"现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量。存储在其中的队列称为循环队列(Circular Queue)。循环队列是把顺序队列首尾相连,把存储队列元素的表从逻辑上看成一个环,成为循环队列。


关于循环队列


为解决前移问题,可使用front记录队首位置,使用tail记录(待插入)等待入队的元素位置;

  • 状况(1) : 规定当 front == tail ; 队列为空即队首位置与待插入位置指向同一位置时,


  • 状况(2) : 当队列还未满时,可将要插入的元素,存到队列空余的位置;即使该位置是已经删除元素后、剩余的空位置.


  • 状况(3) : 当队列满时,为避免与状况(1)中规定的空队列 front = = tail 相矛盾;当队列剩余1个空间时便认为已满. 即 ( tail + 1 ) %队列长度 == front 时,队列已满.


使用循环队列的主要目的是降低出栈时的时间复杂度,以空间换取时间.


循环队列的实现


自定义循环队列;

/**
 * @create 2021-07-26 14:57
 * 循环队列实现;
 */
public class MyLoopQueue<E> implements MyQueue<E> {

    /**
     * data: 队列的容器;
     */
    private E[] data;
    /**
     * front:队首位置; tail:表示待插入元素位置;
     */
    private int front, tail;
    /**
     * size: 队列的元素个数;
     */
    private int size;

    /**
     * 无参构造方法;
     */
    public MyLoopQueue() {
        this(20);
    }

    /**
     * 有参构造方法;
     */
    public MyLoopQueue(int capacity) {
        //注意要多出一个空间;
        data = (E[]) new Object[capacity + 1];
        front = 0;
        tail = 0;
        size = 0;
    }

    /**
     * 查看队列元素个数;
     */
    @Override
    public int getSize() {
        return this.size;
    }

    /**
     * 判断队列是否为空的方法;
     */
    @Override
    public boolean isEmpty() {
        return front == tail;
    }

    /**
     * 调整容量大小的方法;
     */
    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity];
        //将源数组中的元素存入调整大小后的数组中(元素位置不能变);
        for (int i = 0; i < size; i++) {
            newData[i] = data[(front + i) % data.length];
        }
        tail = size;
        front = 0;
        data = newData;
    }

    /**
     * 入队;
     * @param element 需要添加的元素;
     */
    @Override
    public void enqueue(E element) {
        //若队列已满;队列扩容;
        if ((tail + 1) % data.length == front) {
            resize(2 * data.length);
        }
        data[tail] = element;
        size++;
        tail = (tail + 1) % data.length;
    }

    /**
     * 出队
     * @return
     */
    @Override
    public E dequeue() {
        //注意队列为空时的状况;
        if (front == tail) {
            return null;
        }
        E result = data[front];
        size--;
        front = (front + 1) % data.length;
        //容量不足1/4时缩容;
        if (size == data.length / 4 && data.length / 2 > 0) {
            resize(data.length / 2);
        }
        return result;
    }

    /**
     * 查看队首元素;
     * @return
     */
    @Override
    public E getFront() {
        if (front != tail) {
            return data[front];
        }
        return null;
    }

    /**
     * 输出队列;
     */
    @Override
    public String toString() {
        StringBuilder sbl = new StringBuilder();
        sbl.append("队列中有 " + size + " 个元素|");
        sbl.append("队首{");
       /* for (int i = 0; i < size; i++) {
            sbl.append(data[front + i % size]);
            if (i != size - 1) {
                sbl.append(",");
            }
        }*/
        //使用临时变量存储队首位置front;
        int temp = front;
        while (front != tail) {
            sbl.append(data[front] + ",");
            front = (front + 1) % data.length;
        }
        //循环打印元素之后,再将front初始值归还;
        front = temp;
        sbl.append("}队尾");
        return sbl.toString();
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        //循环队列测试;
        MyLoopQueue<Integer> myLoopQueue = new MyLoopQueue<>();
        Random random = new Random();
        for (int i = 0; i < 10; i++){
            myLoopQueue.enqueue(random.nextInt(50));
        System.out.println(myLoopQueue.toString());
    }
    //出队:
    while (!myLoopQueue.isEmpty()){
        Integer dequeue = myLoopQueue.dequeue();
        //System.out.println(dequeue);
        System.out.println(myLoopQueue.toString());
    }
    }
}

结果:

队列中有 1 个元素|队首{36,}队尾
队列中有 2 个元素|队首{36,47,}队尾
队列中有 3 个元素|队首{36,47,48,}队尾
队列中有 4 个元素|队首{36,47,48,40,}队尾
队列中有 5 个元素|队首{36,47,48,40,2,}队尾
队列中有 6 个元素|队首{36,47,48,40,2,19,}队尾
队列中有 7 个元素|队首{36,47,48,40,2,19,15,}队尾
队列中有 8 个元素|队首{36,47,48,40,2,19,15,19,}队尾
队列中有 9 个元素|队首{36,47,48,40,2,19,15,19,44,}队尾
队列中有10 个元素|队首{36,47,48,40,2,19,15,19,44,38,}队尾
队列中有 9 个元素|队首{47,48,40,2,19,15,19,44,38,}队尾
队列中有 8 个元素|队首{48,40,2,19,15,19,44,38,}队尾
队列中有 7 个元素|队首{40,2,19,15,19,44,38,}队尾
队列中有 6 个元素|队首{2,19,15,19,44,38,}队尾
队列中有 5 个元素|队首{19,15,19,44,38,}队尾
队列中有 4 个元素|队首{15,19,44,38,}队尾
队列中有 3 个元素|队首{19,44,38,}队尾
队列中有 2 个元素|队首{44,38,}队尾
队列中有 1 个元素|队首{38,}队尾
队列中有 0 个元素|队首{}队尾

以上是关于数据结构 ---[队列 (Queue)]的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段5——HTML元素结构

超级有用的9个PHP代码片段

python 用于数据探索的Python代码片段(例如,在数据科学项目中)

将代码片段插入数据库并在 textarea 中以相同方式显示

分享几个实用的代码片段(第二弹)

分享几个实用的代码片段(第二弹)