演算法導論 12 動態規劃 下

2022-02-13 13:14:57 字數 3686 閱讀 5305

這一篇主要關於最優二叉查詢樹的分析與實現,以及演算法導論動態規劃一章的幾道思考題。

最優二叉查詢樹,是經過優化的二叉查詢樹。在知曉每個鍵被查詢到的概率的情況下,生成一棵二叉查詢樹,使得查詢的期望深度最小。最優二叉查詢樹仍然保持二叉查詢樹的性質,所以查詢概率最大的鍵不一定是樹的跟。

形式地,給定由 $n$ 個互異關鍵字組成的有序序列 $k=\,k_,...k_\}$,對每個關鍵字 $k_$,被查詢的概率是 $p_$,對每兩個關鍵字 $k_$ 和 $k_$,查詢落在區間 $(k_, k_)$ (而查詢失敗的)的概率為 $q_$,構造一棵二叉查詢樹使一次查詢的期望深度 $e$ 最低。令序列 $d_$ 為虛擬鍵,表示區間 $(k_, k_)$ 。

查詢期望深度為:

$$e_=\sum_^(depth(k_)+1)\cdot p_+\sum_^(depth(d_)+1)\cdot q_$$

遞迴的,查詢期望深度也可以寫成:

$$e_=(e_+1)\cdot p_+(e_+1)\cdot p_+1\cdot p_$$

解決該問題的思路是:

序列 $k_...k_$ 的最優二叉查詢樹,就是以下這些查詢樹中,深度最小的一棵:

根據遞迴的查詢期望深度,對 $k_$ 為根的二叉查詢樹,如果它是最優二叉查詢樹,則兩個子樹也一定是最優二叉查詢樹。需要記錄的子問題就是,序列 $k_...k_$ 的最優二叉查詢樹深度,以及最優二叉查詢樹。在一張二維表中維護該深度,最後查詢 $k_...k_$ 的最有二叉查詢樹就可以。

我的實現如下:為了簡便,僅僅是求出了最優二叉查詢樹的深度,沒有真正去維護樹本身。

double getdepth(const std::vectordouble>>&depthtable,

const

inti,

const

intj)

else

}double getprob(const std::vector&p,

const std::vector&q,

const

inti,

const

intj)

double rslt = 0

;

for (int s=i; s<=j; s++)

rslt += q[j+1

];

return

rslt;

}void makeobst(const std::vectorstring>&k,

const std::vector&p,

const std::vector&q,

std::vector

double>>&depthtable,

std::vector

&treetable,

int i, int

j)

double mindepth =int_max;

for (int s=i; s<=j; s++)

}depthtable[i][j] =mindepth;

}void obst(const std::vectorstring>&k,

const std::vector&p,

const std::vector&q)

const

int n =k.size();

std::vector

treetable(n, std::vector

(n));

std::vector

double>> depthtable(n, std::vector(n, -1

)); makeobst(k, p, q, depthtable, treetable,

0, n-1

); std::cout

<0][n-1

];}

傳入資料 $p=\$ 和 $q=\$,輸出結果為 $2.75$,與演算法導論上相符。

思考題15-1雙調歐幾里得問題 歐幾里得旅行商問題是指平面上給定的 $n$ 個點,確定一條連線各點的最短閉合折線。這是乙個np完全問題。雙調歐幾里得問題對其進行了簡化,給出從 最左側的 點出發,嚴格從左至右經過各點,到達最右側的點,再從最右側的點嚴格從右至左回到最左側的點。下圖顯示了7點歐幾里得旅行商問題的解(左圖)以及雙調版本問題的解(右圖)。

思路:將所有點從左向右排序,為 $k_...k_$ ,該序列的兩個子集 $p_...p_$ 和 $q_...q_$,兩個子集的交集為只有 $\k_$和$k_$兩個元素。問題可以這樣考慮:最短路徑就是以下兩條路徑較短的一條:

可遞迴的並需要維護的子問題就是:一條路徑以 $k_$ 為終點,一條路徑以 $k_$ 為終點,路徑不交叉,不遺漏 $k_...k_$ 間的任意一點,的最短路徑之和。那就是以下兩條路徑中最短的(認為 $j>i$):

