轉自:https://baijiahao.baidu.com/s?id=1571323359136961&wfr=spider&for=pc
幾天以前......
幾天之後......
拍賣行的商品總數量有幾十萬件,對應數據庫商品表的幾十萬條記錄。
如果是按照商品名稱精確查詢還好辦,可以直接從數據庫查出來,最多也就上百條記錄。
如果是沒有商品名稱的全量查詢怎麼辦?總不可能把數據庫裏的所有記錄全查出來吧,而且還要支持不同字段的排序。
所以,只能提前在內存中存儲有序的全量商品集合,每一種排序方式都保存成獨立的集合,每次請求的時候按照請求的排序種類,返回對應的集合。
比如按價格字段排序的集合:
比如按等級字段排序的集合:
需要注意的是,當時還沒有Redis這樣的內存數據庫,所以小灰只能自己實現一套合適的數據結構來存儲。
拍賣行商品列表是線性的,最容易表達線性結構的自然是數組和鏈表。可是,無論是數組還是鏈表,在插入新商品的時候,都會存在性能問題。
按照商品等級排序的集合爲例,如果使用數組,插入新商品的方式如下:
如果要插入一個等級是3的商品,首先要知道這個商品應該插入的位置。使用二分查找可以最快定位,這一步時間複雜度是O(logN)。
插入過程中,原數組中所有大於3的商品都要右移,這一步時間複雜度是O(N)。所以總體時間複雜度是O(N)。
如果使用鏈表,插入新商品的方式如下:
如果要插入一個等級是3的商品,首先要知道這個商品應該插入的位置。鏈表無法使用二分查找,只能和原鏈表中的節點逐一比較大小來確定位置。這一步的時間複雜度是O(N)。
插入的過程倒是很容易,直接改變節點指針的目標,時間複雜度O(1)。因此總體的時間複雜度也是O(N)。
這對於擁有幾十萬商品的集合來說,這兩種方法顯然都太慢了。
——————————————
新節點和各層索引節點逐一比較,確定原鏈表的插入位置。O(logN)
把索引插入到原鏈表。O(1)
利用拋硬幣的隨機方式,決定新節點是否提升爲上一級索引。結果爲“正”則提升並繼續拋硬幣,結果爲“負”則停止。O(logN)
總體上,跳躍表插入操作的時間複雜度是O(logN),而這種數據結構所佔空間是2N,既空間複雜度是 O(N)。
自上而下,查找第一次出現節點的索引,並逐層找到每一層對應的節點。O(logN)
刪除每一層查找到的節點,如果該層只剩下1個節點,刪除整個一層(原鏈表除外)。O(logN)
總體上,跳躍表刪除操作的時間複雜度是O(logN)。
小灰和大黃並不知道,他們的這一解決方案和若干年後Redis當中的Sorted-set不謀而合。而Sorted-set這種有序集合,正是對於跳躍表的改進和應用。