python-031-實(shí)現(xiàn)隊(duì)列結(jié)構(gòu)-列表實(shí)現(xiàn)-動(dòng)態(tài)改變大小

隊(duì)列與前天的棧是表親關(guān)系拐辽,也是一種基本的數(shù)據(jù)結(jié)構(gòu)减噪,與棧不同的是赞庶,它是遵循先進(jìn)先出的原則(FIFO)伏蚊,即只能在隊(duì)首刪除元素,隊(duì)尾插入元素膛虫。

通常我們把插入元素的一段稱為對(duì)尾,刪除元素的一端稱為隊(duì)頭钓猬。

這個(gè)很容易理解稍刀,想象肯德基排隊(duì)點(diǎn)餐:你從隊(duì)尾開始排隊(duì),正在點(diǎn)餐的人在隊(duì)頭敞曹。

隊(duì)列在很多場(chǎng)景都有應(yīng)用账月,且廣泛的應(yīng)用于很多計(jì)算機(jī)設(shè)備中,比如網(wǎng)絡(luò)打印機(jī)澳迫,web服務(wù)器等局齿。

同樣的,先來分析下隊(duì)列應(yīng)該有哪些方法橄登,對(duì)于隊(duì)列Q抓歼,抽象數(shù)據(jù)類型ADT應(yīng)該包含兩種主要的方法出隊(duì)列、進(jìn)隊(duì)列:

  • Q.enqueue(e) 入隊(duì)列拢锹,在隊(duì)尾插入一個(gè)元素谣妻。
  • Q.dequeue 出隊(duì)列,在隊(duì)列中有元素的情況下卒稳,將隊(duì)頭元素返回蹋半,并從隊(duì)列中移除,在隊(duì)列為空是充坑,返回錯(cuò)誤减江。
    對(duì)于我們使用來說他應(yīng)該還包括以下的方法:
  • Q.frist() 返回隊(duì)列的隊(duì)頭元素染突,但是不移除隊(duì)頭元素,這有點(diǎn)像棧的pop()
  • Q.is_empty() 如果隊(duì)列為空則返回True
  • len(Q) 返回隊(duì)列中的元素個(gè)數(shù)
    為了方便我們學(xué)習(xí)這種數(shù)據(jù)結(jié)構(gòu)辈灼,我還實(shí)現(xiàn)了str()方法份企,用來打印隊(duì)列。

同樣的定義了茵休,隊(duì)列為空的一個(gè)異常類薪棒,繼承自Exception。

我們的隊(duì)列名稱叫做ArrayQueue榕莺,因?yàn)槲覀兊年?duì)列使用列表實(shí)現(xiàn)的俐芯。

我們用數(shù)組實(shí)現(xiàn)隊(duì)列有一個(gè)很不方便的地方就是出隊(duì)列的時(shí)候,我們?cè)谠O(shè)計(jì)時(shí)钉鸯,是將列表的前面作為隊(duì)頭的吧史,所以我們將元素出隊(duì)列的時(shí)候,很簡(jiǎn)單的實(shí)現(xiàn)是pop(0),但是我在前面學(xué)習(xí)的過程中了解到唠雕,pop()不帶參數(shù)的話時(shí)間復(fù)雜度為O(1)贸营,但是如果有參數(shù),傳入索引岩睁,這會(huì)導(dǎo)致時(shí)間復(fù)雜度提高钞脂,變?yōu)镺(n-k+1),k是元素的索引捕儒,因?yàn)楸校绻覀儚棾隽饲懊娴脑兀斜砭托枰獙⑦@個(gè)洞補(bǔ)上刘莹,來保證整個(gè)列表的索引的正確性阎毅,而在我們實(shí)現(xiàn)隊(duì)列出隊(duì)列的情況下,如果用pop(0)則會(huì)大大增加時(shí)間復(fù)雜度点弯,達(dá)到O(n)扇调。越簡(jiǎn)單的實(shí)現(xiàn),則效率越低抢肛。

我們用新的方法來避免調(diào)用pop(0)狼钮。用一個(gè)指代為空的指針替代這個(gè)出隊(duì)列的元素,即用None來代替這個(gè)元素的位置捡絮,然后用一個(gè)成員變量self._front來標(biāo)識(shí)隊(duì)頭的位置燃领,這樣的話對(duì)于出隊(duì)操作就變成了O(1),但是這樣子出隊(duì)列幾次后就會(huì)變成這樣:

