數(shù)據(jù)結(jié)構(gòu)與算法 —— 03 隊(duì)

3.隊(duì)列(Queue) ——————本質(zhì)為:"線性表"

隊(duì)列是一種運(yùn)算受限制的線性表,元素的插入(入隊(duì))在表的一端(表尾, rear)進(jìn)行,刪除(出對)則在另一端(表頭, front)進(jìn)行。

允許插入的一端稱為表尾(rear),允許刪除的一端稱為表頭(front)

隊(duì)列的主要操作:

① 初始化
② 入隊(duì):插入
③ 出隊(duì):刪除
④ 獲取隊(duì)頭 —— 不刪除元素
⑤ 求長度:隊(duì)列元素個數(shù) 
⑥ 判空
⑦ 正序遍歷
⑧ 銷毀
表示形式:

㈠邏輯結(jié)構(gòu):
q = (a1, a2, ... , an), a1為隊(duì)頭元素,an為隊(duì)尾元素

     ┌───┬───┬───┬───┬───┐   

出隊(duì)<—— │a1 │a2 │a3 │...│an │ <——入隊(duì)
└───┴───┴───┴───┴───┘
↑ ↑
隊(duì)頭 隊(duì)尾

特點(diǎn):'先進(jìn)先出(FIFO, first in first out)',因此又被稱之先進(jìn)先出的線性表

㈡'物理存儲結(jié)構(gòu)'

(1) 順序存儲結(jié)構(gòu):順序隊(duì)列
其實(shí)質(zhì)為:線性表的順序表

順序隊(duì)列用一維數(shù)組實(shí)現(xiàn),還需設(shè)置兩個指針 front 和 rear 分別指示隊(duì)列的隊(duì)頭元素和隊(duì)尾元素的'下一個位置'。

注意:其實(shí)這里,將隊(duì)頭指針front指向隊(duì)列元素的前一個空的位置處,而rear指針指向隊(duì)列最后一個元素的位置處,也是可行的。

注意:
1.這種單向的順序隊(duì)列極易造成假溢出(即隊(duì)列中明明還有存儲單元,就是不能插入新的元素)
解決辦法:
(1) 每次出隊(duì)一個元素后,將整個隊(duì)列元素均向隊(duì)頭方向移動一個單元,即始終保證整個隊(duì)的隊(duì)頭指示器始終在數(shù)組的第一個存儲單元處。時間復(fù)雜度:O(n)
(2) 將順序隊(duì)列的存儲結(jié)構(gòu)改造成頭尾相連(只是表現(xiàn)在邏輯上的)的圓環(huán),當(dāng)隊(duì)尾指示器rear到達(dá)數(shù)組的上限(數(shù)組最大下標(biāo)處)時, 如果還有數(shù)據(jù)元素需要入隊(duì)且數(shù)組的第 0 個存儲單元空閑時,就可以讓rear指向數(shù)組的0存儲位置。同理,對頭front指示器達(dá)到數(shù)組的上限(數(shù)組最大下標(biāo)處)時,若果還有元素要出隊(duì)時,就將front指向數(shù)組的0端。這樣,就可以將隊(duì)列中空閑的空間利用上。

方法分析:第一種解決方法會造成系統(tǒng)額外的開銷,不是最佳解決辦法。故采用第二種方法。

  1. 在循環(huán)隊(duì)列中,判斷隊(duì)列是空還是滿是個需要重點(diǎn)考慮的問題。單純的依靠 front==rear并不能判斷隊(duì)列空間是空還滿(因?yàn)榭栈驖M時,均有這個關(guān)系)。
    解決辦法:
    (1)設(shè)定一個輔助標(biāo)識位:flag,初始為0,當(dāng)入隊(duì)一個元素就加1,出隊(duì)一個元素就減1。最后結(jié)合flag是否為大于零的數(shù)和front==rear來判斷當(dāng)前循環(huán)隊(duì)列是滿的還是空
    (2)第二種方式就是,在循環(huán)隊(duì)列中少用一個存儲單元。因此,rear和front只相差一個位置。但是請注意:由于是循環(huán)結(jié)構(gòu),所以這個差1,有可能是相差整整一圈,因此,隊(duì)滿的條件:(rear + 1) % queueArray.length == front

方法分析:第一種解決辦法要多設(shè)定一個參數(shù),還要一直對這個參數(shù)執(zhí)行運(yùn)算,這樣會增加一部分系統(tǒng)開銷。因此,采用第二中解決辦法。

順序循環(huán)隊(duì)列為"空"的條件:front == rear == 0
為"滿"的條件:(rear + 1) % queueArray.length == front
隊(duì)列的長度:(rear - front + queueArray.length) % queueArray.length

