用數組實現隊列(順序隊列&循環隊列)


用數組實現隊列(順序隊列&循環隊列)

順序隊列

↘️

隊列(先進先出)


幾個問題:

  • 隊列方法:入隊、出隊
  • 隊列的存儲:即隊首隊尾兩個指針,
  • 擴容:如果隊列容量不夠了,應該擴容,如果隊尾沒有位置了,隊首有位置,應該把元素往前移

主要是上面三個問題,在代碼中都有體現,上面的擴容方法借鑒了ArrayList的擴容方法。

package com.helius.structure.queue;

import java.util.Arrays;

/**
 * 用數組實現一個隊列,即順序隊列
 */
public class ArrayQueue {
    // 存儲數據的數組
    private Object[] elements;
    //隊列大小
    private int size;
    // 默認隊列容量
    private int DEFAULT_CAPACITY = 10;
    // 隊列頭指針
    private int head;
    // 隊列尾指針
    private int tail;
    
    private int MAX_ARRAY_SIZE  = Integer.MAX_VALUE-8;

    /**
     * 默認構造函數 初始化大小為10的隊列
     */
    public ArrayQueue(){
        elements = new Object[DEFAULT_CAPACITY];
        initPointer(0,0);
    }

    /**
     * 通過傳入的容量大小創建隊列
     * @param capacity
     */
    public ArrayQueue(int capacity){
        elements = new Object[capacity];
        initPointer(0,0);
    }

    /**
     * 初始化隊列頭尾指針
     * @param head
     * @param tail
     */
    private void initPointer(int head,int tail){
        this.head = head;
        this.tail = tail;
    }

    /**
     * 元素入隊列
     * @param element
     * @return
     */
    public boolean enqueue(Object element){
        ensureCapacityHelper();
        elements[tail++] = element;//在尾指針處存入元素且尾指針后移
        size++;//隊列元素個數加1
        return true;
    }

    private void ensureCapacityHelper() {
        if(tail==elements.length){//尾指針已越過數組尾端
            //判斷隊列是否已滿 即判斷數組中是否還有可用存儲空間
            //if(size<elements.length){
            if(head==0){
                //擴容
                grow(elements.length);
            }else{
                //進行數據搬移操作 將數組中的數據依次向前挪動直至頂部
                for(int i= head;i<tail;i++){
                    elements[i-head]=elements[i];
                }
                //數據搬移完后重新初始化頭尾指針
                initPointer(0,tail-head);
            }
        }
    }
    /**
     * 擴容
     * @param oldCapacity 原始容量
     */
    private void grow(int oldCapacity) {
        int newCapacity = oldCapacity+(oldCapacity>>1);
        if(newCapacity-oldCapacity<0){
            newCapacity = DEFAULT_CAPACITY;
        }
        if(newCapacity-MAX_ARRAY_SIZE>0){
            newCapacity = hugeCapacity(newCapacity);
        }
        elements = Arrays.copyOf(elements,newCapacity);
    }
    private int hugeCapacity(int newCapacity) {
        return (newCapacity>MAX_ARRAY_SIZE)? Integer.MAX_VALUE:newCapacity;
    }

    /**
     * 出隊列
     * @return
     */
    public Object dequeue(){
        if(head==tail){
            return null;//隊列中沒有數據
        }
        Object obj=elements[head++];//取出隊列頭的元素且頭指針后移
        size--;//隊列中元素個數減1
        return obj;
    }

    /**
     * 獲取隊列元素個數
     * @return
     */
    public int getSize() {
        return size;
    }
}

測試用例

public class TestArrayQueue {

    public static void main(String[] args) {
        ArrayQueue queue = new ArrayQueue(4);
        //入隊列
        queue.enqueue("helius1");
        queue.enqueue("helius2");
        queue.enqueue("helius3");
        queue.enqueue("helius4");
        //此時入隊列應該走擴容的邏輯
        queue.enqueue("helius5");
        queue.enqueue("helius6");
        //出隊列
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        //此時入隊列應該走數據搬移邏輯
        queue.enqueue("helius7");
        //出隊列
        System.out.println(queue.dequeue());
        //入隊列
        queue.enqueue("helius8");
        //出隊列
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        //入隊列
        queue.enqueue("helius9");
        queue.enqueue("helius10");
        queue.enqueue("helius11");
        queue.enqueue("helius12");
        //出隊列
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
    }
}