思考題15-2整齊列印 在印表機上整齊地列印乙個段落,段落由長度為 $l=\...l_\}$ 的 $n$ 個單詞組成,每一行至多輸入 $m$ 個字元。如果某一行包括了單詞 $l_...l_$,那麼行末的空餘字元格數為 $m-j+i-\sum_^l_$。要求整個段落行末多餘空格字元數的立方和最小。

思路:可遞迴的並需要維護的子問題就是:段落 $l_...l_$ 的整齊列印後的多餘空格字元數立方和。假設第一行最多列印 $s$ 個字元,那麼該子問題就的最優解就是如下數個問題中的最優的那個解:

整個問題就是求段落 $l_...l_$ 的整齊列印。

思考題15-4計畫公司聚會 乙個公司具有層次式結構,所有職員形成了一棵以總裁為根的樹。每乙個職員都有對聚會的喜愛程度(乙個實數),但每個職員都不想在聚會上遇到自己的直接上司。生成一張名單,使所有參與者對聚會喜愛程度的和最大,又避免任意兩位參與者是直接的上下級關係。

思路:需要維護的子問題是,以某個職員為根的樹,按照如上規則參加聚會的最大喜愛程度之和。為每個職員維護這個域。

當這個成員不是葉子是,而該成員喜愛度為負,問題的解同上一條中的第一小條。

最後考察以總裁為根的樹,即整個公司,參加聚會的成員。

思考題15-6在棋盤上移動 一張 $n×n$ 的方格期盼,棋子從頂邊移到底邊,只能直行,斜行,類似於西洋棋中的小卒(只不過是從底邊開始,而且斜行並不一定要吃掉棋子)。沒走一次都會獲得一筆金錢,選擇合適的底邊格仔,完成上述移動過程(移動到頂邊),並盡可能多地收集金錢。

思路:很簡單,為每個格仔維護乙個域,表示從這個格仔開始移動到頂邊獲取的最大金錢數。頂邊上都是0,從頂邊開始逐行生成,直到底邊。在底邊選取最大的域就可以了。

思考題15-7最高效益排程 一台機器,在此機器上可處理 $n$ 個作業 $a_,a_...a_$ ,每個左右有乙個處理時間 $t_$,以及最後期限 $d_$ 和 效益 $p_$。假設處理時間都是整數,如何安排處理哪些作業,使效益最高。

思路:所有作業的最後期限,和開始作業的最早時間,之間的時間段,分割成整數段 $0~m$(每一段對應於處理時間中整數的單位)。然後為 $0...m$ 中的每個點 $i$ 維護乙個域,表示 $0...i$ 時間段的最高效益及其作業分配情況。

總算有點明白了「動態規劃」四個字的含義。我想,如果將遞迴版本的演算法全部展開為非遞迴的,那麼大概可以看到,演算法在不斷產生區域性子問題的最優解,並從最優解中尋找較大子問題的最優價,所有的子問題最優解都被妥善地維護起來,而不是立刻拋棄,這樣下次遇到該子問題就可以直接使用結果了。

演算法導論 動態規劃

動態規劃這個演算法,我一直都搞不明白,也許因為我數學能力太差的緣故,總是不得其要領,每次學習這個演算法的時候,總是不知道所謂的狀態轉移方程到底是怎麼樣推導出來的。其實就在我寫這篇部落格的時候,我依然不清楚。什麼問題能用動態規劃來解決呢?動態規劃問題的特徵就是最優子結構,乙個遞迴結構 該問題需要求乙個...

演算法導論 2 動態規劃

動態規劃與分治方法類似,都是通過組合子問題的解來求解原問題 需要注意的是,動態規劃 dynamic programming 這裡的programming並不是指編寫電腦程式,而是指一種 法 分治方法將問題劃分為互不相交的子問題,遞迴地求解子問題,再將它們的解組合起來,求出原問題的解 動態規劃也是通過...

動態規劃 鋼條切割《演算法導論》

給定各長度鋼鐵單位 以及乙個長度為n的鋼條,求最大效益 1 10 分別為p include includeint memorized cut rod aux int p,int len,int r 樸素遞迴演算法 演算法複雜度為2 n級這裡寫 int cut rod int p,int len in...