動態規劃算法
人工智能時代,各國都在大力研究機器人技術,也製造出各種各樣的機器人,比如:爲了解決男女失衡而製造 的美女機器人,假如你參與了某美女機器人的研發,你在這個項目中要求實現一個統計算法:如果美女機器人 一次可以上 1 級臺階,也可以一次上 2 級臺階。求美女機器人走一個 n 級臺階總共有多少種走法。
咋一看,無從下手,不急,我們不是講了分治法嘛?這不是可以乘機表演一下? 啓發性思考:
分治法核心思想: 從上往下分析問題,大問題可以分解爲子問題,子問題中還有更小的子問題 比如總共有 5 級臺階,求有多少種走法;由於機器人一次可以走兩級臺階,也可以走一級臺階,所以我們可以分成兩個情況
1、機器人最後一次走了兩級臺階,問題變成了“走上一個 3 級臺階,有多少種走法?”
2、機器人最後一步走了一級臺階,問題變成了“走一個 4 級臺階,有多少種走法?”
我們將求 n 級臺階的共有多少種走法用 f(n) 來表示,則
f(n) = f(n-1) + f(n-2);
由上可得:
f(5) = f(4) + f(3);
f(4) = f(3) + f(2);
f(3) = f(2) + f(1);
邊界情況分析
走一步臺階時,只有一種走法,所以 f(1)=1
走兩步臺階時,有兩種走法,直接走 2 個臺階,分兩次每次走 1 個臺階,所以 f(2)=2
走兩個臺階以上可以分解成上面的情況
這符合我們講解的分治法的思想: 分而治之
參考:
#include <stdio.h>
#include <stdlib.h>
/***********************************
* 遞歸實現機器人臺階走法統計
* 參數:
* n - 臺階個數
* 返回: 上臺階總的走法
*
***********************************/
int WalkCout(int n)
{
if (n < 0) return 0;
if (n == 1) /* 一級臺階, 一種走法 */
{
return 1;
}
else if (n == 2) /* 二級臺階, 二種走法 */
{
return 2;
}
else /* n 級臺階, n-1個臺階走法 + n-2 個臺階的走法 */
{
return WalkCout(n - 1) + WalkCout(n - 2);
}
}
int main()
{
int n = 0;
printf("請輸入臺階數: ");
scanf_s("%d", &n);
printf("臺階 %d 有:%d種走法\n", n, WalkCout(n));
system("pause");
return 0;
}
用圖理解一下
但是,如果細心的朋友是否會注意到,上面的代碼中存在很多重複的計算?
比如: f(5) = f(4) + f(3) 計算分成兩個分支:
f(4) = f(3)+f(2) = f(2) + f(1) + f(2);
f(3) = f(2) + f(1);
這裏可以觀察出用一些計算重複了非常多遍, 因此要用其他方法才行!!!
運行環境: vs 2019
運行結果:
有沒有辦法避免重複計算的部分?
其實我們可以從下向上分析推斷問題。
f(1) = 1
f(2) = 2
f(3) = f(1) + f(2) = 3
f(4) = f(3) + f(2) = 3 + 2 = 5
f(5) = f(4) + f(3) = 5 + 3 = 8
。。。依次類推 。。。
參考:
#include <stdio.h>
#include <stdlib.h>
/***********************************
* 遞歸實現機器人臺階走法統計
* 參數:
* n - 臺階個數
* 返回: 上臺階總的走法
*
***********************************/
int WalkCout(int n)
{
if (n < 0) return 0;
if (n == 1) /* 一級臺階, 一種走法 */
{
return 1;
}
else if (n == 2) /* 二級臺階, 二種走法 */
{
return 2;
}
else /* n 級臺階, n-1個臺階走法 + n-2 個臺階的走法 */
{
return WalkCout(n - 1) + WalkCout(n - 2);
}
}
int WalkCout2(int n)
{
int ret = 0;
if (n <= 0) return 0;
if (n == 1) return 1;
if (n == 2) return 2;
/* 數組存儲走n個臺階的走法數 */
int* value = new int[n + 1];
value[0] = 0;
value[1] = 1;
value[2] = 2;
for (int i = 3; i <= n; i++)
{
value[i] = value[i - 1] + value[i - 2];
}
ret = value[n];
delete []value;
return ret;
}
int main()
{
int n = 0;
printf("請輸入臺階數: ");
scanf_s("%d", &n);
for (int i = 0; i < n; i++)
{
printf("臺階 %d 有:%d種走法\n", i, WalkCout2(i));
}
system("pause");
return 0;
}
運行環境: vs 2019
運行結果:
輸入100
注意溢出
這就是動態規劃法 !!!
動態規劃也是一種分治思想,但與分治算法不同的是,分治算法是把原問題分解爲若干子問題, 自頂向下,求解各子問題,合併子問題的解從而得到原問題的解。動態規劃也是自頂向下把原問 題分解爲若干子問題,不同的是,然後自底向上,先求解最小的子問題,把結果存儲在表格中, 在求解大的子問題時,直接從表格中查詢小的子問題的解,避免重複計算,從而提高算法效率。
什麼時候要用動態規劃?
如果要求一個問題的最優解(通常是最大值或者最小值),而且該問題能夠分解成若干個子問題, 並且小問題之間也存在重疊的子問題,則考慮採用動態規劃。
怎麼使用動態規劃?
五步曲解決:
- 判題題意是否爲找出一個問題的最優解
- 從上往下分析問題,大問題可以分解爲子問題,子問題中還有更小的子問題
- 從下往上分析問題 ,找出這些問題之間的關聯(狀態轉移方程)
- 討論底層的邊界問題
- 解決問題(通常使用數組進行迭代求出最優解)
課後習題:
給你一根長度爲 n 的金條,請把金條剪成 m 段 (m 和 n 都是整數,n>1 並且 m>1)每斷金條的 長度記爲 k[0],k[1],…,k[m].請問 k[0] k[1]…*k[m]可能的最大乘積是多少?
參考:
#include <stdlib.h>
#include <stdio.h>
int demo1(int length)
{
if (length < 2) return 0;
if (length == 2) return 1;
if (length == 3) return 2;
//定義一個存放長度的數組
int* products = new int[length + 1];
//以下的前三個數組存放的不是最大值,而是長度值
products[1] = 1;
products[2] = 2;
products[3] = 3;
int maxModify = 0;
for (int i = 4; i <= length; i++)
{
/* i/2 可以有可以 i 節約(cpu)重複循環次數但對結果沒有影響 */
for (int j = 1; j <= i / 2; j++)
{
int product = products[j] * products[i - j];
if (product > maxModify)
{
maxModify = product;
}
}
//得到f(i)的最優解
products[i] = maxModify;
}
//返回發f(n)
return products[length];
delete[]products;
}
int main()
{
int length = 10;
printf("%d\n", demo1(length));
system("pause");
return 0;
}
其實百度也有, java的但換湯不換藥, 其實我也不會(掌握思想)做但我把他變成C/C++, 對動態規劃算法更深入的理解, 就是這位大佬講解:(非常細哦!!!)
https://blog.csdn.net/u012429555/article/details/83184146
結語:
學到的知識要, 多複習, 多總結, 多敲. 需要時間的積累, 才能引起質的改變. 自己寫不出來的永遠是別人的.
分享一下我的技巧: 代數法把具體的數字帶進去, 看看能能能找到規律(掌握思想).
還有就是畫圖, 也很重要. 用筆畫出來, 把數代進去, 方法雖然笨, 但真的很實用, 好記憶不如爛筆頭!!!
我是小白, C/C++功力…, 你懂得, 寫的文章可能不是很好. 如果存在問題, 歡迎大神給予評判指正.
錯了不可怕, 可怕的是找不出bug, 誰沒錯過!!!
最近學操作系統我認爲, 學什麼都要成本(時間), 即使它是免費的, 我個人認爲要挑來學, 挑重點來學, 而不是從頭到尾, 除非考試考研.
今日是: 2020年5月11日, (由於疫情的原因), 家裏的很多弟弟現在纔會學校, 只有兩位姐姐和我老表. 寫博客,也可自己加強記憶,就當寫寫日記吧!!!
希望給個贊: 反正你又不虧, 順便而已