1. 程式人生 > >其它 >吹牛必備之迴圈佇列與雙端迴圈佇列

吹牛必備之迴圈佇列與雙端迴圈佇列


技術標籤:資料結構與演算法佇列資料結構java

迴圈佇列

迴圈佇列概念

所謂的迴圈佇列,其實還是一個佇列,從外部呼叫的介面和實際的功能上看和普通的佇列完全沒有區別,而之所以它叫迴圈佇列而不是佇列,是因為它用於實現的底層邏輯和一般佇列不同,一般佇列底層採用雙向連結串列,而迴圈佇列底層採用索引不斷迴圈的陣列實現。

迴圈佇列操作圖示

建立迴圈佇列,勢必要先建立一個數組:

E elements=(E[]) new Object[8];//其中E表示泛型

假如向佇列中入隊11,22,33,44這四個元素,則其儲存結構如圖所示。

吹牛必備之迴圈佇列與雙端迴圈佇列


此時若將元素55入佇列,則直接在尾元素後入佇列:

吹牛必備之迴圈佇列與雙端迴圈佇列


倘若要出佇列,則稍稍複雜一些,將隊頭元素清空,並將front的指向後移:

吹牛必備之迴圈佇列與雙端迴圈佇列


假設在此基礎上出隊3次,併入隊66,77,88,則結果如下圖所示:

吹牛必備之迴圈佇列與雙端迴圈佇列


若在陣列沒有更大的索引位置可以使用時,還要繼續入隊99,則採用索引迴圈的方法,即放到左邊空餘的陣列空間。此時入隊,若元素從0開始放置(即front從0開始),入隊元素應該放置在索引為4即size的位置(因為已有4個元素,分別放置了0,1,2,3的位置),而此時由於front的偏移,入隊元素放置的位置也需要進行偏移,即size+front=8。而8明顯超出了陣列可以用的索引,此時將8對陣列大小取模,放置左側空餘的陣列空間(迴圈)。

吹牛必備之迴圈佇列與雙端迴圈佇列


再次入隊100,101,102:

吹牛必備之迴圈佇列與雙端迴圈佇列


此時佇列已經裝滿,若還要繼續入隊,則需要進行擴容(新建一個更大的陣列,將陣列從0開始遷移過去,並修改原陣列引用物件指向新陣列):

private void ensureCapacity(int newSize){
    int oldCapacity=elements.length;
    //如果容量可以承受則不擴容
    if(newSize<=oldCapacity)return;
    //否則擴容1.5
    newSize=oldCapacity+(oldCapacity>>1);
    E[] newElements= (E[]) new Object[newSize];
    //資料遷移
    for(int i=0;i<size;i++)
        newElements[i]=elements[index(i)];
    elements=newElements;
    front=0;//指標歸零
    System.out.println(oldCapacity+"擴容為"+newSize);
}

/**
* 簡化取模的索引對映
 * @param index
 * @return
 */
private int index(int index){
    //避免取模運算(效率低下)
    index+=front;
    return index-(index>=elements.length?elements.length:0);
}

此時擴容後的結果將為:

吹牛必備之迴圈佇列與雙端迴圈佇列


然後再入隊:

吹牛必備之迴圈佇列與雙端迴圈佇列


以上就是迴圈佇列的操作步驟。

具體實現

public class CircleQueue<E>{
    private int front;//指向頭節點
    private int size;
    private E[] elements;
    //預設佇列大小
    private static final int DEFAULT_CAPACITY=10;

    public  CircleQueue(){
        this(DEFAULT_CAPACITY);
    }

    public  CircleQueue(int capacity){
        capacity=capacity>DEFAULT_CAPACITY?capacity:DEFAULT_CAPACITY;
        elements= (E[]) new Object[capacity];
    }

    /**
     * 獲取佇列大小
     * @return
     */
    public int size(){
        return size;
    }

    /**
     * 判斷陣列是否為空
     * @return
     */
    public boolean isEmpty(){
        return size==0;
    }

    /**
     * 清空佇列
     */
    public void clear(){
        //清空所有引用,等待jvm進行垃圾回收
        for(int i=0;i<size;i++)
            elements[index(i)]=null;
        size=0;
        //還原指標
        front=0;
    }


    /**
     * 入隊
     */
    // 0 0 1 2 3
    public void offer(E element){
        ensureCapacity(size+1);
        elements[index(size)]=element;//末尾入隊
        size++;
    }

    private void ensureCapacity(int newSize){
        int oldCapacity=elements.length;
        //如果容量可以承受
        if(newSize<=oldCapacity)return;
        //否則擴容1.5
        newSize=oldCapacity+(oldCapacity>>1);
        E[] newElements= (E[]) new Object[newSize];
        //資料遷移
        for(int i=0;i<size;i++)
            newElements[i]=elements[index(i)];
        elements=newElements;
        front=0;//指標歸零
        System.out.println(oldCapacity+"擴容為"+newSize);
    }

