讀古今文學網 > 算法神探:一部谷歌首席工程師寫的CS小說 > 27 警察學院中的「堆」 >

27 警察學院中的「堆」

第二天一大早,Frank就從安全屋裡溜出來,穿過市中心到警察學院去。只要到了學校,看到身邊有很多警察、學員和退休警官,他就會覺得很輕鬆。他甚至笑著大搖大擺地走過了通往學院辦公大樓的院子。

Frank很多年沒有來這裡了。學校有一條規定,需要教授們一直保持辦公室的門是打開的,以方便學生隨時來問問題。然而實際情況是只有很少的學生會主動利用這個學習機會,大多數學生則是到考試前一天晚上才意識到自己有多少不會的知識,Frank則要更晚,他一直要等到考完試看到卷子後才能發現自己有多麼無知。

掃一眼大門口的教職工目錄就可以發現,Loop教授佔用了整個頂樓的私人辦公室。Frank並沒有感到驚訝。教職工大樓特殊的設計令辦公室的分配備受爭議。每層樓的辦公室數量都是下面一層樓的二分之一。這意味著你在更高層享受更廣闊美景的同時,辦公室的面積也變成了下一層辦公室的兩倍。在幾年的爭搶後,院長頒布了一個基於教齡的嚴格規定——每個人的教齡必須比其樓下辦公室的人的教齡要長。結果,他把教職工大樓變成了一個「堆」。

Olivia Loop博士是巫師犯罪學的教授,她的教齡有70年之久。只有研究浮點運算的Babbleton教授可以與她比肩,他有61年的教齡。

Frank走到最頂層時,他大口喘著氣,在想95歲的老教授是如何爬上來的。不過她每天爬上爬下,也確實得到了鍛煉。

「進來,」Loop教授的聲音從開著的門裡面傳來,「快坐下,免得摔倒了。這個樓梯對於像你這樣的年輕人來說有點強人所難了。」

Frank走進了辦公室,非常感激地癱倒在Loop教授桌前的木椅上。他氣喘吁吁了好一會兒,Loop教授則靜靜地看著他。

「不錯的辦公室!」Frank終於能開口說話了。

「確實不錯,不是嗎?」Loop教授說,「我等了70年,就為了在這個辦公室工作,70年!Iterator教授拒絕退休也是為了這個,而我耐心地等到了這天。你知道Iterator教授宣佈退休的那天發生了什麼嗎?」

Frank搖搖頭,還是有些喘不過氣,說不出話來。

「有一個年輕又自命不凡的人,也就是Lambda教授,想強佔我的辦公室!」

「真的?」Frank氣喘吁吁地說。

Loop教授聳聳肩:「你應該知道,在警察學院,退休一直都是一件令人激動的事情。因為實行了教齡制度,只有教齡最老的教授可以退休,一旦有人退休,所有人都想找機會搬到更好的辦公室去。

「其實,這都是Iterator教授的錯。在執教75年後,他終於搬走了,路上還碎碎念著他的一些煩人的學生。但他退休這件事,他只告訴了一個人——Lambda教授,一位只有11年教齡的教授。

「他不顧我們的系統,直接從他寒磣的辦公室搬到了頂樓。哈哈!每次有人退休,這種事就會發生!這棟大樓裡底層一個辦公室的教授直接跑到頂樓去,試圖佔有那裡的辦公室。我告訴你,一旦有辦公室空出就會這樣!

「當我聽說Iterator教授的離開時,我直接跑到了頂樓,希望宣告這個辦公室是我的。它確實應該是我的,你知道嗎,我是唯一一個符合規定的人,我在這裡工作了70年。但是有著61年教齡的Babbleton教授聽說我跑上去了之後,他也想試試爭取這個辦公室。他們每次都這樣。

「只要有一個辦公室空出來了,樓下的兩個教授都會跑上去聲稱這個辦公室是自己的。所以在這樣的情況下,我和Babbleton教授必須與Lambda教授爭搶最好的辦公室。

「就這樣,Lambda教授、Babbleton教授和我,我們吵了一個多小時,Lambda教授根本沒有權利來爭奪,大家都心知肚明,但他固執地爭了很久。終於只剩我和61年教齡的Babbleton教授在爭吵。最後,我贏了,我逼迫只有11年教齡的Lambda去了我的舊辦公室,有著61年教齡的Babbleton仍待在他原來那個辦公室。

「Lambda教授把他的東西帶到了我原來的辦公室,但是那個可憐蟲發現又有兩個教授等著他,他們是我原先辦公室樓下的兩位教授,正在爭取搬上來的機會。

「他們都比Lambda更有資格佔用我原先的辦公室,他們的教齡分別是40年和30年,這次他們沒有吵得太厲害,Variable教授贏了,畢竟他工作了40年。

「不幸的Lambda教授又搬到了樓下,這次,Lambda辦公室下一層的兩位教授都比他年輕,我覺得他應該感到高興,因為他贏了,他終於讓其他教授吃了閉門羹。

「其實Lambda教授還是很幸運的,」Loop教授解釋道,「他本想要佔有頂層的辦公室,卻意外地搬到了辦公樓中有較多年輕教授的一邊,這使得他最終還是比之前上升了一層。規定只說過樓上的辦公室的教授必須要比其樓下辦公室的教授教齡更長。所以Lambda教授現在純粹靠運氣贏得了二樓的辦公室,但是還有比他更長教齡的教授在一樓呢。」

