數(shù)據(jù)結構之隊列

一:概述

????隊列同樣是一種特殊的線性表何什,其插入和刪除的操作分別在表的兩端進行譬巫,隊列的特點就是先進先出(First In First Out)咖楣。我們把向隊列中插入元素的過程稱為入隊(Enqueue),刪除元素的過程稱為出隊(Dequeue)并把允許入隊的一端稱為隊尾芦昔,允許出的的一端稱為隊頭诱贿,沒有任何元素的隊列則稱為空隊。如果把棧比作一個木桶的話咕缎,那么隊列就是水管珠十;木桶只有一端是通的,另一端封閉凭豪;而水管兩端都通焙蹭,一端進數(shù)據(jù),一端出數(shù)據(jù):
隊列.jpg

????關于隊列的操作嫂伞,我們這里主要實現(xiàn)入隊孔厉,出隊,判斷空隊列和清空隊列等操作帖努,聲明隊列接口Queue(隊列抽象數(shù)據(jù)類型)如下:
public interface Queue<T> {
    //隊列長度
    int size();
    
    //隊列是否為空
    boolean isEmpty();
    
    //入隊
    boolean enqueue(T data);
    
    //返回隊列頭
    T peek();
    
    //出隊
    T dequeue();
}

二:順序循環(huán)隊列

????關于順序隊列(底層都是利用數(shù)組作為容器)的實現(xiàn)撰豺,我們將采用順序循環(huán)隊列的結構來實現(xiàn),在給出實現(xiàn)方案前先來分析一下為什么不直接使用順序表作為底層容器來實現(xiàn)拼余。實際上采用順序表實現(xiàn)隊列時污桦,入隊操作直接執(zhí)行順序表尾部插入操作,其時間復雜度為O(1)匙监,出隊操作直接執(zhí)行順序表頭部刪除操作凡橱,其時間復雜度為O(n)小作,主要用于移動元素,效率低梭纹,既然如此躲惰,我們就把出隊的時間復雜度降為O(1)即可,為此在順序表中添加一個頭指向下標front和尾指向下標变抽,出隊和入隊時只要改變front础拨、rear的下標指向取值即可,此時無需移動元素绍载,因此出隊的時間復雜度也就變?yōu)镺(1)诡宗。其過程如下圖所示:
double.009.jpeg
????從上圖的演示可以看出,隊列的出隊和入隊击儡,都會修改front和rear塔沃,對于順序隊列來說,因為rear一直增大阳谍,當增大到一定程度時蛀柴,就會出現(xiàn)隊列貌似滿了的現(xiàn)象,這時候無法添加元素矫夯,就算隊列里面有空間也不行鸽疾,這種情況叫做假溢出,假溢出的缺點是很明顯的训貌。順序循環(huán)隊列就可以 解決假溢出的問題制肮,如上圖中,碰到80咋整递沪?當然是讓80回頭是岸豺鼻,放到隊里前面去:
double.011.jpeg

????所以,對于循環(huán)隊列來說款慨,front不一定小于rear儒飒,這樣設計的好處很明顯,能夠循環(huán)利用存儲單元檩奠。
????上面的方法解決了空間浪費的問題约素,但是考慮的還不夠全面;80入隊后笆凌,這根"水管"的頭和尾都被堵住了,那么如果還想入隊怎么辦士葫?辦法總是有的乞而,這個時候就要考慮擴容了;那么什么時候擴容呢慢显?真的要等到所有位置都填充了數(shù)據(jù)才開始擴容嗎爪模?并不是的欠啤,如上圖所示,隊列中還有個空位屋灌,但是明顯無法入隊了洁段;這時候如果可能的話,就要擴容了共郭;另外祠丝,如果真的還可以入隊,那么front和rear就勢必相等了除嘹;需要注意的是写半,空隊列的front和rear也是相等的;當然尉咕,我們通過設置標記位來判斷到底是空隊列還是滿隊列叠蝇,不過一個更好的方案是預留一個位置,當隊列還剩一個位置沒有數(shù)據(jù)填充時年缎,就認為該隊列是滿的悔捶,例如上圖的隊列,雖然1號位置是空的单芜,但是我們還是認為該隊列已經(jīng)滿了蜕该,不宜再入隊了。
????綜上可知缓溅,隊列的front和rear的取值范圍應該是0 - size-1蛇损;需要注意的是,隊里中的元素坛怪,除了隊頭和隊尾外淤齐,其他元素之間的位置是相連的,在上圖中袜匿,不存在2和4號位有數(shù)據(jù)更啄,而3號位卻空出來的場景,所以有如下的約定:
????1.front是隊頭元素的下標居灯;rear指向下一個入隊的元素存儲的位置的下標
????2.當front == rear時祭务,此隊列是空隊列
????3.入隊改變rear的指向,出隊改變front的指向怪嫌,size是指隊列的長度(不是容量)
????4.約定滿隊列的條件是front == (rear + 1) % size义锥;此時,隊列中還有一個空的位置岩灭,主要是方便區(qū)分空隊列和滿隊列拌倍;如上圖中,rear == 1,front == 2柱恤,(1 + 1) % 5 == 2数初。
????下面就通過數(shù)組來實現(xiàn)一個簡單的隊列:

