這一部分主要算法導論中遞歸式、堆排序和快速排序章節里選擇的對我而言較有價值的題目。
練習4.1-1 證明 $T(n)=T(\lceil n/2\rceil)+1$ 的解為 $O(\lg n)$ 。
解答:猜測 $T(n)\leq c\cdot \lg(n-b)$ ($-b$ 的原因見《算法導論》-4.1代換法-一些細微問題),則用數學歸納法,如果其滿足 $n/2$ 則對 $n$ 有:
$$T(n)=T(\lceil n/2\rceil)+1\leq c\cdot \lg(\lceil n/2\rceil-b)+1\leq c\cdot \lg(n+1-2b)-c\cdot \lg2+1\leq c\cdot \lg n$$
最后一個不等號在 $c>1$ 而且 $b>1$ 的情況下成立。
練習4.1-6 求解遞歸式 $T(n)=2T(\sqrt{n})+1$ 的漸進緊確解。
思路:令 $m=\lg n$ 則原遞歸式 $T(n)$ 可以表示為 $S(m)=T(2^{m})=2T(2^{m/2})+1=2S(m/2)+1$ 。猜測 $S(m)\leq cm-b$ ,數學歸納法證明,如果其去滿足 $m/2$ 則對 $m$ 有:
$$S(m)=2S(m/2)+1\leq2(cm/2-b)+1=cm-2b+1\leq cm$$
代入到 $T(n)$ 中則 $T(n)\leq c\cdot \lg n$,即 $T(n)=\mathrm{O}(\lg n)$ 。
練習4.2-2 利用遞歸樹證明 $T(n)=T(n/3)+T(2n/3)+cn$ 的解是 $\Omega(n\lg n)$ 。
思路:該遞歸樹不是完全二叉樹,葉子節點有深有淺,最淺的葉子節點是從根節點沿着路徑 $n\rightarrow (1/3)n\rightarrow (1/3)^{2}n\rightarrow ...\rightarrow (1/3)^{k}n=1$,的層數 $k=\log_{3}n$ 。在該最淺的層數向上,每一層的代價都是 $cn$ 。因此 $T(n)\geq k\cdot cn = cn\cdot \log_{3}n=\Omega(n\lg n)$ 。
練習4.2-4 利用遞歸樹求遞歸式 $T(n)=T(n-a)+T(a)+cn$ 的漸進緊確界。
思路:畫出遞歸樹,該遞歸樹極不平衡,所有是右孩子的節點全部是葉子節點,根節點的代價是 $cn$ ,第 $i$ 層的代價是 $c(n+(1-i)a)$ 。一共有 $n/a$ 層(包括根節點的第 $0$ 層)。總代價:
$$T(n)=cn+\sum_{i=1}^{n/a-1}c(n+(1-i)a)=\Theta(n^{2})$$
練習4.2-5 利用遞歸樹求遞歸式 $T(n)=T((1-a)n)+T(an)+cn$ 的漸進緊確界。
思路:做算法導論的題目,關鍵還是要理解題目的意圖。這道題和上一道是姊妹題,意在說明遞歸樹划分時,如果用常數划分,不管划分得多么均衡,其代價都是 $\Theta(n^{2})$ ,如果用比例划分,不管划分得多么失衡,其代價都是 $\Theta(n\lg n)$ ,其原因就是 $n$ 可以足夠大,以突出常數划分的失衡和比例划分的均衡。練習4.2-2已經證明了其特殊情況的下界(一般情況也沒有什么區別,$1/3$系數的影響最終進入了常數項),即 $T(n)=\Omega(n\lg n)$ 。現在只要證明 $T(n)=O(n\lg n)$ :如果 $T(n)\leq dn\lg n$ ,用代換法:
$$T(n)\leq d(1-a)n\lg(1-a)n+dan\lg an+cn=dn\lg n +dn(a\lg a+(1-a)\lg(1-a))$$
因為 $a\in (0,1)$ ,所以 $\lg a<0,\lg(1-a)<0$,$d$ 有合適的取值能夠使得 $T(n)\leq dn\lg n$ 。
思考題4-2 某數組 $A[1...n]$ 含有從 $0$ 到 $n$ 的整數,但是缺了一個。另有一個數組 $B[0...n]$ 含有從 $0$ 到 $n$ 的所有整數。但是不能夠單獨訪問數組中的某個整數,而只能訪問某一位(整數是二進制的),就是說對數組 $A$ 的操作只能是“取 $A[i]$ 的第 $j$ 位”。給出一個代價為 $\Theta(n)$ 的算法,找出這個所缺的整數。
思路:二進制無符號整數一共 $\lceil \lg n\rceil$ 位。統計數組 $4$ 和 $B$ 中第 $1$ 位 為 $1$ 的整數個數(代價為 $\Theta(n)$),如果 $A$ 中第 $1$ 位為 $1$ 的整數個數小於 $B$ 中的,說明缺失的整數的第 $1$ 位是 $1$ ,否則缺失整數的第一位是 $0$ ;在數組 $A$ 和 $B$ 第 $1$ 位是 $1$ 或 $0$ 的整數里繼續統計第二位為 $1$ 的整數個數(代價為 $\Theta(n/2)$)......直到確定缺失的整數的每一位。整個算法耗費的代價為:
$$\Theta(n)+\Theta(n/2)+...+\Theta(n/(2^{\lg n}))=\Theta(2n)=\Theta(n)$$
思考題4-5 斐波納契數。定義生成函數(形式冪函數)為:
$$f(x)=\sum_{i=0}^{\infty}F_{i}z^{i}=0+z+z^{2}+2z^{3}+3z^{4}+5z^{5}+...$$
- 求證 $f(x)=z+z\cdot f(x)+z^{2}\cdot f(x)$ 。
解答:將 $f(x)$ 代入
$$f(x)=z+\sum_{i=0}^{\infty}F_{i}\cdot z^{i+1}+\sum_{i=0}^{\infty}F_{i}\cdot z^{i+2}=z+ z^{2}+\sum_{i=2}^{\infty}F_{i}\cdot z^{i+1}+\sum_{i=2}^{\infty}F_{i-1}\cdot z^{i+1}=z+z^{2}+\sum_{i=3}^{\infty}F_{i}\cdot z^{i}=\sum_{i=0}^{\infty}F_{i}\cdot z^{i}$$ - 證明:
$$f(x)=\frac{z}{1-z-z^{2}}=\frac{z}{(1-\phi z)(1+\hat{\phi}z)}=\frac{1}{\sqrt{5}}\cdot(\frac{1}{1-\phi z}-\frac{1}{1+\hat{\phi} z})$$
思路:將a中證明的式子變換就可以得到。
這一道題后面還有兩道證明做不出來。
思考題4-6 VLSI(超大規模集成)芯片測試。有 $n$ 個設計完全相同的芯片,有一部分次品(壞的芯片)。這些芯片可以兩兩相互測試,報告另一個芯片是好的或是壞的。好的芯片總是如實報告另一個芯片的好壞,而壞的芯片的報告結果不可控制,即:
- A報告:B是好的;B報告:A是好的。則都是好的或者都是壞的。
- A報告:B是壞的;B報告:A是壞的。則至少有一個壞的。
- A報告:B是好的;B報告:A是壞的。則至少有一個壞的。
- A報告:B是壞的;B報告:A是好的。則至少有一個壞的。
已知好的芯片的數量大於 $n/2$ ,給出代價為 $\Theta(n)$ 的算法,找出所有好的芯片。
思路:只要找到一個好芯片,就可以用代價 $\Theta(n)$ 找出所有的好芯片。找一個好芯片的過程是這樣:隨機兩兩組合成 n/2 組互相測試,將互相報告是好芯片(上面的第一種)這種情況的組合挑出來,將每個組合拆下一個芯片,單獨放在一起,構成原先 $n$ 芯片的一個子集(當子集的規模小於 $n/2$ 的時候,算法的代價才為 $\Theta(n)$)。對這個子集重復該方法,直到子集中只剩下一個芯片,該芯片就是好芯片。當然,這里忽略了 $n$ 為奇數的情況,那么就先假設 $n$ 是 $65536$ 之流吧。
需要證明的是這么幾點:首先,需要證明該子集非空,即能選出這樣的子集來:因為A中好芯片的數量大於 n/2 ,必定會出現好芯片和好芯片互相測試的組合,所以子集不可能為空;其次,需要證明子集的規模不大於 $n/2$ , 只有這樣算法的代價才能夠控制在 $\Theta(n)+\Theta(n/2)+...+\Theta(1)=\Theta(n)$ 以內:因為隨機選取一對芯片中的一個加入子集,即使在最極端的情況下,好芯片僅僅和好芯片組合,壞芯片和壞芯片組合並共同“欺騙”測試者互相報告為好芯片,那么也僅僅有 $n/2$ 個芯片入選;最后,需要證明子集中好芯片的數量仍然大於子集元素個數的一半,使該條件傳遞下去直到子集中僅有一個元素:考慮沒有被選入子集的芯片,要么是好芯片和壞芯片的組合,要么是壞芯片和壞芯片的組合,肯定沒有好芯片和好芯片的組合,所以沒有被選入子集的芯片中壞芯片的數量大於等於好芯片的數量,考慮到所有芯片中好芯片的數量大於壞芯片的數量,那么子集中好芯片的數量一定大於壞芯片的數量。
最后得到一個芯片,一定是好芯片,再用該芯片測試其他剩下的芯片,得到所有的好芯片。
思考題4-7 Monge矩陣。這道題比較長,難度不大,但結論卻很有趣,因此把結論寫下來,具體證明過程就略去。
一個 $m\times n$ 矩陣可以被稱為Monge矩陣,當且僅當:對於所有 $i,j,k,l$ 有 $1\leq i<k\leq m$ 和 $1\leq j<l\leq m$,則:$A[i,j]+A[k,l]\leq A[i,l]+A[k,j]$ 。下面是一個Monge矩陣:
$$\begin{matrix}10 & 17 & 13\\ 17 & 22 & 16\\ 24 & 28 & 22\\11 & 13 & 6\end{matrix}$$
直觀上,小的數分布在左上右下直線上,大的數分布在左下右上直線上。有這么幾個有趣的結論:
- 一個矩陣為Monge矩陣的充要條件是:對所有的 $i=1,2,...,m-1$ 和 $j=1,2,...,n-1$ ,有:$A[i,j]+A[i+1,j+1]\leq A[i,j+1]+A[i+1,j]$ 。即當且僅當矩陣中的每一個 $2\times 2$的小矩陣都為Monge矩陣時,整個大矩陣才而且一定為Monge矩陣。
- 認為 $f(i)$ 表示第 $i$ 行中最小的數的列索引,如上面的矩陣,$f(1)=1,f(2)=f(3)=f(4)=3$ 。則有 $f(i)$ 是非嚴格遞增函數,任意 $i<j$ 有 $f(i)<f(j)$ 。
- 從Monge矩陣中任意抽掉幾行或幾列,得到的新矩陣依然是Monger矩陣。這一個特性可以用來參加遞歸算法。
練習6.5-8 給出一個時間為 $O(n\lg k)$ 的算法,將 $k$ 個已排序的鏈表合並成一個排序鏈表的算法。$n$ 為輸出鏈表中元素的個數。(最小堆做k路鏈接)。
思路:類似於最小堆排序,構建一個最小堆,每個節點表示一個已排序鏈表,節點值就是鏈表的第一個值,根節點就是第一個元素最小的鏈表。從根節點鏈表中取出第一個元素放入輸出中,代價為 $O(1)$,這是最小的元素;然后根節點的值變大了,可能違反了最小堆性質,運行“保持最小堆性質”,代價為 $O(\lg k)$。每找出一個元素需要 $O(\lg k)+O(1)=O(\lg k)$ 代價,全部排序 $n$ 個元素需要的代價是 $O(n\lg k)$ 。
思考題6-2 對 $d$ 叉堆的分析。$d$ 叉堆和二叉堆類似,唯一的例外是每個非葉子節點有 $d$ 個子女而不是 $2$ 個。
- 如何在數組中表示 $d$ 叉堆(類似二叉堆)。我在草稿上找了半天規律,還是被卡住了……答案是:索引為 $i$ 的節點的第 $j$ 個孩子節點的索引為 $d\cdot (i-1)+j+1$,父節點的索引為 $(i-2)/d+1$ 。關鍵的思想就是:索引值為 $i$ 的節點的第 $j$ 個孩子節點,其前面的所有節點是:索引值為 $i$ 的節點的前面的所有節點($i-1$ 個)的子節點,加上自己的前 $j-1$ 個孩子節點,加上根節點。
- $d$ 叉堆的高度是?答案 $\lceil \log_{d}n\rceil$ 。
- 給出 $d$ 叉堆的Extract-Max,Insert,Increase-Key幾個實現。都很簡單,模仿二叉堆即可,只不過比較兄弟節點的時候要比較 $d$ 個節點。
思考題6-3 Young矩陣,也是一個難度不大但是有趣的問題。一個 $m\times n$ 的Young矩陣,每一行數據都是從左到右排序,每一列數據都是從上到下排序。矩陣中可以包含 $\infty$ 項。比如這樣一個矩陣:
$$\begin{matrix}0 & 0 & 1 & 5\\ 2 & 5 & 5 & \infty\\ 3 & \infty & \infty & \infty\end{matrix}$$
- 如果該矩陣 $A$ 中, $A[1,1]=\infty$ ,則矩陣的所有元素都是$\infty$ ,$A[m,n]\neq \infty$ ,則矩陣的所有元素都不是$\infty$。這幾乎是顯然的。
- Young矩陣和二叉堆很類似,可以實現Extract-Min算法和Insert算法。
Extract-Min算法:取出一個元素 $A[i,j]$ ,然后比較 $A[i+1,j]$ 和 $A[i,j+1]$ ,選取小的一個填充到 $A[i,j]$ 中,比如 $A[i+1,j]$ 。由於 $A[i+1,j]\geq A[i,j]$,$A[i,j]\geq A[i-1,j]$,$A[i,j]\geq A[i,j-1]$ ,所以新填入的元素滿足 $A_{new}[i,j]\geq A[i-1,j],A_{new}[i,j]\geq A[i,j-1]$ 。又因為 $A[i+1,j]\leq A[i,j+1]$ (之前比較過), $A[i+1,j]\leq A[i+2,j]$,后者即將成為 $A[i+1,j]$ ,所以滿足 $A_{new}[i,j]\leq A[i,j+1],A_{new}[i,j]\leq A_{new}[i+1,j]$ ,四個方向都滿足了循環不變式,再去對 $A[i+1,j]$ 執行這一套直到執行到遇到 $\infty$ 為止。從 $A[1,1]$ 開始執行就提取出了最小的元素。
Insert算法類似,只不過從 $A[m,n]$ 插入,遇到依次比較左邊和上方的數,將較大的一個“擠”下來,同樣可以證明循環不變式,跟Extract-Min基本一樣。 - 利用 $n\times n$ 的Young矩陣進行排序的代價為 $O(n^{3})$ 。獲取堆頂元素的代價為 $O(2n)$ ,共有 $n^{2}$ 個元素,代價為 $O(n^{3})$ 。注意這里實際上排了 $n^{2}$ 個元素,實際上排 $n$ 個元素的代價是 $O(n\sqrt{n})$ 。
- 在運行時間 $O(m+n)$ 內確定一個給定的數是否存在於矩陣中。從矩陣左下角開始,如果給定的數大就向右,否則向上。