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