Data Structures and Algorithms (f) circular queue - sequential storage structure

Circular queue


No hurry to define the circular queue, the queue cycle look drawbacks storage structure.

Drawbacks sequential storage queue structure

Sequential storage queue structure itself is achieved by ArrayList, when enqueued data element, corresponding to the end of the table ArrayList additive element, the data element When dequeuing, delete the header ArrayList equivalent elements. Obviously, the time complexity enqueued O (1), the time complexity dequeued O (n), add or delete data elements of linear form complex symbols are time O (n), but the average is calculated. Time complexity queue dequeue O (n), the average is not calculated, because every time a team is O (n).

There are drawbacks, can be optimized for other ideas

A optimization : In order to avoid the time when there is only one element, the tail HOL coincidence processing becomes cumbersome, all incorporated two pointers, front point to the head elements, rear pointing to the tail element, so that the team head pointer and the tail pointer as element the changes move. We enqueue and dequeue operations is O (1).
Problem : rear pointer to the end of the table will not be moved after it, and after the shift has been the team front, in front of the space is wasted.

Optimization two : when team head or the tail pointer reaches the tail portion, can be redirected For backward header, it is equivalent to the linear form connected end to end, into a ring, there is a pointer from the head end of cyclical, i.e. the number length linear form, the linear length of the table pointer +1 modulo pointer can be completed from start to finish jump, this would resolve the problem of wasted space.
Question : But there are new problems, we determine the full queue and queue empty conditions are (rear + 1)% n == front, where n is the length of the linear form, which can not make the right judgment.

Optimization three : the set aside a space does not exist any element, the tail pointer always points to the null space, time, queue full condition (rear + 1)% n == front, the queue is empty condition rear == front ,correct.

In fact, the above optimization result is a circular queue head and tail of the queue we call this contact structure is called a circular queue sequentially stored.

Here Insert Picture Description
The method implemented in Java code sequence stored in the circular queue structure, the new ArrayQueueLoop implement the Queue interface, wherein the rewriting, as follows, detailed notes

import java.util.Iterator;

//循环队列  底层用动态数组实现
public class ArrayQueueLoop<E> implements Queue<E> {
    private E[] data;     //定义数组
    private int front;    //头指针
    private int rear;     //尾指针 指向
    private int size;     //有效元素个数

    public ArrayQueueLoop(){
        data= (E[]) (new Object[11]);//因为有一个空的空间,有效能存储的空间有10个
        front=0;    //初始化
        rear=0;
        size=0;
    }

    @Override
    public int getSize() {
        return size;    //获取有效元素个数,直接返回size即可
    }

    @Override
    public boolean isEmpty() {
        return size==0&&front==rear;    //循环队列为空的条件
    }

    @Override
    public void enqueue(E e) {
        if((rear+1)%data.length==front){       //队列满时
            resize(2*data.length-1);    //扩容,扩二倍
        }
        data[rear]=e;     //把e从队尾入队列,rear角标处是空的空间
        rear=(rear+1)%data.length;  //更新尾指针,rear重新指向空
        size++;   //有效元素+1
    }

    @Override
    public E dequeue() {
        if(isEmpty()){  //队列空时不可出队,抛异常
            throw new IllegalArgumentException("队列空");
        }
        E ret=data[front];  //变量ret存头指针的元素,用于返回
        front=(front+1)%data.length;   //更新头指针
        size--;     //有效元素-1
        //当有效元素小于等于能存储的数组长度,同时缩容后长度的大于等于10,方可进行缩容操作
        if(size<=(data.length-1)/4&&(data.length-1)/2>=10){
            resize((data.length-1)/2+1);   //缩容缩一半
        }
        return ret;    //返回被删除的元素
    }
    //扩缩容函数
    private void resize(int newLen){
        E[] newData= (E[]) (new Object[newLen]);    //创新数组,即缩容后的数组
        int p=front;    //用指针p遍历原数组
        int i=0;        //用指针i遍历新数组
        while(true){
            newData[i]=data[p];    //把原数组中的元素复制给新数组
            i++;
            p=(p+1)%data.length;
            if(p==rear){    //p指针遍历的尾指针处结束循环
                break;
            }
        }
        front=0;    //把头指针指向扩容后数组表头位置
        rear=size;  //尾指针指向有效元素长度角标
        data=newData;   //把新数组给元素组,偷梁换柱
    }

    @Override
    public E getFront() {
        if(isEmpty()){
            throw new IllegalArgumentException("队列为空");
        }
        return data[front];    //获取队头,队头就是头指针处
    }

    @Override
    public E getRear() {
        if(isEmpty()){
            throw new IllegalArgumentException("队列为空");
        }
        return data[(rear-1+data.length)%data.length];  //获取队尾,队尾是尾指针的前一个,尾指针始终指向空
    }

    //清空队列元素,把构造函数里的代码拿过来(初始化状态)
    @Override
    public void clear() {
        data= (E[]) (new Object[11]);//因为有一个空的空间
        front=0;
        rear=0;
        size=0;
    }

    @Override
    public String toString() {
        StringBuilder sb=new StringBuilder();   //创建StringBuilder对象,可以直接在原字符串上添加字符,不许要每次改变都创新字符串
        /*
        输出这样的格式
            ArrayQueueLoop: 15/20
            [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
         */
        sb.append(String.format("ArrayQueueLoop: %d/%d\n",size,data.length-1));
        sb.append('[');
        if(isEmpty()){
            sb.append(']');
        }else{
            for(int i=front;i!=rear;i=(i+1)%data.length){   //打印元素,从头指针处开始,到达指针结束
                sb.append(data[i]); 
                if((i+1)%data.length==rear){    //当遍历到指针i下一个位置是尾指针处时,此时i在最后一个元素处,打印']'
                    sb.append(']');
                }else{                      //否则在元素间打印','
                    sb.append(',');
                }
            }
        }
        return sb.toString();   //返回sb对象,调用toString方法
    }

    @Override
    public Iterator<E> iterator() {
        return new ArrayQueueLoopIterator();    //创建内部类对象
    }
    //迭代器 内部类(就是打印表内元素,并让其支持foreach循环)
    private class ArrayQueueLoopIterator implements Iterator{
        int p=front;      //从头指针处开始
        @Override
        public boolean hasNext() { 
            return p!=rear;     //继续条件:p指针不指向尾指针处
        }

        @Override
        public Object next() {  
            E ret=data[p];      //ret输出元素
            p=(p+1)%data.length;    //p递增
            return ret;
        }
    }
}

Write a test class, when finished each method can test for errors, avoid hard to find after all finished BUG.

Published 70 original articles · won praise 56 · views 1984

Guess you like

Origin blog.csdn.net/qq_43624033/article/details/103572549