初始想法
當記憶體足夠時可以直接在記憶體中維護位向量,有對應位是否為1表示是否出現了對應整數。當記憶體只有幾百位元組時可以考慮將位向量分段,每次讀一遍順序檔案正確設定對應位,位向量的每個段都存入外部檔案。
經典解法
提出的第一種解法實際上超出了題目要求的範圍,因為仔細讀題目會發現只是要找出乙個不在給定檔案中的32位整數,而不需要找出所有,可以用二分查詢的思想快速解決這個問題,每次給定乙個缺少乙個整數的範圍都尋找合適的分割方法將包含乙個缺失整數的範圍縮小,再在縮小的範圍內繼續查詢,直到範圍足夠小使得這個範圍內的整數都可以放入記憶體中,然後就是用位向量方法找出缺失整數。
// 以下方法針對32位無符號整數,如果是一般整數就需要先用0分割
mask =
1<<
31while cnt >
5for each num in inputfile // 根據輸入檔案中數字的最高位為0還是1放入不同的輸出檔案
if num & mask
cnt1++
write num to outfile1
else
cnt2++
write num to outfile2
mask >>
1//邏輯右移一位
inputfile = cnt1 < cnt2 ? outfile1: outfile2
cnt = cnt1 < cnt2 ? cnt1: cnt2
// 根據位向量找出缺失整數,難在確定當前範圍,之後得到當前範圍的位向量即可
mask = mask-
1if any num in inputfile bigger than mask
range =
[mask+
1, mask+
1| mask]
else
range =[0
, mask]
在只有幾十位元組記憶體的情況下,實現迴圈左移i
位的經典方法有三種
a. 鏈式移位
1. 初始想法
假設原向量為a
,迴圈左移前後元素的索引具有以下對應關係:01
…i-1
ii+1
…n-1
n-in-i+1
…n-101
…n-i-1
上面的對應關係可以翻譯成:原向量的a[k]
將變為左移後向量的a[(k+n-i)%n]
,這樣寫不夠優美,可以借助模的性質改寫為a[(k-i)%n]
。以n=10,i=3
為例,轉移關係為x[0],這可以看成是轉移鏈,也就是這個方法被我稱為鏈式移位的原因。
偽碼如下:
prev = a[i]
k =0
do temp = a[k]
a[k]
= prev
k =(k+i)
% n prev = temp
while k >
0
2. 完善後
但是無法確定是不是所有元素都在一條轉移鏈上,實際上所有向量項可以分為d
條轉移鏈,其中d
為n,i
的最大公約數,這些鏈的代表元素為a[0],a[1],...,a[d-1]
,對上面偽碼修改如下:
for t from 0 to d-
1 k = t
prev = a[k+i]
do temp = a[k]
a[k]
= prev
k =(k+i)
% n prev = temp
while k != t
為什麼鏈的代表元素為a[0],a[1],...,a[d-1]
?
b. 遞迴
待迴圈左移的陣列ab
,根據a
,b
的大小分為以下兩種情況
b
的大小比a
大,將b
分為bl,br兩部分,也就是ablbr,將a與br交換得到brbla,這時a已經放到了合適的位置上,考慮子陣列brbl,只需要將該子陣列迴圈左移i
位,末尾拼接上a就可以得到原陣列迴圈左移i
位的結果。
b
的大小比a
小,將a
寫為alar,其中∣al
∣=∣b
∣,長度
相等|a_l|=|b|,長度相等
∣al∣=
∣b∣,
長度相等
,將b
與al交換,得到baral,只要把aral迴圈左移i−∣
b∣i-|b|
i−∣b
∣位就可以得到balar,也就是原陣列迴圈左移i
位的結果。
偽碼如下:
shift
(v, bg, i, n)
//將陣列v[bg...bg+n-1]迴圈左移i位
if i ==
0|| n ==
0return
if i < n-i
swap
(v[bg.
..bg+i-1]
, v[bg+n-i.
..bg+n-1]
)shift
(v, bg, i, n-i)
else
swap
(v[bg.
..bg+i-1]
, v[bg+n-i.
..bg+n-1]
)shift
(v,bg+i, i-
(n-i)
, n-
(n-i)
)return
簡單分析
分析遞迴的時間複雜度關注的是遞迴層數與每層開銷,也可以使用聚合分析,分析每個元素到達陣列中合適位置需要多少次移動。
考慮情況1,交換後得到brbla,a部分的每個元素到達這個位置涉及3次移動(交換需要3次移動),且這部分的元素之後不會再改動位置。同樣的分析可以用於情況2,交換後得到baral,b部分每個元素到達這個位置涉及3次移動(交換需要3次移動),且這部分的元素之後不會再改動位置。因此每個元素要達到最終的位置都只需要3次移動的開銷,整個遞迴的複雜度為o(n
)o(n)
o(n)
。c. 逐段轉置
待迴圈左移的陣列ab
,整個轉置得到b'a'
,其中b'
為b
的轉置,a'
為a
的轉置,因此只要再分別對a',b'
轉置就可以得到陣列ba
,也就是迴圈左移的結果。
d. 三種程式用時比較
給定字典找出某個單詞的所有變位詞,只要兩個單詞的字母種類和個數相等就認為兩個單詞是變位詞
構造一棵深度為26的樹,乙個非葉子節點表示乙個字母,該非葉子節點出發的一條邊表示單詞中有多少個該字母,葉子節點表示變位詞等價類,變位詞用鍊錶連線,先遍歷一遍字典構建這棵樹,然後給定乙個單詞就沿著這棵樹直到達到某個葉子節點找到所有變位詞。
給每個單詞乙個標識(單詞種類和個數),以此為鍵排序可以將同乙個標識的單詞集合在一起,也就將所有變位詞集中到了一起。
方法1的時間複雜度為o(n
)o(n)
o(n)
,方法2的時間複雜度為o(n
lgn)
o(nlgn)
o(nlgn
)。儘管方法1時間複雜度低,但是使用快排時可以很好地利用快取,而方法1中每次載入樹上不同的節點用於查詢可能不能很好地利用快取。
程式設計珠璣第二章
a題給定乙個最多包含40億個隨機排列的32位整數的順序檔案,找出乙個不在檔案中一32位整數。1 在檔案中至少存在這樣乙個數?2 如果有足夠的記憶體,如何處理?3 如果記憶體不足,僅可以用檔案來進行處理,如何處理?答案 1 32位整數,包括 2146473648 2146473647,約42億個整數,...
程式設計珠璣第二章總結
分析 如果記憶體足夠的話,可以採取第一章的點陣圖排序法,以o n 的效率就可以解決。如果記憶體不足的話,但是有幾個外部檔案可以儲存資料,那麼就可以採用二分查詢的思想來解決問題 這裡的思路比較巧妙,它不是通常意義上的二分查詢,但是思維模式很相似.我們可以通過掃瞄輸入檔案,將第一位是0的寫入乙個檔案,將...
程式設計珠璣第二章ABC
問題 找出乙個不在檔案中的32位整數。注意 題目中沒有說,這40億個數是否是含有重複的資料 條件限制 1 如果有足夠的記憶體,如何處理?2 如果記憶體僅有上百位元組 記憶體不足 且 可以用若干外部臨時檔案,如何處理?舉例 比如abcdef 左移三位,則變成defabc 條件限制 空間限制 可用記憶體...