C/C++_五大常規算法_動態規劃算法

動態規劃算法

人工智能時代,各國都在大力研究機器人技術,也製造出各種各樣的機器人,比如:爲了解決男女失衡而製造 的美女機器人,假如你參與了某美女機器人的研發,你在這個項目中要求實現一個統計算法:如果美女機器人 一次可以上 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
注意溢出
在這裏插入圖片描述

這就是動態規劃法 !!!

動態規劃也是一種分治思想,但與分治算法不同的是,分治算法是把原問題分解爲若干子問題, 自頂向下,求解各子問題,合併子問題的解從而得到原問題的解。動態規劃也是自頂向下把原問 題分解爲若干子問題,不同的是,然後自底向上,先求解最小的子問題,把結果存儲在表格中, 在求解大的子問題時,直接從表格中查詢小的子問題的解,避免重複計算,從而提高算法效率。

什麼時候要用動態規劃?
如果要求一個問題的最優解(通常是最大值或者最小值),而且該問題能夠分解成若干個子問題, 並且小問題之間也存在重疊的子問題,則考慮採用動態規劃。

怎麼使用動態規劃?
五步曲解決:

  1. 判題題意是否爲找出一個問題的最優解
  2. 從上往下分析問題,大問題可以分解爲子問題,子問題中還有更小的子問題
  3. 從下往上分析問題 ,找出這些問題之間的關聯(狀態轉移方程)
  4. 討論底層的邊界問題
  5. 解決問題(通常使用數組進行迭代求出最優解)

課後習題:

給你一根長度爲 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日, (由於疫情的原因), 家裏的很多弟弟現在纔會學校, 只有兩位姐姐和我老表. 寫博客,也可自己加強記憶,就當寫寫日記吧!!!

希望給個贊: 反正你又不虧, 順便而已

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章