通常來講,動態規劃總要遍歷全部的狀態,而搜索能夠排除一些無效狀態。更重要的是搜索還能夠剪枝,可能剪去大量沒必要要的狀態,所以在空間開銷上每每比動態規劃要低不少。算法
如何協調好動態規劃的高效率與高消費之間的矛盾呢?有一種折中的辦法就是記憶化算法。記憶化算法在求解的時候仍是按着自頂向下的順序,每求解一個狀態,就將它的解保存下來,之後再次遇到這個狀態的時候,就沒必要從新求解了。這種方法綜合了搜索和動態規劃兩方面的優勢,於是仍是頗有使用價值的。數組
舉一個例子:如右圖所示是一個有向無環圖,求從頂點1到頂點6的最長路徑。(規定邊的方向從左到右)函數
咱們將從起點(頂點1)開始到某個頂點的最長路徑做爲狀態,用一維數組opt記錄。Opt[j]表示由起點到頂點j時的最長路徑。顯然,opt[1]=0,這是初始狀態,即動態規劃的邊界條件。因而,咱們很容易地寫出狀態轉移方程式:opt[j]=max{opt[k]+a[k,j]}(k到j有一條長度爲a[k,j]的邊)。雖然有了完整的狀態轉移方程式,可是仍是不知道動態規劃的順序。因此,還須要先進行一下拓撲排序,按照排序的順序推下去,opt[6]就是問題的解。spa
能夠看出,動態規劃相比搜索之因此高效,是由於它將全部的狀態都保存了下來。當遇到重複子問題時,它不像搜索那樣把這個狀態的最優值再計算一遍,只要把那個狀態的最優值調出來就能夠了。例如,當計算opt[4]和opt[5]時,都用到了opt[3]的值。由於已經將它保存下來了,因此就沒有必要再去搜索了。code
可是動態規劃仍然是有缺點的。一個很突出的缺點就是要進行拓撲排序。這道題的拓撲關係是很簡單的,但有些題的拓撲關係是很複雜的。對於這些題目,若是也進行拓撲排序,工做量很是大。遇到這種狀況,咱們能夠用記憶化搜索的方法,避免拓撲排序。blog
【例】滑雪排序
【問題描述】遞歸
小明喜歡滑雪,由於滑雪的確很刺激,但是爲了得到速度,滑的區域必須向下傾斜,當小明滑到坡底,不得再也不次走上坡或等着直升機來載他,小明想知道在一個區域中最長的滑坡。滑坡的長度由滑過點的個數來計算,區域由一個二維數組給出,數組的每一個數字表明點的高度。下面是一個例子:it
1 2 3 4 5io
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
一我的能夠從某個點滑向上下左右相鄰四個點之一,當且僅當高度減少,在上面的例子中,一條可行的滑坡爲25-24-17-16-1(從25開始到1結束),固然25-24……2…1更長,事實上這是最長的一條。
【輸入格式】
輸入的第一行爲表示區域的二維數組的行數R和列數C(1≤R、C≤100),下面是R行,每行有C個數表明高度。
【輸出格式】
輸出區域中最長的滑坡長度。
【輸入樣例】ski.in
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
【輸出樣例】ski.out
25
【算法分析】
因爲一我的能夠從某個點滑向上下左右相鄰四個點之一,如上圖所示。當且僅當高度減少,對於任意一個點[i,j],當它的高度小於與之相鄰的四個點([i-1,j], [i,j+1], [i+1,j], [i,j-1])的高度時,這四個點能夠滑向[i,j],用f[i,j]表示到[i,j]爲止的最大長度,則f[i,j]=max{f(i+a,j+b)}+1,其中座標增量{(a,b)=[(1,0),(-1,0),(0,1),(0,-1)],0<i+a<=r,0<j+b<=c,High[i,j]<High[i+a,j+b]}。爲了保證知足條件的f[i+a,j+b]在f[i,j]前算出,須要對高度排一次序,而後從大到小規劃(高度)。最後再比較一下全部f(i,j){0<i≤r,0<j≤c},找出其中最長的一條路線。咱們還能夠用記憶化搜索的方法,它的優勢是不需進行排序,按照行的順序,利用遞歸逐點求出區域中到達此點的最長路徑,每一個點的最長路徑只求一次。
1 const
2 dx:array[1..4] of shortint=(0,-1,0,1); {x的座標增量}
3 dy:array[1..4] of shortint=(-1,0,1,0); {y的座標增量}
4 var
5 r,c,ans,anss:longint; 6 map,f:array[1..100,1..100] of longint; 7 procedure init; 8 var i,j:longint; 9 begin
10 readln(r,c); 11 for i:=1 to r do
12 for j:=1 to c do
13 read(map[i,j]); {讀入每一個點的高度}
14 ans:=0; anss:=0; 15 fillchar(f,sizeof(f),0); 16 end; 17 function search(x,y:longint):longint; {函數的做用是求到[x,y]點的最長路徑}
18 var i,j,nx,ny,tmp,t:longint; 19 begin
20 if f[x,y]>0 then {此點長度已經求出,沒必要進行進一步遞歸,保證每個點的最大長度只求一次,這是記憶化搜索的特色}
21 begin
22 search:=f[x,y]; exit; 23 end; 24 t:=1; 25 for i:=1 to 4 do {從四個方向上搜索能達到[x,y]的點}
26 begin
27 nx:=x+dx[i]; ny:=y+dy[i]; {新座標}
28 if (1<=nx)and(nx<=r) and (1<=ny)and(ny<=c) {邊界限制}
29 and (map[nx,ny]>map[x,y]) {高度比較}
30 then
31 begin
32 tmp:=search(nx,ny)+1; {遞歸進行記憶化搜索}
33 if tmp>t then t:=tmp; 34 end; 35 end; 36 f[x,y]:=t; 37 search:=t; 38 end; 39 procedure doit; 40 var i,j:longint; 41 begin
42 for i:=1 to r do {按照行的順序,利用遞歸逐點求出區域中到達此點的最長路徑}
43 for j:=1 to c do
44 begin
45 anss:=search(i,j); 46 //f[i,j]:=anss; 47 if anss>ans then ans:=anss; {尋找最大長度值}
48 end; 49 end; 50 procedure outit; 51 var i,j:longint; 52 begin
53 {for i:=1 to r do begin 54 for j:=1 to c do 55 write(f[i,j],' '); writeln; end;}
56 writeln(ans); 57 end; 58 begin
59 init; 60 doit; 61 outit; 62 end.