注意:取模的目的是為了整合rear和front大小為一個問題

'代碼描述'(順序循環(huán)結(jié)構(gòu)隊(duì)列):

public class sequenceQueue<T> {
    private final int maxSize = 10; //默認(rèn)是隊(duì)列容量
    private T queueArray[]; //實(shí)現(xiàn)隊(duì)列的數(shù)組
    private int front; //隊(duì)頭指示器
    private rear; //隊(duì)尾指示器,指向隊(duì)尾元素的下一個位置(始終保持所指的位置是空內(nèi)容,即未被利用的那個存儲單元)
    /**
    *   順序隊(duì)列初始化
    */
    //采用默認(rèn)容量初始化順序隊(duì)列
    public sequenceQueue() {
        front  = rear = 0;
        queueArray = (T[])new Object[maxSize];
    }
    //采用指定容量初始化順序隊(duì)列
    public sequenceQueue(int n) {
        front  = rear = 0;
        queueArray = (T[])new Object[n];
    }
    /**
    * 入隊(duì)操作:插入
    */
    public void enQueue(T obj) {
        //隊(duì)列是否已滿,若滿了則需要擴(kuò)容
        if ((rear + 1) % queueArray.length == front) {
            //擴(kuò)容
            T[] p = (T[])new Object[queueArray.length * 2];
            //復(fù)制原數(shù)組中的數(shù)據(jù)至新的數(shù)組中
            //表明rear指示器現(xiàn)在數(shù)組的末尾處,front指示器在數(shù)組的0下標(biāo)處
            if (rear == ((T[])queueArray).length - 1) {
                for (int i = 1; i <= rear; i++) {
                    p[i] = queueArray[i];
                }
            }else {
                /**
                * 表明rear指示器在數(shù)組的其他位置處,則將隊(duì)列分為兩部分:
                *   (1) front位置到數(shù)組末尾
                *   (2) 0存儲單元到rear指針器處
                *   因此,得分段復(fù)制
                */
                int i, j = 1;
                // 復(fù)制front到末尾這段的數(shù)據(jù)
                for (i = front + 1; i < queueArray.length; i++, j++) {
                    p[j] = queueArray[i];
                }
                // 復(fù)制從0存儲單元到rear指示器處的數(shù)據(jù)
                // 注意:這里將queueArray[0]位置的數(shù)據(jù)(為0)也復(fù)制到新的數(shù)組中,表明
                //     新數(shù)組的扔是以0存儲單元作為"判滿"的輔助單位
                for (i = 0; i < rear; i++, j++) {
                    p[j] = queueArray[i];
                }
                front = 0; // 將front調(diào)整到數(shù)組頭部位置
                rear = queueArray.length - 1; //將rear調(diào)整到數(shù)組中含有數(shù)據(jù)的尾部的位置
            }
            queueArray = p;
        }
        /**
        * 執(zhí)行插入數(shù)據(jù)的過程,
        *   若將rear指向隊(duì)尾元素的下一個位置時,front在隊(duì)頭元素處
        *   rear = (rear + 1) % queueArray.length;                  
        *   queueArray[rear] = obj; //因?yàn)閞ear指針在隊(duì)尾元素的下一個位置處,因此先放元素,再移動指針
        */
        //這個表示rear指向隊(duì)尾的元素,注意和上面這段代碼的區(qū)別
        //取模是為了防止rear指針越界,
        rear = (rear + 1) % queueArray.length;  //因?yàn)閞ear指針在隊(duì)尾元素處,因此先移動rear指針,再放元素               
        queueArray[rear] = obj;                 
    }

    //出隊(duì)操作:刪除
    public T deQueue() {
        //判空
        if (isEmpty()) {
            System.out.println("順序隊(duì)列為空,不能進(jìn)行出隊(duì)操作");
            return null;
        }
        /**
        * 進(jìn)行出隊(duì)的操作過程
        * 由于:front在隊(duì)頭元素的前一個位置處,所以,插入數(shù)據(jù)時,要先移動指針,后放數(shù)據(jù)
        */
        front = (front + 1) % queueArray.length; //front指針取模也是為了front防止越界
        return queueArray[front];
    }

    //獲取操作:返回隊(duì)頭的元素,不刪除該元素
    public T getTop() {
        //判空
        if (isEmpty()) {
            System.out.println("順序隊(duì)列為空,不能進(jìn)行獲取隊(duì)頭元素的操作");
            return null;
        }
        return queueArray[(front + 1) % queueArray.length];
    }

    //求長度
    public int size() {
        return (rear - front + queueArray.length) % queueArray.length;
    }

    //判空
    public boolean isEmpty() {
        return front == rear;
    }

