倒排索引優化 - 跳錶

海鳥發表於2014-09-23

在前面一篇介紹 倒排索引 的文章中我們知道, 兩個關鍵字的合併操作的時候複雜度是 θ(N), 如果在合併操作時遇到最極端的情況, 所掃描和比較的次數是兩個列表集合的所有元素個數之和, 即是線性增長的, 這在資料量特別大的時候是很低效的. 我們還是看一下兩個集合的合併操作程式碼示例: 

a = [1, 2, 3, 6, 9, 11, 45, 67]
b = [4, 6, 13, 45, 69, 98]

i = j = 0
result = []
while i < len(a) and j < len(b):
    if a[i] == b[j]:
        result.append(a[i])
        i = i + 1
        j = j + 1
    elif a[i] < b[j]:
        i = i + 1
    else:
        j = j + 1

print result

# 輸出
[6, 45]

 如果待合併的兩個倒排表資料量很大, 但是交集很少時, 會是什麼情況呢?

[1, 2, 3, 4, 5, ... 10001, 10005]
[1, 10001, 10008]

如果對這兩個做合併操作, 最後的交集結果只有  [1, 10001] 2個元素, 但是卻要做10001次移動和比較操作, 所以肯定有什麼辦法來優化這一點. 可能你已經想到了, 我們做了這麼多無用比較, 是因為我們每次指標向前移動的步子太小了點, 如果我們在每次比較後向前多移動一點, 可以忽略很比無用的操作. 這就是跳錶的思想.

我們看第一個倒排表, 如果它以5000為步長前進, 進我們只需要向前查詢兩個即可找到我們需要的元素: 10001 . 這裡寫一個跳錶功能的合併演算法程式碼:

a = range(10008)
b = [1, 10001, 10008]

i = j = 0
result = []
step = 100
count = 0
while i < len(a) and j < len(b):
    if a[i] == b[j]:
        result.append(a[i])
        i = i +1
        j = j + 1
        count = count + 1
    elif a[i] < b[j]:
        while (i + step < len(a)) and a[i+step] <= b[j]:
            i = i + step
            count = count + 1
        else:
            i = i + 1
            count = count + 1
    else:
        while (j + step < len(b)) and b[j+step] <= a[i]:
            j = j + 5000
            count = count + 1
        else:
            j = j + 1
            count = count + 1

print result
print count




a = range(10008)
b = [1, 10001, 10008]
count = 0

i = j = 0
result = []
while i < len(a) and j < len(b):
    if a[i] == b[j]:
        result.append(a[i])
        i = i + 1
        j = j + 1
        count = count + 1
    elif a[i] < b[j]:
        i = i + 1
        count = count + 1
    else:
        j = j + 1
        count = count + 1

print result
print count

上面程式碼中故意構造了一個很大的集合 [0 ... 10007], 然後用變數count作為計數器來分析兩個演算法分別執行的操作次數, 可以看到採用跳錶演算法時(我們模擬了step=100)的計算次數是207, 而用之前的方式計算次數是10008, 可見效能提升了很多倍.

這裡有幾點說明下:

1. 這裡為了簡單說明跳錶的思路, 全部用了陣列表示倒排表, 其實真實的資料結構應該是連結串列結構(linked list). 這才符合磁碟儲存結構. 

2. 跳錶的原始結構演算法比這個複雜, 而且根據場景的不同, 跳錶有不同的實現. 這裡因為不是利用跳錶的快速查詢功能, 所以沒有多級指標索引概念, 詳細跳錶實現查考: skip list 

相關文章