以下這道題通過一步一步的分析優化可以看出暴力搜尋方法,記憶搜尋方法,動態規劃方法的優化過程,往往寫出暴力搜尋方法是比較容易的,這樣一步步的分析可以更好的理解動態規劃方法。
題目:給定陣列arr,arr中所有的值都為正數且不重複。每個值代表一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定乙個整數aim代表要找的錢數,求換錢有多少種方法。
令arr=,aim=1000。
過程:用0張5元,讓去組成剩下的1000,方法記作res1
用1張5元,讓去組成剩下的995,方法記作res2
用2張5元,讓去組成剩下的990,方法記作res3
……………………
用200張5元,讓去組成剩下的0,方法記作res201
這樣得到的每個結果,再用其他面值的貨幣去組成剩下的錢,利用遞迴,算出每個結果下剩下的錢有多少種組合,再把這201個結果的組合數相加得到最終的結果數。
**如下:
public int coins1(int arr, int aim)以上就是遞迴得出結果。暴力搜尋演算法之所以暴力,就是需要重複之前的計算,之前已經計算過的,在後面的還要再計算一遍,比如使用0張5元和1張10元的情況下,那麼後續process1方法的引數將是process1(arr,2,990),當用2張5元和0張10元的時候後續還是要計算process1(arr,2,990),因為錢數是固定的,用不同種的組合會有相同的條件下出現。這樣會多出很多不必要的過程,非常影響效率。return process1(arr, 0, aim);
}public int process1(int arr, int index, int aim) else
}return res;
}
記憶搜尋方法:
之前我們進行計算的時候,process1中的引數,arr是不變的,只有index和aim在變,之所以暴力方法的時間長,是因為計算了很多重複的資料,既然要優化,減少複雜度,就要程式減少或者不進行重複的計算,只需要把之前計算的結果記錄下來,這樣後面再計算的時候,先進行判斷,是否之前計算過,如果沒有計算過,再進行遞迴計算,這樣減少了很多的遞迴過程,可以提高很多效率。新增乙個二維陣列,這個二維陣列記錄的就是process2中變化的後面兩個引數,把計算過的結果存在裡面,後續的計算前,先進行判斷。
public int coins2(int arr, int aim)簡單的說暴力搜尋演算法和記憶搜尋演算法,記憶搜尋之所以比暴力快,因為記憶搜尋演算法把之前計算過的過程記下來了,有就直接用,沒有就算int map = new int[arr.length + 1][aim + 1];
return process2(arr, 0, aim, map);
}public int process2(int arr, int index, int aim, int map) else else }}
map[index][aim] = res == 0 ? -1 : res;
return res;
}
記憶搜尋方法就是動態規劃的一種, 記憶搜尋方法知識記錄哪些過程計算過,哪些沒有計算過,而動態規劃方法是記錄下來了每個計算的路徑,怎麼計算出這個的,後面的計算都會用到之前的計算過程。動態規劃規定了計算順序,而記憶化搜尋方法不規定順序,只關心結果
我們用乙個二維陣列(矩陣)dp記錄,dp[i][j]表示使用arr[0……i]組成總數為j的方法數,dp矩陣的求法和分析如下:
對於矩陣dp第一列dp[i][0],表示的是組成錢的總數為0的組合數,只有一種,什麼也不用,dp矩陣的第一列的值都設定為1
對於矩陣dp第一行dp[0][i],表示的是用陣列中下標為0的元素,可以組合成什麼樣的錢數,陣列中第乙個元素是5,很明顯可以組成5,10,15……,5的倍數,這樣把成倍數關係的位置也設定為1,也就是dp[0][arr[0] * j] = 1,j是滿足大於0,arr[0]*j小於等於錢的總數
對於剩下的任意dp[i][j],我們依次從左到右,從上到下計算,dp[i][j]的值可能來自下面:
public int coins3(int arr, int aim)但是還有乙個問題,實際執行就會發現,其實我們每次求得方法數還是把之前的運算結果列舉出來,在實際的執行時間上也可以看出來實際上時間並沒有減少int dp = new int[arr.length][aim+1];
for (int i = 0; i < arr.length; i++)
for (int j = 1; arr[0] * j <= aim; j++)
int num = 0;
for (int i = 1; i < arr.length; i++)
dp[i][j] = num;}}
return dp[arr.length-1][aim];
}
我們接著優化其實我們發現要計算dp[i][j]的值就是dp[i][j-arr[i]]+dp[i-1][j],
這樣我們可以通過前面的結果直接可以得到最後的結果,沒有必要再去挨個求
public int coins3(int arr, int aim)只是改變了一點,執行時間上已經可以達到1msint dp = new int[arr.length][aim+1];
for (int i = 0; i < arr.length; i++)
for (int j = 1; arr[0] * j <= aim; j++)
int num = 0;
for (int i = 1; i < arr.length; i++)
}return dp[arr.length-1][aim];
}
動態規劃把狀態的計算順序規定了,所以較記憶搜尋方法比動態規劃時間複雜度大
面對暴力搜尋的優化:先用常見的遞迴方法寫出暴力搜尋方法,看暴力搜尋方法中得到值的哪些可以記錄下來,代表遞迴過程的引數,得到記憶搜尋方法。
動態規劃自我感覺真的好難,其實最好想的就是記憶搜尋方法,接著看看哪些可以一步步的優化
參考:程式設計師**面試指南--左程雲
演算法 記憶化搜尋演算法
記憶化搜尋實際上是遞迴來實現的,但是遞迴的過程中有許多的結果是被反覆計算的,這樣會大大降低演算法的執行效率。而記憶化搜尋是在遞迴的過程中,將已經計算出來的結果儲存起來,當之後的計算用到的時候直接取出結果,避免重複運算,因此極大的提高了演算法的效率。對於乙個遞迴函式w a,b,c 這是個簡單的遞迴函式...
非常暴力的搜尋演算法 深度優先搜尋演算法
第一題 有1 9 9個數,選取3個數組成以下格式 數字只能用一次,不能重複 有1 9 9個數,選取3個數組成以下格式 數字只能用一次,不能重複 public class demo1 第二題 有1 9 9個數,選取3個數組成以下格式 數字只能用一次,不能重複 如 12 35 47 public cla...
A 搜尋演算法
啟發式搜尋演算法 要理解 a 搜尋演算法,還得從啟發式搜尋演算法開始談起。所謂啟發式搜尋,就在於當前搜尋結點往下選擇下一步結點時,可以通過乙個啟發函式 來進行選擇,選擇代價最少的結點作為下一步搜尋結點而跳轉其上 遇到有乙個以上代價最 少的結點,不妨選距離當前搜尋點最近一次展開的搜尋點進行下一步搜尋 ...