    /**
     * 簡化取模的索引對映
     * @param index
     * @return
     */
    private int index(int index){
        //避免取模運算(效率低下)
        index+=front;
        return index-(index>=elements.length?elements.length:0);
    }
    /**
     * 出隊
     * @return
     */
    public E poll(){
        E frontElement=elements[front];
        elements[front]=null;
        front=index(1);//更新指標到下一個元素
        size--;
        return frontElement;
    }

    /**
     * 獲取隊頭元素
     * @return
     */
    public E peek(){
        return elements[front];
    }

    @Override
    public String toString() {
        return "CircleQueue{" +
                "front=" + front +
                ", size=" + size +
                ", elements=" + Arrays.toString(elements) +
                '}';
    }
}

迴圈雙端佇列

迴圈雙端佇列概念

迴圈雙端佇列其實就是一個雙端佇列,即可以在隊頭和隊尾都進行入隊或者出隊操作的佇列。它與迴圈佇列相比,也只是增加了在隊尾進行出隊操作的介面offerRear和在隊頭進行入隊操作的pollFront,其次由於在隊頭入隊需要獲取到原始索引為-1的對映索引(即頭節點之前的節點,頭節點原始索引為0),還需要對提供索引對映的index(int index)方法進行修改。

具體實現

public class CircleDeque<E> {
    private int front;//指向頭節點
    private int size;
    private E[] elements;
    //預設佇列大小
    private static final int DEFAULT_CAPACITY=10;

    public  CircleDeque(){
        this(DEFAULT_CAPACITY);
    }

    public  CircleDeque(int capacity){
        capacity=capacity>DEFAULT_CAPACITY?capacity:DEFAULT_CAPACITY;
        elements= (E[]) new Object[capacity];
    }

    /**
     * 獲取佇列大小
     * @return
     */
    public int size(){
        return size;
    }

    /**
     * 判斷陣列是否為空
     * @return
     */
    public boolean isEmpty(){
        return size==0;
    }

    /**
     * 清空佇列
     */
    public void clear(){
        //清空所有引用,等待jvm進行垃圾回收
        for(int i=0;i<size;i++)
            elements[index(i)]=null;
        size=0;
        //還原指標
        front=0;
    }


    /**
     * 從隊尾入隊
     */
    // 0 0 1 2 3
    public void offerRear(E element){
        ensureCapacity(size+1);
        elements[index(size)]=element;//末尾入隊
        size++;
    }

    /**
     * 從隊頭入隊
     */
    public void offerFront(E element){
        ensureCapacity(size+1);
        int newFront=size==0?0:index(-1);//若放放入的是第一個元素,直接放在0處
        elements[front=newFront]=element;//隊頭入隊並跟新front
        size++;
    }

    private void ensureCapacity(int newSize){
        int oldCapacity=elements.length;
        //如果容量可以承受
        if(newSize<=oldCapacity)return;
        //否則擴容1.5
        newSize=oldCapacity+(oldCapacity>>1);
        E[] newElements= (E[]) new Object[newSize];
        //資料遷移
        for(int i=0;i<size;i++)
            newElements[i]=elements[index(i)];
        elements=newElements;
        front=0;//指標歸零
        System.out.println(oldCapacity+"擴容為"+newSize);
    }

    /**
     * 簡化取模的索引對映
     * @param index
     * @return
     */
    private int index(int index){
        //防止index為-1
        //避免取模操作
        index+=front;
        if(index<0)
            return index+elements.length;
        return index-(index>=elements.length?elements.length:0);
    }
    /**
     * 從隊頭出隊
     * @return
     */
    public E pollFront(){
        E frontElement=elements[front];
        elements[front]=null;
        front=index(1);//更新指標到下一個元素
        size--;
        return frontElement;
    }

    /**
     * 從隊尾出隊
     * @return
     */
    public E pollRear(){
        int rear=index(size-1);
        E frontElement=elements[rear];
        elements[rear]=null;//尾部出隊
        size--;
        return frontElement;
    }

    /**
     * 獲取隊頭元素
     * @return
     */
    public E peekFront(){
        return elements[front];
    }

    /**
     * 獲取隊尾元素
     * @return
     */
    public E peekRear(){
        return elements[index(size-1)];
    }

    @Override
    public String toString() {
        return "CircleDeque{" +
                "front=" + front +
                ", size=" + size +
                ", elements=" + Arrays.toString(elements) +
                '}';
    }
}