public class SeqQueue<T> implements Queue<T> {
    //默認容量,必要的時候可以擴容
    private static final int DEFAULT_SIZE = 10;
  
    //存儲數(shù)據(jù)的數(shù)組
    private T elementData[];

    //隊頭和隊尾指針
    private int front,rear;

    //隊列長度
    private int size;

    @SuppressWarnings("unchecked")
    public SeqQueue() {
        elementData = (T[]) new Object[DEFAULT_SIZE];
        front = rear = 0;
    }

    //隊列長度梗顺,直接返回size好了
    @Override
    public int size() {
        return size;
    }

    //按照上面的約定泡孩,front == rear時為空隊列
    @Override
    public boolean isEmpty() {
        return front == rear;
    }

    //入隊
    @Override
    public boolean enqueue(T data) {
        //如果隊列滿了,那么擴容寺谤,隊列滿的條件上面解釋過
        if(this.front == (this.rear + 1) % this.elementData.length) {
            ensureCapacity(elementData.length*2+1);
        }
    
        //將數(shù)據(jù)存入rear指向的位置    
        elementData[this.rear] = data;

        //更新rear指向下一個空元素的位置仑鸥,當rear指向最后一個位置時,
        //(this.rear + 1) % elementData.length就等于0矗漾,也就是
        //說锈候,此時rear指向了隊頭,只有這樣做敞贡,才能做到存儲單元的復用
        this.rear = (this.rear + 1) % elementData.length;

        //長度+1泵琳,沒毛病
        size ++;
        return true;
    }

    //獲取隊頭元素,直接返回數(shù)組指定的數(shù)據(jù)即可誊役,簡單
    @Override
    public T peek() {
        return elementData[front];
    }

    //出隊
    @Override
    public T dequeue() {
        //獲取頭元素获列,最終返回的也是他
        T tmp = this.elementData[front];

        //將front指向下一個元素,需要注意的是蛔垢,原來的頭元素并沒有置空
        //因為front已經(jīng)指向下一個元素击孩,原來的頭元素的位置雖然還有數(shù)據(jù)
        //但是他已經(jīng)沒有意義了,下次入隊的時候鹏漆,就會把這個元素的數(shù)據(jù)
        //更新巩梢,在這里置空操作是非必要的,這跟上圖的操作是不太一樣的
        this.front = (this.front + 1) % this.elementData.length;

        //長度-1艺玲,沒毛病
        size --;
        return tmp;
    }

    //擴容括蝠,思路很簡單,就是重新創(chuàng)建一個數(shù)組饭聚,
    //然后將原來的數(shù)據(jù)按照秩序拷到新的數(shù)組
    @SuppressWarnings("unchecked")
    private void ensureCapacity(int capacity) {
        //臨時存儲老的數(shù)據(jù)
        T[] old = elementData;
      
        //創(chuàng)建新的數(shù)組
        elementData = (T[]) new Object[capacity];
        int j = 0;
      
        //這里的循環(huán)條件有點不一樣忌警,首先肯定是從隊頭開始拷貝,
        //這個隊頭的索引不一定是0秒梳,只能取this.front法绵;循環(huán)結
        //束的條件是i != this.rear,也就是從隊頭開始拷酪碘,一
        //到隊尾結束朋譬;自增的條件是i = (i + 1) % old.length,
        //這里千萬不能用i++兴垦,因為有時候rear小于front此熬,如果i++
        //那么永遠也到不了隊尾,同時還會拋出數(shù)組越界的異常,而
        //(i + 1) % old.length能夠保證當rear小于front的時候犀忱,
        //i能及時,正確的回到正確的位置扶关,也就是上圖中的"水管"的左邊
        //這樣才能保證新隊列的順序能和老隊列的順序保持一致阴汇,很重要
        for(int i = this.front ; i != this.rear ; i = (i + 1) % old.length) {
            elementData[j++] = old[i];
        }
        this.front = 0 ;
        this.rear = j;
    }

    //清空隊列
    public void clear(){
        //循環(huán)的條件和上面擴容的條件一樣
        for(int i = this.front ; i != this.rear ; i = (i + 1) % elementData.length){
            //把每個元素置空
            elementData[i] = null;
        }
        this.front = this.rear = -1;
        size = 0;
    }
}

????上面就是順序隊列的實現(xiàn)方式,稍稍有點繞节槐,總體還是比較簡單的搀庶。

三:鏈式隊列