Frank靜靜地等老教授把故事講完,但老教授聽起來好像有說不完的話,他試探地問道:「Loop教授,我可不可以佔用您一點時間?我想問您一些問題。」

「當然可以,」Loop教授說,「我猜是關於這周的作業問題吧!」

Frank愣了一下,立刻回過神來,「什麼?不是,我現在已經不是這裡的學生了。」

「你現在還不是嗎?那你應該考慮加入警隊,這可是一個很光榮的職業。」

「我十年之前就畢業了。」

「是嗎?」Loop教授聳了聳肩,「教書教久了,學生們都不記清了。」

「好吧,」Frank說,他絕望地找回剛被打亂的思路,「對了,我想知道關於安保的咒語。」

「哦,我並不教魔法,」Loop教授說,「我教的是巫師犯罪學,這是一門……」

「我上過您的課,」Frank打斷道,「我不想知道如何施展咒語,我只想知道有哪些類型的安保咒語,尤其經常會在警局使用的。」

Loop教授的表情突然變得嚴肅起來。「這是非常敏感的信息,」她說,她的聲音變得冰冷,「只有很少一部分人知道。」

「這也是我為什麼來這裡的原因。」Frank說。

「你到底為什麼需要這個信息?」她問。

「我在調查首都警局的盜竊案。」他回答道,心裡想:她先是胡言亂語地說了一通故事,現在居然又要盤問我?我時間可不多了。

「我需要看你的警徽。」Loop教授伸出手來。

Frank從他的披風口袋裡找出私人偵探的徽章,扔在她的桌上。

「私人偵探?」Loop教授笑了笑,然後她的聲音又變得非常強硬,「給我出去。」

「Loop教授……」Frank的聲音被插銷的上鎖聲打斷。

警用算法導論:堆

節選自Drecker教授講義

最大堆是基於二叉搜索樹的數據結構,它的每個節點與其子節點之間需要時刻維持有序關係。具體來說,堆在存儲元素時一定要遵循堆的特性,對於最大堆,樹中的任意一個節點的值都要大於(或等於)其下面的所有節點。這種結構允許最大堆高效地支持幾個非常重要的操作:(1)找到最大的元素,(2)刪除最大的元素,(3)插入任意元素。這三個操作使得堆成為實現優先級隊列的理想數據結構。

堆看起來像一棵樹,它很容易通過數組來實現,其中數組中的每個元素對應了樹中的一個節點,根節點位於索引0處,如下頁圖所示。子節點索引可以通過父節點的索引計算得到。具體來說就是,索引i處節點的子節點位於索引2×i+1和2×i+2處。因此,索引1處節點的兩個子節點分別位於索引(2×1)+1=3和索引(2×1)+2=4處,如下頁圖所示。

有時為了簡單起見,一些堆在實現的時候直接跳過了數組索引0。根節點被放置在索引1處。在這種情況下,索引i處節點的子節點位於索引2×i和2×i+1處,這使得索引計算更為簡單。無論哪種方式,都可以便捷地通過父節點的索引值來計算出兩個子節點的索引值,也可以通過子節點的索引值計算出父節點的索引值(子節點的索引值除以2後向下取整便可得到其父節點的索引值)。

由於根節點(數組中的第一個元素)總是最大堆中的最大值,因此你可以始終在固定時間內獲取該值,而不論數組中還有多少其他元素。這使得用戶可以有效地查找優先級隊列中的最高值元素。

如果你想添加一個元素或刪除最大元素,這個過程會更複雜,需要首先打破堆的特性,然後再逐步恢復堆的特性。

為了添加一個新元素,首先將新的元素添加到數組的最後面(即樹底層中的第一個空白處)。如果新添加入節點的值大於其父節點的值,這將破壞堆特性,因此需要將此節點向上移動,直到它不再大於其父節點的值,並重新恢復堆的特性。也就是說,如果新加入節點的值大於其父節點的值,就不斷地將該節點的值與其父節點的值進行交換。例如,如果要將60這個數添加到前面的堆中,則首先將它插入底部,然後將其向上移動,進行兩次與上一級節點的交換操作。因為第一次在與15交換後,該節點的值仍然大於其新的父節點55,所以還需要再與55交換一次。

刪除最大值元素也是類似的。將原來的最大值與數組的最後一個元素交換位置,使原來最後的那個元素成為新的根節點。

接下來刪除現在最後的這個元素就可以了(此時原來的最大值已經成為數組的最後一個元素)。雖然現在已經正確地刪除了原來的最大值的節點,但這個操作也破壞了堆的特性。

我們需要從新的根節點開始沿樹向下調整該節點,以恢復堆的特性。在樹的每一層,我們將該節點的值與其子節點進行比較。如果該值小於它的任何一個子節點的值,就需要將該值向下移動,並將兩個子節點中值較大的那個子節點與其交換位置,以恢復堆的特性。直到該節點的子節點都比這個值小時,就結束操作。

插入新元素和刪除最大元素的操作都需要我們從樹頂部逐層調整直至合適的位置,不過最多只需從頂部到底部調整一遍。如果要往堆中增加一倍的節點,只需要在堆的底部增加一層節點即可,所以此時的插入和刪除操作依然很快,即使是一個大的堆也會很快。換句話說,雖然堆的節點總數增加了一倍,但堆的層數只增加了一層,插入和刪除操作僅比原來多交換一次。此外,由於上述插入和刪除操作可以保持樹的平衡,所以之後的操作也同樣是高效的。