4.线性表之队列(手工实现)

队列

队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。

同样,作为一种特殊的线性表,队列也有顺序存储结构和链式存储结构两种类型。

队列顺序存储的不足
假设一个队列有n个元素,则顺序存储的队列需要建立一个大于n的数组,并把队列所有元素存储在数组的前n个单元,数组下标为0的一端即是队头。入队则是在队尾添加一个元素,不需要移动任何元素,时间复杂度为O(1);出队则是在队头下标为0的位置,也就意味着队列中所有元素都得向前移动,时间复杂度为O(n)

.

但是为什么出队列时需要移动其他元素呢?如果不移动,出队列的效率会大大增加。为了避免当只有一个元素时,队头和队尾重合变得麻烦,引入两个指针front和rear。front指针指向队头元素,rear指针指向队尾元素的下一个位置,这样当front等于rear时此队列不是还剩一个元素而是空队列.

以初始长度为5的数组为例,入队a1、a2、a3、a4,如上图左一所示。出队a1和a2,front指针移动到下标为2的位置,rear指针不变,再入队a5时,rear指针移动到数组之外产生数组越界。而此时下标为0和1的位置是空闲的,我们把这种现象称为假溢出。

解决假溢出的方法就是使用循环队列。

顺序循环队列

头尾相接的顺序存储结构称为顺序循环队列

继续上例,插入a5时,rear指针指向0位置。就会避免 数组越界和假溢出问题。接着入队a6和a7,此时rear指针和front指针指向同一个位置2,那么如何判断队列为空呢? 之前是front=rear时队列为空,现在队列为满队列时front=rear也成立。

  • 方法1:设置一个变量flag,当front==rear且flag=0时为空队列,front==rear且flag=1时为满队列
  • 方法2:当队列空时,就是front=rear,当队列满时,我们修改条件,保留一个元素空间。如下图所示

   对于第二种方法,由于rear可能比front大,也可能他比front小,所以队列满的条件为(rear+1)%QueueSize==front。而当rear>front时,队列长度为rear-front;当rear<front时,队列长度为QueueSize-front + 0+rear。所以综合看,通用的计算队列长度的公式为(rear-front+QueueSize)%QueueSize

类结构图

顺序循环队列封装类

package com.company.datastructure;

public class MyCircleQueue<E> {
    private final int MAX_SIZE = 10;  //最大长度为10(为了演示方便)
    private Object[] elements;  //定义一个数组用来存储元素
    private int front;  //头指针
    private int rear;  //尾指针
    public MyCircleQueue(){
        elements = new Object[MAX_SIZE];
        front = 0;
        rear = 0;
    }

    /**
     * 入队列
     * @param element 元素值
     */
    public void EnQueue(E element){
        if((rear+1)%MAX_SIZE==front){  //判断队列是否已满条件
            throw new IndexOutOfBoundsException("队列已满");
        }else{
            elements[rear] = element;
            rear = (rear+1)%MAX_SIZE;  //队列满的话移到数组开始位置
        }
    }

    /**
     * 出队列
     * @return 出队列元素值
     */
    public E DeQueue(){
        if(rear==front){
            throw new NullPointerException("队列为空");
        }else{
            E OldElement = (E)elements[front];
            front = (front+1)%MAX_SIZE;
            return OldElement;
        }
    }

    /**
     * 获得队列长度
     * @return 队列长度
     */
    public int getSize(){
        return (rear-front+MAX_SIZE)%MAX_SIZE;  //队列长度通用公式
    }

    /**
     * 获得队列头部元素
     * @return
     */
    public E get(){
        return (E)elements[front];
    }

    /**
     * 队列是否为空
     * @return 是返回true,否则返回false
     */
    public boolean isEmpty(){
        return rear==front;
    }
}

测试类

package com.company.datastructure;

public class TestMyCircleQueue {
    public static void main(String[] args) {
        MyCircleQueue<String> mcq = new MyCircleQueue<>();
        for (int i = 0; i < 9; i++) {
            mcq.EnQueue("chen"+i);
        }
        System.out.println(mcq.getSize());
        System.out.println(mcq.get());
        String str = mcq.DeQueue();
        System.out.println(str);
        mcq.DeQueue();
        mcq.DeQueue();
        System.out.println(mcq.get());
        System.out.println(mcq.getSize());
        mcq.EnQueue("chen"+10);
        int size = mcq.getSize();
        for (int i = 0; i < size; i++) {
            System.out.println(mcq.DeQueue());
        }
    }
}

链队列

从上面的顺序循环队列可以看到,可能存在数组溢出的情况,或者是考虑数组溢出时进行数组扩容,但效率会适当降低,所以我们需要不需要考虑长度的链队列。

队列的链式存储,其实就是线性表的单链表,只不过是需要尾进头出而已,我们把它称为链队列

队列的入队,其实就是在单链表的末尾插入一个结点。

队列的出队,就是头结点的后继结点出队,将头节点的后继改为它后面的结点,若链表除头结点外只剩一个结点,需要将rear指针指向头结点。

类结构图

链队列封装类

package com.company.datastructure;

public class MyLinkQueue<E> {
    private class Node{
        private E element;
        private Node next;
        public Node(){

        }
        public Node(E element){
            this.element = element;
        }
    }
    private Node front;
    private Node rear;
    private int size;
    public MyLinkQueue(){
        front = new Node();
        rear = front;
    }

    /**
     * 入队
     * @param element 元素值
     */
    public void EnQueue(E element) {
        Node newNode = new Node(element);
        rear.next = newNode;
        rear = newNode;
        size++;
    }

    /**
     * 出队
     * @return 出队元素值
     */
    public E DeQueue(){
        if(front==rear){
            throw new NullPointerException("队列为空");
        }else{
            Node OldNode = front.next;
            front.next = OldNode.next;
            if(rear==OldNode){
                rear = front;
            }
            size--;
            return OldNode.element;
        }
    }

    /**
     * 获得链队列长度
     * @return 长度
     */
    public int getSize(){
        return size;
    }

    /**
     * 返回队列头部元素
     * @return
     */
    public E get(){
        return front.next.element;
    }
}

测试类

package com.company.datastructure;

public class TestMyLinkQueue {
    public static void main(String[] args) {
        MyLinkQueue<String> mlq = new MyLinkQueue<>();
        mlq.EnQueue("chen0");
        mlq.EnQueue("chen1");
        mlq.EnQueue("chen2");
        System.out.println(mlq.getSize());
        int size = mlq.getSize();
        for (int i = 0; i < size; i++) {
            System.out.println(mlq.get());
            mlq.DeQueue();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_39722922/article/details/86541071
今日推荐