隊(duì)頭     None    |    None    |    None    |    1    |    2    |    ········    |    6    |   隊(duì)尾 

因?yàn)榱斜碓诘讓邮怯脭?shù)組實(shí)現(xiàn)的锦援,隨著進(jìn)隊(duì)出隊(duì)的操作猛蔽,這個(gè)列表將變的越來越大,最終的大小是這個(gè)隊(duì)列自創(chuàng)建以來入隊(duì)元素的總數(shù)量。這是很可怕的曼库。
這種設(shè)計(jì)會(huì)對(duì)那種所需隊(duì)列大小相對(duì)穩(wěn)定区岗,但經(jīng)常入隊(duì)出隊(duì)的應(yīng)用非常不友好,會(huì)造成很大的內(nèi)存浪費(fèi)毁枯。
于是我們就考慮利用前面的這些None慈缔,——循環(huán)的使用列表,當(dāng)列表的隨后一個(gè)位置被使用之后种玛,將下一個(gè)保存的位置變?yōu)榱斜淼那懊妗?/p>

這樣就會(huì)出一個(gè)問題藐鹤,就是前面的對(duì)頭位置怎么確定,我們用這個(gè)公式 (self._front + 1) % N赂韵,N是當(dāng)前底層數(shù)組的長(zhǎng)度娱节。比如,我們的隊(duì)列底層數(shù)組的長(zhǎng)度為10:

None    |    None    |    None    |    1(self._front)    |    2    |    3    |    6    |    7    |    8    |    9    |

現(xiàn)在我們要?jiǎng)h除隊(duì)頭元素祭示,self._front = (self._front + 1) % N => (self._front = 3 + 1) % 10 = 4肄满,這樣就得到了我們的新的隊(duì)頭。

如果要新插入一個(gè)元素 :

1    |    None    |    None    |    1(self._front)    |    2    |    3    |    6    |    7    |    8    |    9    |

就要用(self._front + self._size) % N來確定新加入元素的索引质涛。
這你自己算吧稠歉。

接下來就是動(dòng)態(tài)數(shù)組的問題了,當(dāng)所有的空間都滿了汇陆,我們需要擴(kuò)展空間怒炸,就需要將底層數(shù)組的大小變?yōu)樵瓉淼膬杀叮瑏斫档拖乱淮胃淖償?shù)組大小的概率毡代。

如果隊(duì)列中的元素個(gè)數(shù)小于等于空間的四分之一阅羹,就把底層數(shù)組的大小減小一半。

在改變數(shù)組大小的時(shí)候月趟,我們其實(shí)是用了一個(gè)新的數(shù)組灯蝴,將舊的數(shù)組值復(fù)制到新的數(shù)組中恢口,但是這不是單純的復(fù)制孝宗,因?yàn)槲覀冎霸谟?jì)算self._front時(shí)用到了當(dāng)前數(shù)組的大小,如果直接復(fù)制們就會(huì)出大問題耕肩,所以我們需要將原來的隊(duì)頭放到新數(shù)組的第一個(gè)因妇。

放出代碼:

class Empty(Exception):
    pass


