天天看點

《算法技術手冊》一2.4.2 對數級算法的性能

酒保和顧客打了一個10 000 美元的賭。酒保說:“我會從1~1 000 000中挑選一個秘密數字,你有20次的機會來猜這個數字。每次猜完,我會告訴你結果是高了、低了,還是猜中了。如果你能在20個問題之内猜到我的秘密數字,我給你10 000美元,否則你給我10 000美元。”你會打這個賭嗎?回答當然是應該打,因為你能夠穩赢。表2-1給出了範圍為1~8的示例場景。表中展示了如何通過一系列的詢問,每次将候選資料縮減一半。

表2-1:在1~8之間猜數字的示例行為

《算法技術手冊》一2.4.2 對數級算法的性能

酒保的每次回答都可以将數字範圍縮減一半,直到剩下最後一個可能的數字。最後的情況會在1 + log2 (n)次詢問之後出現,其中log2(x)是計算以2為底數的x的對數。向下取整函數x将數字x向下取整到小于等于x的最大整數。例如,如果酒保選擇的數字在1~10,你需要猜測的次數為1 + log2 (10)= 1 + 3.32,即4次。如果需要進一步證明上述公式,可以假設酒保在兩個數字中選擇一個,那麼你需要兩次才能保證猜到該數字,即1 + log2 (2) = 1 + 1 = 2。需要注意的是,根據酒保的規則,你必須要說出你猜的數字。

這種方法在1 000 000個數字的時候也同樣可行。事實上,例2-1所示的猜數算法能夠對于任意範圍[low, high]有效,并且在1 +log2 (high-low+1)次内猜測到隐藏的數字n。如果有1 000 000個數字,那麼這個算法将在最多1 +log2 (1 000 000)= 1 + 19.93亖最多猜20次(最壞情況)就可以知道是哪個數字。

例2-1:在範圍[low, high]之間猜數字的Java代碼

// 當n确認在範圍[low, high]時,計算需要猜測的次數

public static int turns (int n, int low, int high) {

int turns = 0;

// 如果還有潛在的數字需要猜測,則繼續

while (high >= low) {

}

return turns;

對數級算法是非常高效的,因為它們能夠快速收斂得到解。這種算法的成功之處在于可以每次将問題的規模縮減一半。以上的猜數算法最多經過k = 1 + log2 (n)次疊代就可以得到解,在第i次(0在書中接下去的部分中,log(n)均指代以2為底的對數,是以我們會舍棄log2 (n)中的下标。

另外一個展現高效對數級算法的例子是使用二分(bisection)算法求一進制方程的根,即求出滿足連續型函數f(x) = 0的x。已知有兩個初始值a和b,其中f(a)和f(b)正負符号相反,即一個是正數,另一個是負數。在每一步中,算法二分範圍[a, b]并計算它的中間點c,以此來決定根所在的半區間。是以,每一輪都會使得c更加近似根值,并将誤內插補點削減一半。

為了求f(x) = xsin(x)-5x -cos(x)的根,已知a = -1,b = 1。算法會逐漸收斂至f(x) = 0,x=-0.189302759即為方程的根(見表2-2)。

表2-2:二分法

《算法技術手冊》一2.4.2 對數級算法的性能

繼續閱讀