????分析完順序隊列,我們接著看看鏈式隊列的設計與實現(xiàn)铜异,對于鏈式隊列哥倔,將使用帶頭指針front和尾指針rear的單鏈表實現(xiàn),front直接指向隊頭的第一個元素揍庄,rear指向隊尾的最后一個元素咆蒿,其結構如下:
1532872643491.jpg
????可以看到,跟順序循環(huán)隊列比起來蚂子,只是將底層的數(shù)組換成了單向鏈表沃测,國際慣例,畫圖來表示出隊和入隊的過程:
隊列.010.jpeg

????從圖中可以看出食茎,鏈式隊列的入隊和出隊蒂破,只需要改變尾節(jié)點和頭結點的指向即可,而且還不存在順序鏈表中的假溢出問題别渔,下面代碼實現(xiàn)他:

public class LinkQueue<T> implements Queue<T> {
    
    //對于鏈式隊列來說附迷,頭尾節(jié)點是必不可少的
    private Node<T> front,rear;
    
    //鏈表的長度
    private int size;
    
    //鏈式隊列沒有容量的限制,但是最好是給它設置一個上線
    private  static final int MAX_SIZE = 128;
    
    public LinkQueue() {
        this.front = this.rear = null;
    }

    //簡單哎媚,不解釋
    @Override
    public int size() {
        return size;
    }

    //頭尾節(jié)點均為空代表空隊列
    @Override
    public boolean isEmpty() {
        return this.front == null && this.rear == null;
    }

    @Override
    public boolean add(T data) {
        if(size >= MAX_SIZE) {
            return false;
        }
        Node<T> node = new Node<T>(data);
        //空隊列插入
        if(this.front == null) {
            this.front = node;
        //非空隊列插入
        }else {
            this.rear.next = node;
        }
        //新插入的節(jié)點就是尾節(jié)點
        this.rear = node;
        size ++;
        return true;
    }

    //返回頭節(jié)點的數(shù)據(jù)喇伯,簡單
    @Override
    public T peek() {
        return this.front == null ? null : this.front.data;
    }

    @Override
    public T poll() {
        //空隊列返回空
        if(isEmpty()) {
            return null;
        //非空隊列返回頭節(jié)點,然后將頭結點指向他的后繼節(jié)點
        }else {
            T tmp = this.front.data;
            this.front = this.front.next;
            //如果隊列只有一個節(jié)點抄伍,那么出隊后就成了空隊列艘刚,所以隊尾也要置空
            if(this.front == null) {
                this.rear = null;
            }
            size --;
            return tmp;
        }
    }
    
    //清除隊列
    public void clear() {
        //頭尾節(jié)點都不存在了,鏈表也就不存在了截珍,這樣隊列就沒了
        this.front = this.rear = null;
        this.size = 0;
    }
    
    @SuppressWarnings("hiding")
    class Node<T> {
        public T data;
        public Node<T> next;

        public Node() {

        }

        public Node(T data) {
            this.data = data;
        }

        public Node(T data, Node<T> next) {
            this.data = data;
            this.next = next;
        }
    }
}

????以上就是兩種隊列的設計和實現(xiàn)攀甚,比較簡單;相對來說岗喉,鏈式隊列更簡單秋度,出隊和入隊比順序循環(huán)隊列簡潔,而且沒有假溢出的問題钱床。
摘自:https://blog.csdn.net/javazejian/article/details/53375004#%E9%98%9F%E5%88%97%E7%9A%84%E6%8A%BD%E8%B1%A1%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荚斯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌事期,老刑警劉巖滥壕,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異兽泣,居然都是意外死亡绎橘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門唠倦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來称鳞,“玉大人,你說我怎么就攤上這事稠鼻「灾梗” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵候齿,是天一觀的道長熙暴。 經(jīng)常有香客問我,道長毛肋,這世上最難降的妖魔是什么怨咪? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮润匙,結果婚禮上诗眨,老公的妹妹穿的比我還像新娘。我一直安慰自己孕讳,他們只是感情好匠楚,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著厂财,像睡著了一般芋簿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上璃饱,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天与斤,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛霍弹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播食寡,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼廓潜!你這毒婦竟也來了抵皱?” 一聲冷哼從身側響起善榛,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呻畸,沒想到半個月后移盆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡伤为,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年味滞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钮呀。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖昨凡,靈堂內(nèi)的尸體忽然破棺而出爽醋,到底是詐尸還是另有隱情,我是刑警寧澤便脊,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布蚂四,位于F島的核電站,受9級特大地震影響哪痰,放射性物質(zhì)發(fā)生泄漏遂赠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一晌杰、第九天 我趴在偏房一處隱蔽的房頂上張望跷睦。 院中可真熱鬧,春花似錦肋演、人聲如沸抑诸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜕乡。三九已至,卻和暖如春梗夸,著一層夾襖步出監(jiān)牢的瞬間层玲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工反症, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辛块,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓惰帽,卻偏偏與公主長得像憨降,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子该酗,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

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