class ArrayQueue:
    DEFAULT_CAPACITY = 10

    def __init__(self):
        self._data = [None] * ArrayQueue.DEFAULT_CAPACITY
        self._size = 0
        self._front = 0

    def __len__(self):
        return self._size

    def is_empty(self):
        return self._size == 0

    def first(self):
        if self.is_empty():
            raise Empty('Queue is empty!')
        return self._data[self._front]

    def enqueue(self, e):
        if self._size == len(self._data):
            self._resize(2 * len(self._data))  # 當(dāng)隊(duì)列滿了,就把隊(duì)列容量擴(kuò)大一倍
        avail = (self._front + self._size) % len(self._data)
        self._data[avail] = e
        self._size += 1

    def dequeue(self):
        if self.is_empty():
            raise Empty('Queue is empty!')
        answer = self._data[self._front]
        self._data[self._front] = None
        self._front = (self._front + 1) % len(self._data)
        self._size -= 1
        if 0 < self._size <len(self._data) // 4:
            self._resize(len(self._data)//2)  # 在隊(duì)列元素不足隊(duì)列容量的四分之一時(shí)猿诸,將隊(duì)列長(zhǎng)度縮小一半
        return answer

    def _resize(self, capacity):
        old = self._data
        walk = self._front
        self._data = [None] * capacity
        for k in range(self._size):
            self._data[k] = old[walk]
            walk = (1 + walk) % len(old)
        self._front = 0

    def __str__(self):
        return str(self._data)   # 為了方便我們觀察隊(duì)列內(nèi)部

這個(gè)隊(duì)列的每一種操作的時(shí)間復(fù)雜度都為O(1)婚被,其中入隊(duì)出隊(duì)的時(shí)間復(fù)雜的計(jì)算用到了會(huì)計(jì)學(xué)中攤銷算法。這里自行百度梳虽。

下面我們來測(cè)試一下這個(gè)隊(duì)列:

if __name__ == '__main__':
    my_queue = ArrayQueue()
    my_queue.enqueue(1)
    my_queue.enqueue(5)
    print(my_queue)
    my_queue.dequeue()
    my_queue.dequeue()
    print(my_queue)
    my_queue.enqueue(0)
    my_queue.enqueue(3)
    my_queue.enqueue(19)
    my_queue.enqueue(0)
    my_queue.enqueue(4)
    my_queue.enqueue(66)
    my_queue.enqueue(88)
    my_queue.enqueue(111)
    print(my_queue)
    my_queue.dequeue()
    my_queue.dequeue()
    my_queue.dequeue()
    my_queue.dequeue()
    print(my_queue)
    my_queue.enqueue(0)
    my_queue.enqueue(4)
    my_queue.enqueue(6)
    my_queue.enqueue(8)
    print(my_queue)
    my_queue.enqueue(11)
    print(my_queue)
    my_queue.enqueue(0)
    my_queue.enqueue(4)
    my_queue.enqueue(6)
    my_queue.enqueue(8)
    print(my_queue)

這涉及了所有需要改變隊(duì)列底層數(shù)組大小的情況址芯。

結(jié)果:

image.png

我在之前跟著書也寫過隊(duì)列,跟著個(gè)比起來,簡(jiǎn)直是垃圾——《python程序員算法寶典》張波谷炸、楚秦編著北专。

現(xiàn)在真是什么人都敢出來寫書。
垃圾Q浮M赝恰!描孟!

我寫文章也就自己看看驶睦,你們竟然厚顏無恥出書!D湫选3『健!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末青抛,一起剝皮案震驚了整個(gè)濱河市旗闽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜜另,老刑警劉巖适室,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異举瑰,居然都是意外死亡捣辆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門此迅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汽畴,“玉大人,你說我怎么就攤上這事耸序∪绦” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵坎怪,是天一觀的道長(zhǎng)罢坝。 經(jīng)常有香客問我,道長(zhǎng)搅窿,這世上最難降的妖魔是什么嘁酿? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮男应,結(jié)果婚禮上闹司,老公的妹妹穿的比我還像新娘。我一直安慰自己沐飘,他們只是感情好游桩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布牲迫。 她就那樣靜靜地躺著,像睡著了一般借卧。 火紅的嫁衣襯著肌膚如雪恩溅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天谓娃,我揣著相機(jī)與錄音脚乡,去河邊找鬼。 笑死滨达,一個(gè)胖子當(dāng)著我的面吹牛奶稠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捡遍,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锌订,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了画株?” 一聲冷哼從身側(cè)響起辆飘,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谓传,沒想到半個(gè)月后蜈项,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡续挟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年紧卒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诗祸。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跑芳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出直颅,到底是詐尸還是另有隱情博个,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布功偿,位于F島的核電站盆佣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脖含。R本人自食惡果不足惜罪塔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一投蝉、第九天 我趴在偏房一處隱蔽的房頂上張望养葵。 院中可真熱鬧,春花似錦瘩缆、人聲如沸关拒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽着绊。三九已至谐算,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間归露,已是汗流浹背洲脂。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剧包,地道東北人恐锦。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像疆液,于是被迫代替她去往敵國和親一铅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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