    //正向遍歷
    public void nextOrder() {
        System.out.print("[");
        int j = front;
        for (int i = 1; i <= size(); i++) {
             j = (j + 1) % queueArray.length;
             if (j == rear) {
                 System.out.print(queueArray[j]);
             }else {
                System.out.print(queueArray[j] + ", ");
             }                   
        }
        System.out.println("]");
    }

    //銷毀
    public void clear() {
        front = rear = 0;
    }
}
(2) 鏈?zhǔn)酱鎯Y(jié)構(gòu):鏈隊(duì)列

用鏈表實(shí)現(xiàn)的隊(duì)列稱為鏈隊(duì)列,
其實(shí)質(zhì)是:線性表的單鏈表(只是在'頭刪尾插'而已)

注意:
1.鏈隊(duì)列的長度是不固定的,因此不存在假溢出的問題,故用一般的(非循環(huán))隊(duì)列即可。
2.同線性表的單鏈表一樣,為了操作方便,在鏈隊(duì)列中添加一個頭結(jié)點(diǎn),并令頭指針(front)指向頭結(jié)點(diǎn)。(在鏈棧中沒有使用頭結(jié)點(diǎn))

鏈隊(duì)列為'空'的條件(front和rear均指向頭結(jié)點(diǎn)):front.next == null

'代碼描述'(鏈隊(duì)列):
public class LinkQueue<T> {

private Node<T> front, rear; //這里的Node和前面的鏈表的Node是一樣的
private int length; //記錄隊(duì)列元素的個數(shù)

//1.初始化鏈隊(duì)列
public LinkQueue() {

    length = 0;
    front = rear = new Node<T>(null); //初始時,front和rear均指向表頭結(jié)點(diǎn)
}

//2.入隊(duì):插入(不用考慮隊(duì)滿的情況,也就沒有擴(kuò)容的現(xiàn)象)
public void enQueue(T obj) {
    rear.next = new Node<T>(obj, null);
    rear = rear.next; //將rear指針移動到新的隊(duì)尾結(jié)點(diǎn)處。
    length ++; //增加一個元素個數(shù)
}

//3.出隊(duì):刪除(刪除的是頭結(jié)點(diǎn)的后繼結(jié)點(diǎn),即第一個結(jié)點(diǎn))
public T deQueue() {
    //判空
    if (isEmpty()) {
        System.out.println("鏈隊(duì)列為空,不可以進(jìn)行出棧操作");
        return null;
    }
    //出棧過程
    Node<T> p = front.next; //輔助結(jié)點(diǎn)
    front.next= p.next; //由于表頭(頭結(jié)點(diǎn))的"位置"固定不動,因此,只能變動第一個結(jié)點(diǎn)。
    length --;
    /**
    * 這部分是不是多余呢?不是多余的,當(dāng)隊(duì)列中只有一個結(jié)點(diǎn)時,出隊(duì)后,此時隊(duì)列為空了。
    * 就要將rear指針指向頭結(jié)點(diǎn),即front的位置處
    */
    if (front.next == null) {
        front = rear;
    }
    return p.data;
}

//4.獲?。悍祷仃?duì)頭元素
public T getHead() {
    //判空
    if (isEmpty()) {
        System.out.println("鏈隊(duì)列為空,不可以進(jìn)行獲取隊(duì)頭元素的操作");
        return null;
    }
    //獲取隊(duì)頭元素的過程
    return front.next.data;
}

//5.求長度
public int size() {

    return length;
}

//6. 判空
public boolean isEmpty() {

    return front.next == null;
}

//7.正向遍歷
public void nextOrder() {

    System.out.print("[");
    Node<T> p = front.next;
    while (p != null) {
        if (p.next == null) {
            System.out.print(p.data);
        }else {
            System.out.print(p.data + ", ");
        }   
        p = p.next;
    }
    System.out.println("]");
}

//8.銷毀
public void clear() {
    length = 0;
    front.next = rear.next = null;
}

}

循環(huán)隊(duì)列和鏈隊(duì)列的比較:
它們的基本操作都是:O(1)
循環(huán)隊(duì)列長:度固定,所以會存在浪費(fèi)空間的情況。但是,在頻繁出入隊(duì)的時候,不需要申
            請、釋放結(jié)點(diǎn),因此,空間開銷少點(diǎn)
鏈隊(duì)列:長度靈活可變,但會頻繁的申請、釋放結(jié)點(diǎn),造成一頂?shù)南到y(tǒng)開銷

總結(jié):長度確定時,用循環(huán)鏈表
      長度不可測時,選擇鏈隊(duì)列

典型應(yīng)用:鍵盤輸入各種字符的過程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內(nèi)容