java实现队列-------(数组队列、循环队列)

本文章介绍队列,分别介绍数组队列、循环队列二种队列,并对其进行比较。

什么是队列:队列是只允许在一端进行插入操作,而在另一端进行删除操作. 队列是一种先进先出(FIFO)的线性表,允许插入的一端称为队尾,允许删除的一端称为队头.

首先创建Queue接口,面向接口编程,其中接口中创建以下方法:

getSize:获取队列中的元素个数

isEmpty:查看队列中是否为空

enqueue:入队 向队尾添加元素

dequeue:出队 将队首的元素拿出

getFront:查看队首的元素

public interface Queue<E> {
    int getSize();
    boolean isEmpty();
    void enqueue(E e);
    E dequeue ();
    E getFront();
}

数组队列 

数组队列,顾名思义,此队列底层是用数组实现的,这里就不详细讲解数组代码的实现过程。

请参考https://blog.csdn.net/qq_44313091/article/details/97539644

创建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<>();
    }

以下代码重写了Queue接口中的方法,还重写了toString的方法。

    @Override
    public int getSize () {
        return array.getsize();
    }
    
    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }
    public int getCapacity() {
        return array.getcapacity();
    }
    @Override
    public void  enqueue (E e) {
        array.addLast(e);
    }
    @Override
    public E dequeue () {
        return array.removeFirst();
    }
    @Override
    public E getFront() {
        return  array.get(0);
    } 

@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();
    }

以上就是数组队列实现的全部代码,下面对数组队列中的方法进行时间复杂度分析。其中,数组队列、循环队列、链表队列中getSize、isEmpty方法的时间复杂度均为o(1)

void enqueue (E)         o(1)(均摊)

E  dequeue()           o(n)

E  front()                  o(1)

因为取出队首的元素时,队列中的所有元素都要向前移动一位,所以时间复杂度为o(n);正因为出队的时间复杂度为o(n),增大了运行时间,所以提出了循环队列的概念,将出队的时间复杂度变为o(1)

循环队列

下面讲解一下循环队列的实现思路,循环队列最底层还是以数组来实现。

 

图1,front指向对首元素,tail指向待添加的元素地址。当front和tail指向相同时表示队列为空。

 

 图2中,拿出队首的元素时,不需要将所有的元素前移一位,只需要维护front的指向即可。向队列中添加元素只需要维护tail的指向就可以。

 图3中,当不断地添加元素,导致tail不能继续++,这时大家可以发现,当取出队首的元素时,前面的空间没有被利用,导致前面的空间剩余,这时可以将tail指向前面的空余的空间,这就可以将数组看成一个环状,充分利用了数组的所有空间。

图4,当又向队列中添加一个元素时,如图所示,这时将不能继续添加元素,因为继续添加元素,tail即会和front相等,图1中说道,当tail与front指向相同时,表示队列为空。 (tail+1)%c==front表示队列已满,这里的c表示数组的容量。

以下将是循环队列的实现代码

创建LoopQueue类,实现接口Queue。底层创建数组,因为循环队列会浪费数组中的一个空间,所以构造方法中是capacity+1。

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:返回队列的容量

isEmpty:查看队列是否为空,当front==tail时,队列为空

getSize:返回队列中元素个数

enqueue:向队尾添加元素。首先判断队列是否已满,若满则将队列扩容为原来的2倍;否则,将e添加到tail指向的空间,维护tail和size。

dequeue:取出队首元素。先判断队列是否为空,若为空则抛出异常;否则,返回并删除ront指向的元素,维护front、size。若队列的元素个数为容量的1/4,将队列容量缩小为原队列的1/2。这里是防止频繁的掉头resize方法。

getfront:先判断队列是否为空,若为空则抛出异常;否则,返回front指向的元素。

public int getCapacity() {
        return data.length-1    ;
    }
    
    @Override
    public boolean isEmpty() {
        return front ==tail;
    }
    @Override
    public int getSize() {
        return size;
    }
    @Override
    public void enqueue (E e) {
        if((tail+1)%data.length==front) {
            resize (getCapacity()*2);
        }
        data[tail] =e;
        tail =(tail+1)%data.length;
        size++;
    }
        @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 ;
        }
        @Override
        public E getFront() {
            if(isEmpty()) {
                throw new IllegalArgumentException("Queue is Empty.");
            }
            return data[front];
        }

 resize:对队列容量进行修改,以充分利用内存空间。将原队列front指向的元素放到新队列的首项。

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;
    }

最后重写toString方法

@Override
    public String toString() {
        StringBuilder res =new StringBuilder();
        res.append(String.format("Array: 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();  
    }

 经过以上代码,就将循环队列全部完成,彻底将enqueue方法的时间复杂度改为o(1),大大减小了运行时间。为了充分体现数组队列和循环队列的差别,我写了一个测试方法,来比较ArrayQueue和LoopQueue的运行时间。

testQueue:传入Queue类型,opCount值。方法开始进行计时,一个for循环将opCount个随机数存到队列中,第二个for循环将存入的数逐一取出,最后返回运行时间。

我这里的测试用例为100000,运行之后可以发现LoopQueue的运行时间比ArrayQueue的运行时间短。

private static double testQueue (Queue <Integer>q , int opCount) {
        long startTime  =System.nanoTime();
        Random rd =new Random();
        for(int i=0;i<opCount ;i++) {
            q.enqueue(rd.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(arrayqueue,opCount); 
        System.out.println("LoopQueue,time:"+time2+"s");
    }
}
 

这就是我对队列的理解以及实现代码,若发现错误请留言,不喜勿喷,谢谢!!!!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值