結果:

helius1
helius2
helius3
helius4
helius5
helius6
helius7
helius8
null
helius9
helius10

循環隊列

用java實現循環隊列的方法:

  1. 增加一個屬性size用來記錄目前的元素個數。目的是當head=rear的時候,通過size=0還是size=數組長度,來區分隊列為空,或者隊列已滿。

  2. 數組中只存儲數組大小-1個元素,保證rear轉一圈之后不會和head相等,也就是隊列滿的時候,rear+1=head,中間剛好空一個元素。

    當rear=head的時候,一定是隊列空了。

隊列(Queue)兩端允許操作的類型不一樣:

可以進行刪除的一端稱為隊頭,這種操作也叫出隊dequeue;

可以進行插入的一端稱為隊尾,這種操作也叫入隊enqueue。

隊列的示意圖

img

實現隊列時,要注意的是假溢出現象,如上圖的最后一幅圖。

如圖所示的假溢出現象,順序隊列可以如此,循環隊列我們可以讓這個尾指針指向front前面的元素,這也正符合我們想要的循環隊列的定義。

img

解決辦法:使用鏈式存儲,這顯然可以。在順序存儲時,我們常見的解決辦法是把它首尾相接,構成循環隊列,這可以充分利用隊列的存儲空間。

循環隊列示意圖:

img

在上圖中,front指向隊列中第一個元素,rear指向隊列隊尾的下一個位置。

但依然存在一個問題:當front和rear指向同一個位置時,這代表的是隊空還是隊滿呢?大家可以想象下這種情景。

解決這種問題的常見做法是這樣的:

使用一標記,用以區分這種易混淆的情形。

犧牲一個元素空間。當front和rear相等時,為空;當rear的下一個位置是front時,為滿。

如下圖:

imgimg

下面我們給出循環隊列,並采用第二種方式,即犧牲一個元素空間來區分隊空和隊滿的代碼.

幾個重點:

1、front指向隊頭,rear指向隊尾的下一個位置。

2、隊為空的判斷:frontrear;隊為滿的判斷:(rear+1)%MAXSIZEfront。

上面說的rear即為代碼中的的tail

/**
 * 使用數組實現循環隊列
 * @author Helius
 */
public class CirculiQueue {
    //存儲隊列數據的數組
    private Object[] elements;
    //默認數組容量
    private int DEFAULT_CAPACITY=10;
    //隊列中元素個數
    private int size;
    // 隊列頭指針
    private int head;
    //隊列尾指針
    private int tail;

    /**
     * 默認構造函數
     */
    public CirculiQueue(){
        elements = new Object[DEFAULT_CAPACITY];
    }

    /**
     * 通過傳入的容量參數構造隊列
     * @param capacity
     */
    public CirculiQueue(int capacity){
        elements = new Object[capacity];
    }

    /**
     * 元素入隊列
     * @param element
     * @return
     */
    public boolean enqueue(Object element){
        //判斷隊列是否已滿
        if(head == (tail+1)%elements.length){
            //隊列已滿
            return false;
        }
        //將元素存入tail位置上
        elements[tail]=element;
        //尾指針后移
        /*tail++;
        if(tail==elements.length){
            tail = 0;
        }*/
        tail = (tail+1)%elements.length;
        size++;
        return true;
    }

    /**
     * 元素出隊列
     * @return
     */
    public Object dequeue(){
        //判斷隊列是否為空
        if(head==tail){
            return null;
        }
        //獲取head位置上的元素
        Object element = elements[head];
        //頭指針后移
        /*head++;
        if(head==elements.length){
            head = 0;
        }*/
        head = (head+1)%elements.length;
        size--;
        return element;
    }

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

這里也添加了size屬性,可以稍做修改,來實現第一種方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM