数据结构与算法 | 记忆化搜索(Memorize Search)
在本系列的文章中已經(jīng)寫了二叉樹(Binary Tree)、深搜(DFS)與廣搜(BFS)、哈希表(Hash Table)等等,計(jì)劃接下來要寫的是動態(tài)規(guī)劃(Dynamic Programming,DP),它算得上是最靈活的一種算法。回憶筆者學(xué)習(xí)動態(tài)規(guī)劃的時候,最開始接觸的是經(jīng)典的 “01背包” 問題;不過現(xiàn)在想起來,以“01背包問題”作為初次接觸的動態(tài)規(guī)劃算法的問題_并不友好_;花費(fèi)了不少時間才慢慢感悟到動態(tài)規(guī)劃算法的核心思想。
先前的文章中涉及了不少搜索算法,在搜索算法上融入動態(tài)規(guī)劃算法思想的 記憶化搜索(Memorize Search)不妨是一個不錯的_承前啟后_的選擇。相對于 “01背包”類問題,記憶化搜索 對初學(xué)者 理解 動態(tài)規(guī)劃 更友好,也能更好的感受到其魅力所在。
記憶化搜索,所謂 “記憶” 引用 Geeksforgeeks 網(wǎng)站上介紹記憶搜索原文中一句話就是 “to transform the results of a function into something to remember.” 把函數(shù)的結(jié)果存儲下來作為 “記憶”。將“記憶”應(yīng)用于搜索算法上,也就是搜索到有記錄了函數(shù)結(jié)果的地方,其實(shí)就不需要再進(jìn)行函數(shù)計(jì)算,直接返回 “記憶” 的結(jié)果即可。
記憶化搜索是一種自頂向下(Top-Down)分析的算法,文字描述過于懸浮于理論,保持本系列文風(fēng)且用算法題來看下記憶化搜索算法具體的內(nèi)容。
自頂向下(Top-Down)
LeetCode 329. 矩陣中的最長遞增路徑【困難】
給定一個 m x n 整數(shù)矩陣 matrix ,找出其中 最長遞增路徑 的長度。
對于每個單元格,你可以往上,下,左,右四個方向移動。 你 不能 在 對角線 方向上移動或移動到 邊界外。(即不允許環(huán)繞)。
- 直接順著題意進(jìn)行分析,找到 “最長遞增路徑” 直接用搜索遍歷,把 matrix每個單元格的最長遞增路徑都計(jì)算下,返回其中最長的路徑。不妨就假設(shè)下有一個 search的函數(shù)能夠計(jì)算出 以當(dāng)前單元格為起點(diǎn)的最長遞增路徑長度。遍歷過程中 的最大長度 存儲再 max 中,最后返回即可,代碼如下:
// 假設(shè)中的函數(shù)
public int search(int[][] matrix,int x,int y){
int max;
return max;
}
public int longestIncreasingPath(int[][] matrix) {
int max = 0;
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
//(i,j)格的最長遞增路徑長度
max = Math.max(max,search(matrix,i,j));
}
}
return max;
}
- 問題開始關(guān)鍵點(diǎn)現(xiàn)在變成了 search 函數(shù)的實(shí)現(xiàn);接著順著題意分析,可以往上,下,左,右四個方向移動.首先考慮一個方向情況,只往上方向移動且可以往上移動(上方相鄰的單元格大于當(dāng)前單元格,遞增),那么此時 當(dāng)前單元格的最大路徑長度就是
上方單元格的最大路徑 + 1,其中1代表當(dāng)前單元格。
public int search(int[][] matrix,int x,int y){
int number = matrix[x][y],up = 1;
// 保障可以往“上”移動, x-1 沒有越邊界( x > 0 )
// 且是 遞增的 matrix[x-1][y] > number
if( x > 0 && matrix[x-1][y] > number ){
// 遞歸調(diào)用,同類子問題,(x-1,y)格的最長遞增路徑長度
up += search( matrix, x-1, y );
}
return up;
}
- 如此,擴(kuò)展到 四個方向,最終當(dāng)前單元格最大路徑就是 四個方向中取最大的返回。
public int search(int[][] matrix,int x,int y){
int number = matrix[x][y],up = 1,down =1,left=1,right=1;
if(x>0 && matrix[x-1][y] > number){
up += search(matrix,x-1,y);
}
if(x+1<matrix.length && matrix[x+1][y] > number){
down += search(matrix,x+1,y);
}
if(y+1<matrix[0].length && matrix[x][y+1] > number){
right += search(matrix,x,y+1);
}
if(y>0 && matrix[x][y-1] > number){
left += search(matrix,x,y-1);
}
return Math.max(Math.max(up,down),Math.max(right,left));
}
-
實(shí)現(xiàn)到這里按照搜索思路的算法已經(jīng)完成;不過會發(fā)現(xiàn)性能不高,分析過程會發(fā)現(xiàn)調(diào)用 search函數(shù) 時候,同樣一格位置會計(jì)算多次。此時聯(lián)系想想先前提到的 “記憶” ,把函數(shù)的結(jié)果存儲下來作為 “記憶”,也就是用 一個二維數(shù)組 cahce 緩存起來 已經(jīng)計(jì)算過單元的結(jié)果。
search 實(shí)現(xiàn)改為 (cache需要在 longestIncreasingPath 根據(jù) matrx大小 new下,這里略) ,代碼如下:
public int[][] cache = null;
public int search(int[][] matrix,int x,int y){
if(0 != cache[x][y])
return cache[x][y];
int number = matrix[x][y],up = 1,down =1,left=1,right=1;
if(x>0 && matrix[x-1][y] > number){
up += search(matrix,x-1,y);
}
if(x+1<matrix.length && matrix[x+1][y] > number){
down += search(matrix,x+1,y);
}
if(y+1<matrix[0].length && matrix[x][y+1] > number){
right += search(matrix,x,y+1);
}
if(y>0 && matrix[x][y-1] > number){
left += search(matrix,x,y-1);
}
// 存儲 “記憶”
cache[x][y] = Math.max(Math.max(up,down),Math.max(right,left));
return cache[x][y];
}
- 到此,已經(jīng)按照記憶化搜索算法思路完成了問題的解決。
回顧下記憶化搜索解題過程,我們是從算法問題出發(fā) -> 分析需要完成的計(jì)算(子問題)-> 進(jìn)一步進(jìn)行解決。這其實(shí)就是 自頂向下(Top-Down)的思考方式。
記憶化搜索 與 動態(tài)規(guī)劃
再來看"記憶",cache[x][y] 所記錄的是 x,y 這個單元格已經(jīng)計(jì)算過的 最大路徑長度。當(dāng)前單元格 的最大路徑長度使用上、下、左、右四個方向上的單元格 最大路徑長度 來進(jìn)行計(jì)算 ,使用"記憶" 其實(shí)就是在使用子問題的最優(yōu)解。
current_max = Max( up+1, down+1, left+1, right+1 )
另外一個角度描述,規(guī)劃決策 當(dāng)前單元格的最大路徑 是根據(jù) (上、下、左、右)相鄰四個方向上的單元格最大路徑 進(jìn)行的計(jì)算。相鄰四個方向上的最大路徑(子問題最優(yōu)解) 并非一開始靜態(tài)寫入下來,而是在程序運(yùn)行過程中至少計(jì)算一次存儲下來,可看作 動態(tài)的計(jì)算。根據(jù)動態(tài)計(jì)算子問題最優(yōu)結(jié)果來進(jìn)行規(guī)劃決策當(dāng)前最優(yōu)結(jié)果,也就是所謂 動態(tài)規(guī)劃(Dynamic Programming)的字面意思。
可以多體會下: 解決最優(yōu)化問題,根據(jù)子問題的最優(yōu)結(jié)果 規(guī)劃決策 -> 當(dāng)前問題最優(yōu)結(jié)果 -> 進(jìn)而求解最初問題。
所以有一種說法就是:記憶化搜索 是 動態(tài)規(guī)劃 自頂向下(Top-Down)分析的一種實(shí)現(xiàn)形式,通常用遞歸來實(shí)現(xiàn)。
最后總結(jié)本文
- 本系列文章中寫此篇 承前啟后的思考,記憶化搜索 的基本概念;
- 通過一道題演示 自頂向下(Top-Down)的分析,實(shí)際應(yīng)用記憶化搜索解決 具體算法問題;
- 解讀 記憶化搜索 與 動態(tài)規(guī)劃 的關(guān)系,以及動態(tài)規(guī)劃一些概念;
下一篇咱們再一起繼續(xù)解讀 動態(tài)規(guī)劃(Dynamic Programming) ,歡迎關(guān)注 Java研究者專欄、博客、公眾號等
總結(jié)
以上是生活随笔為你收集整理的数据结构与算法 | 记忆化搜索(Memorize Search)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GitHub 官方开源的字体集「GitH
- 下一篇: 【MySQL】MySQL中的锁