讀古今文學網 > 算法神探:一部谷歌首席工程師寫的CS小說 > 12 餐廳中的棧和隊列 >

12 餐廳中的棧和隊列

Frank俯下身子,快速地向門的方向跑去,他試著推門、拉門、踢門,但除了製造出巨大的聲響外,一無所獲。

Frank轉向Socks,希望這位年輕的巫師知道可以讓鐵門扭曲的咒語。鑒於目前他們的處境,他相信Socks對使用開鎖咒語應該沒有意見。不過當Frank看到燃燒著的羊皮紙和向天花板上飄的煙霧時,他一時呆住了。他的腦海中浮現出了一個煙霧繚繞的廚房的景象,這讓他回想起了在警察學院的第一年,他彷彿聽到廚房中的廚師在對他大喊大叫。Frank用力地閉上了眼睛,試圖將這個景象從腦海中抹去。

在警察學院的最初兩個月,Frank利用課餘時間在學校的餐廳打工。他幹的工作並不怎麼光鮮。新來的人都不會被安排著洗盤子,更不要說做飯了。Frank要做的是將一大堆洗乾淨的托盤、盤子和餐具從廚房拿到餐廳內的桌上,每個星期要花費15個小時。

即使工作很枯燥,Frank依然幹得很開心。「看!我在給你們幫倒忙。我是你們這些勤雜工的天敵!」他會對那三個負責收拾桌子和髒亂餐具的勤雜工這樣叫道。他試圖去打破學院裡兩分鐘內運送盤子數最多的紀錄,但失敗了。他還發明了一個可以在餐廳裡玩的新遊戲,叫扔勺子。但直到有一天,當他在餐廳內遇到Heappens教授的時候,他才學到了一些真正的知識。

「咳。有些數據結構就不應該存在於餐廳中。」Heappens教授邊研究今天餐廳有哪些食物,邊這樣說道。

當時是下午兩點半。吃午飯的人群高峰期已經過了。Frank Runtime正在努力將一堆碗運到放湯的地方。儘管教授那句話並不是對他說的,但他還是抬起了頭,問道:「什麼數據結構?」

「棧,」Heappens教授抬起頭看著Frank說道,「棧不應該用在餐廳裡。」

「不,它們用在這正好,」Frank用一種不知者無畏的語氣說道,「你看,這些碗、盤和煎餅都是以棧的形式放著的。」

Heappens不屑地揮了揮手,準備走開,說道:「算了,你對數據結構又知道些什麼?」

「不然你還能怎麼擺它們?」Frank問道,「要是攤開來擺的話也太佔空間了。」

教授停下來,十分擔憂地看著Frank。這樣看了快一分鐘後,他說道:「你知道棧和隊列的區別嗎?」

Frank搖了搖頭。他還沒有上過警察數據結構這門課。

「棧是後進先出的數據結構,」教授解釋道,「它支持兩種操作:將一個東西放入頂端,以及從頂端拿出一個東西來。」

他指了指面前那一疊盤子,「就像這疊盤子一樣。你可以把一個盤子放上去。」

他將手裡拿的空盤子放了上去。

「也可以從頂端拿一個盤子下來。」他將自己的盤子拿了回來。

「每次你從棧裡面拿出一個東西的時候,你拿到的都是最近才放上去的。最先被放進去的東西會一直待在棧的最底部,直到你把它上面的所有其他東西都拿出來。」

「所以呢?」Frank問道,「這有什麼問題?」

「如果你正確使用它的話,後進先出的數據結構沒什麼問題。在你寫深度優先算法的時候,棧有用極了。你只用每次將新的搜索選項放到棧的最頂端,然後在倒退的時候把它們從頂端拿出來就好了。但餐廳已經錯誤地用棧好幾十年了。

「就比如這一疊盤子。你知道最下面那一個盤子已經在這待了多久了嗎?」

Frank試圖回憶他上次看到盤子這裡空著是什麼時候,但他根本想像不出來這個畫面。

「五年!」Heappens教授叫道,「我知道,因為我對這個盤子標記過。五年來,你們這樣的學生不斷地把新的盤子放在最頂端,而最下面這個盤子從來沒有被用過。它的邊緣一直在那沾灰。

「但這並不是最可怕的。看看他們是怎麼處理這些土豆泥的。」

Frank眼睛掃了掃旁邊那一大木碗的土豆泥。一個廚師正在往裡面加剛出爐的土豆泥。那個廚師手裡拿著一個大鍋,正愉快地用一個長柄勺把土豆泥從鍋中攬到碗中。過了一會兒Frank才意識到廚師只是將原來的土豆泥埋在了最下面。他的胃開始不舒服了。

「最下面的土豆泥放了多久了?」Frank問道,其實Frank並不想知道答案。

「別擔心。他們至少每週會將這些木碗洗一次。所以再久也不過一周。」

Frank並沒有感到欣慰。事實上,他感到非常不舒服。快速掃了一圈,Frank看到餐廳內幾乎每個地方都在用後進先出的數據結構。當他看到裝沙拉醬的大桶時,他不敢再看下去了,他感到既慌張又噁心。

「我們能做些什麼?」Frank問道。

