1. 程式人生 > >python 的 dict 會隨著key的增加而變慢嗎

python 的 dict 會隨著key的增加而變慢嗎

很多人會告訴你,這是Hash Table,而Hash Table的訪問速度是O(1)的,而對於你來說,這就和沒說一樣。這個答案既不算精確,也沒能回答你的問題。

首先如果你真的想搞清楚這個問題的來龍去脈,你需要搞懂Hash Table到底是什麼東西。Hash Table首先默認了一件事情,在電腦中,讀取或者寫入一個已知地址的記憶體需要的最大時間是固定的,和有可能寫入記憶體的長度無關的。

舉個例子,你在家有一個櫃子,櫃子上有一堆抽屜,你把這些抽屜分別編號為1-10。這時候我和你說,找10號抽屜,你可以立刻找到10號。我說找6號,你可以立刻找到6號。現在你的櫃子擴大了,抽屜有100個,我說找56號,你還是可以立刻找到56號。你櫃子又大了,變成了1000個抽屜,我說找729號,你依然可以立刻找到729號。

對於人類來說,這是不可能的。隨著抽屜數量的增加,我們找對應抽屜的速度會下降。但是對於現在的計算機來說,這個假設是成立的。這是你要理解的第一點計算機和人類的不同。無論有多少抽屜,只要你說找這個號碼的抽屜,不管是第一個,最後一個,還是中間任何抽屜,計算機的反應速度都一樣。

好,現在假設我們有1000個抽屜,我們有4份檔案。每一份檔案有一個唯一且不重複的檔案編碼,這個編碼是8位碼,比如01303457。我們需要把這四份檔案儲存到抽屜裡,並且希望下次有人來要這些檔案的時候,我們可以立刻找到。

我們可以把這四份檔案都放到第一個抽屜,然後人來的時候把四個檔案找出來,逐一對比檔案編碼。這是一種線性資料結構,他的搜尋速度和檔案的數量是線性關係。我們有100份檔案,速度大概是10份的10倍。我們管他叫O(N)。我們可以按照檔案編碼先把檔案排序,然後都放第一個抽屜裡。這樣搜的時候,我們可以利用二分法來找檔案。這時候我們的速度提高到了log(N)。隨著檔案的增加,我們的搜尋時間還是在增加,但是增加的緩慢了。

然而還有一個方法。我們可以看檔案的尾號,把他們放進和尾號對應的抽屜裡。比如01303457號檔案,我們就給他放到457號抽屜。我們可以想象,在檔案比較少的時候,我們的每個抽屜很可能只有一份檔案。這時候有個人來說我要01303457號檔案,我們就可以直接去457號抽屜給她拿。由於我們找一個抽屜的速度時固定的,所以隨著檔案的增加,我們搜尋檔案的時間是沒有增加的。

下面才是重點,也是兩種邏輯產生衝突的地方。如果檔案多了,肯定會有抽屜不止兩個檔案。最理想的情況下,在有1001份檔案的時候,也會有一個抽屜有兩份檔案。而最差的情況,所有的檔案尾號都一樣,那我們不是回到了第一種方法麼?

沒錯!你想的是完全正確的。相信自己的判斷。

很多課在入門資料結構的時候會盡量回避這個地方,或者一筆帶過,留下莫名其妙的學生們,背下來Hash Table的access是O(1)。而事實上這裡的O(1)是有很多先決條件的。

第一,這是一個平均值,不是最差值。在Worst case下,Hash Table的access時間並非O(1),是會隨著資料的增長而增長的。也就是說,我們常理上看,雖然有著每一份來的檔案編號尾號都一樣的可能性,但是平均上說,他們應該是分的比較開的。而如何把檔案儘量分散地對應到相應的抽屜,也是一個學問,這裡不贅言(思考一下如果按照常規的檔案編號方法,順序編號,用尾數和用開頭的三位會有什麼不同?)。

第二,你抽屜的數量要遠大於檔案的數量。如果你只有10個抽屜,有100份檔案,那當然會隨著檔案數量的增加,找檔案的速度變慢。對於Hash Table也是一個道理,你準備好的記憶體需要遠大於實際使用的記憶體。如果你準備迎接100個數據,你可能就要準備400個以上的空位。隨著資料量的增加,為了保證你的讀寫速度還在O(1),你的記憶體開銷也會變大。換言之,一個理想Hash Table的讀寫速度確實是O(1),但是佔用記憶體是無限大(因此理想Hash Table不存在)。

回到你的問題,在我們正常寫程式的時候,所用到的資料量都不算太大(相對於可支配記憶體),所以我們可以把dict看作一個理想Hash Table。這時候隨著key的增加,它確實不會變慢(佔用記憶體變多)。但是到了極端情況,當你的key非常非常非常多的時候(這時候要看他Hash Table的具體實現),他的速度是有可能隨著key的增加而減慢的。