「用隊列,」教授回答道,「隊列幾乎可以說是為餐廳量身定制的。」

「隊列?」Frank問道。

「先進先出的數據結構,」Heappens教授解釋道,「像棧一樣,它們同樣是用來儲存東西的,並且也支持兩種操作。你可以將一個東西放到隊列的最後,也可以從最前面拿出一個東西來。這樣,你拿出來的永遠都是最先放進去的東西。」

Frank想像了一下每次都從一疊盤子的最底下拿盤子,多麻煩,於是問道:「但如何做呢?」

「這正是數據結構如何運作的問題。看看等三明治的人所排的隊,那就是一個隊列。現在隊裡面有四個人,而最前面的人已經等待的時間最長。」

正當Heappens教授說著,又有一個人開始排隊了。「看,他走到了隊列的最後。」教授說道。

Frank和教授看著那條隊伍,直到排在第一位的人拿著自己的三明治走了。

「看,有人從隊伍最前面離開了,」教授開心地說道,「這個餐廳需要更多的隊列。所有餐廳都需要更多的隊列。」

Frank想到了之前看到的土豆泥,意識到教授說的沒錯。數據的儲存方式可以很大程度上影響到它被存取的方式。對於土豆泥的這種情況,存取的順序是很重要的。

即便意識到這點很容易,Frank花了好幾天才把隊列這種數據結構用到了餐廳裡。改變盤子和碗存取的方式相對簡單一些,他只需要把原來的那堆盤子或碗拎起來,然後把新的放在最底下就好了。說服廚師讓他們改變加菜的方式就困難多了,廚師們非常享受將一大勺一大勺的土豆泥攬到碗裡的這個過程。最終,Frank提議讓他們用兩個碗,每次將那碗舊的土豆泥攬到新的土豆泥的碗裡。雖然嚴格來說這還不是一個隊列,但這樣做既讓廚師們依然可以享受到攬土豆泥的樂趣,也避免了舊的食物被埋在新的食物下面。

有一天,Frank需要頂替一個生病了的麵包師。Frank堅持說後進先出地將面包裝入烤箱對放在最後的麵包是不公平的。所以他提出,每過25秒鐘就要拿出烤箱中最後一塊麵包,並將其他麵包往後推,然後在最前面放入一片新的麵包。

如果烤箱有前後兩個門的話,這將會是一個非常好的計劃。不幸的是,餐廳用的烤箱只有一個門。這讓Frank的計劃實施起來變得非常困難。這樣時不時地改變麵包的位置的確能讓每一塊麵包受熱更加均勻。但Frank意識到自己加麵包的速度根本趕不上烘烤進度,很快就有麵包烤糊了,濃濃的黑煙從烤箱中飄了出來。

當其他廚師都在忙著拿水桶去救火時,Frank只是麻木地看著那烤糊了的麵包。當他意識到隊列也許不是對餐廳裡的所有問題都適用時,他感到了一絲困惑和絕望。看來關於數據結構他需要學的還有很多。

警用算法導論:棧和隊列Ⅰ

節選自Drecker教授講義

棧和隊列是用來存放數據的兩種簡單的數據結構。表面上看,它們和一個列表沒有什麼區別,其實它們和列表的區別主要體現在添加和刪除數據的方式上。

棧是後入先出的數據結構,就像我們對辦公桌上的一疊公文的操作順序那樣。新的元素會被加入(推入)到棧的最上方,而刪除(彈出)元素的時候也會從最上方刪除。如果1、2、3、4、5依次被推入了一個棧,那麼它們會以5→4→3→2→1的順序被彈出。當然,在現實生活中,如果你真把桌上的公文全都處理完了,警長會馬上給你安排更多的工作。

你可以用一個數組(A)和一個記錄當前棧頂位置的變量(Top)來實現一個棧。當推入一個新的元素時,就會把它加到下一個空的位置裡,也就是A[Top+1]裡。同時,你也需要把Top的值加上1。

當從棧裡面彈出一個元素時,可以用Top來找到應該彈出的元素(即A[top])。同時,你也需要將Top的值減去1。

當然,如果你的數組大小是固定的,那麼在推入新的元素時也要注意檢查數組中還有沒有剩餘空間。

隊列是一個先入先出的數據結構,就像排成一列等待被詢問的犯罪嫌疑人一樣。新的元素會被加到隊列的最後,而刪除元素時會從隊列最前面刪除。如果1、2、3、4、5依次被加入到了隊列,它們會以同樣的順序從隊列中出來。

隊列也可以用數組來實現。你需要兩個變量來記錄目前隊列中的第一個元素(Front)和最後一個元素(Back)在哪。當加入一個新元素時,我們把它放到目前隊列尾部之後的一個格子(A[Back+1]),並把Back加1。

相反,在彈出一個元素時,我們把目前處於隊列最前端的元素(A[Front])彈出,並將Front加1。

如果使用一個固定大小的數組來實現隊列,在不斷向隊列中添加元素和從中彈出元素時,數組的前端會逐漸出現一段空白,如果隊列用完了數組後面的所有空間,可以讓它繞到前面來利用這段空白空間。但如果這樣做,一定要細心地處理好在添加和彈出元素時Back可能繞到數組最前面的情況。