日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

数据结构与算法、讲解、动态规划一脸懵?看完之后轻松掌握!

發(fā)布時(shí)間:2024/8/23 编程问答 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据结构与算法、讲解、动态规划一脸懵?看完之后轻松掌握! 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

來(lái)源 | 昊天碼字

責(zé)編 | Carol

封圖?| CSDN 付費(fèi)下載于視覺(jué)中國(guó)

碰到動(dòng)態(tài)規(guī)劃問(wèn)題摸不著頭腦?總結(jié)不出動(dòng)態(tài)規(guī)劃的類(lèi)型?有多少人曾經(jīng)歷過(guò)這種迷茫與無(wú)助?看完本文,讓你一腳邁進(jìn)動(dòng)態(tài)規(guī)劃的大門(mén)。

我們?cè)谟眠f歸求解問(wèn)題的過(guò)程中,可能會(huì)存在將遞歸的某一環(huán)節(jié)計(jì)算多次的現(xiàn)象,因?yàn)橹暗倪f歸結(jié)果無(wú)法有效存儲(chǔ),下次碰到一樣的值還需要重新計(jì)算一次,下面引出一個(gè)例子:

相信大家都知道斐波那契數(shù)列,那我們由斐波那契數(shù)列遞歸求解過(guò)程中帶來(lái)的問(wèn)題來(lái)引出動(dòng)態(tài)規(guī)劃問(wèn)題。

斐波那契數(shù)列

斐波那契數(shù)列的規(guī)律為:當(dāng)n > 2時(shí),fib(n)=fib(n-1)+fib(n-2),當(dāng)n為1或2時(shí),fib(n)=1。假如我們讓電腦代入這個(gè)遞歸式來(lái)求fib(5),則求解過(guò)程如下圖:

求fib(5)

我們可以看到,如果我們按照如上的遞歸式來(lái)求一個(gè)斐波那契數(shù),那么當(dāng)n很大時(shí),這棵樹(shù)會(huì)非常龐大,且從中我們也可看出,電腦會(huì)非常傻的把fib(3)算了兩遍,這種問(wèn)題叫做重疊子問(wèn)題,所以這個(gè)算法的時(shí)間復(fù)雜度會(huì)達(dá)到O(2^n),但實(shí)際上我們沒(méi)有必要把fib(3)算兩遍,我們?cè)谒愕谝槐闀r(shí)把它保存起來(lái),在下次需要時(shí)直接調(diào)用就可以了。

因此我們?cè)谟?jì)算fib(5)時(shí),完全可以從fib(1)開(kāi)始計(jì)算,從1一直算到5,并不斷保存,在必要時(shí)調(diào)用之前保存的值,而不需要重新運(yùn)算,這樣算法的時(shí)間復(fù)雜度瞬間就降到O(n)了。

所以我們?cè)谧鰟?dòng)態(tài)規(guī)劃時(shí),首先要用到的就是這個(gè)思想,可以看出,動(dòng)態(tài)規(guī)劃的特點(diǎn)之一就是開(kāi)辟內(nèi)存保存數(shù)值,那么我們可以根據(jù)開(kāi)辟內(nèi)存的方式分為一維動(dòng)態(tài)規(guī)劃問(wèn)題和二維動(dòng)態(tài)規(guī)劃問(wèn)題,下面我們來(lái)具體介紹。

第一類(lèi)問(wèn)題(一維)

1.求薪水問(wèn)題

我們來(lái)看下面的圖,從上到下依次為8種工作,橫軸代表工作所占的時(shí)間段,紅色字體代表每份工作的薪水,兩份同一時(shí)間的工作不能同時(shí)做,我們求的問(wèn)題是一個(gè)員工怎樣在0到11的時(shí)間段內(nèi)獲取最大的薪水。

我們首先思考這道題的本質(zhì),就是選不重合的工作,求能最多得到的薪水,我們先建立一個(gè)數(shù)組workvalue來(lái)保存每份工作的薪水。

我們先按照開(kāi)辟內(nèi)存的思想來(lái)做這道題,之后我會(huì)說(shuō)明為什么要開(kāi)辟內(nèi)存。由上文的動(dòng)態(tài)規(guī)劃規(guī)律可知,我們可以依次從只做前一個(gè)工作能得到的最大薪水、只做前二個(gè)工作能得到的最大薪水、只做前三個(gè)工作能得到的最大薪水來(lái)思考,并建立一個(gè)value數(shù)組來(lái)保存它們,假如我們想求前6個(gè)工作中選哪些工作才能賺的最多,那么我們可以有兩個(gè)選擇:做或者不做這份工作,第一種,假如我們選擇第6份工作,我們先找到最近的一個(gè)與第6份工作時(shí)間不重合的工作(第2份工作),那最大薪水為做第6份工作得到的錢(qián)加上做前2個(gè)工作能得到的最大的薪水(本題就是第6份工作的薪水3+value[2]);第二種,我們可以選擇不做第6份工作,那么最大的薪水就是做前5個(gè)工作得到的最大的薪水。因此:

value[6]=max(3+value[2],value[5]);

我們可以看出,如果我們不開(kāi)辟數(shù)組,用遞歸的思想接著求value[2],那么不可避免地計(jì)算了重復(fù)子問(wèn)題,因此我們選擇開(kāi)辟數(shù)組,從前往后用動(dòng)態(tài)規(guī)劃的思想做。此外,我們還應(yīng)設(shè)立pre數(shù)組來(lái)存每個(gè)工作的前一個(gè)與之不重合的工作,例如pre[6]=2。因此我們可以寫(xiě)出如下代碼來(lái)解決上述問(wèn)題:

#include <iostream> using namespace std; int value[10]; int workvalue[8] = {5, 1, 8, 4, 6, 3, 2, 4}; int pre[8] = {0, 0, 0, 1, 0, 2, 3, 5}; int main() {value[0] = 0;value[1] = 5;value[2] = 5;for (int i = 3; i <= 8; i++) {value[i] = max(value[i - 1], value[pre[i - 1]] + workvalue[i - 1]);}cout << value[8];return 0; }

求出結(jié)果等于13,即最多的薪水。那么這道問(wèn)題解決,鑒于如果此題換個(gè)衣服大家可能會(huì)不認(rèn)識(shí)的現(xiàn)象,我們接下來(lái)再搞一道同類(lèi)型不同描述的問(wèn)題。

2.求最大數(shù)

我們可以在如下arr數(shù)組中的數(shù)字中可以任意選數(shù)字,但是不能選相鄰的數(shù)字,輸出我們選的數(shù)字中和最大的值。

arr數(shù)組

我們可以一眼看出,此題同樣是個(gè)選擇與否的問(wèn)題,不能選相鄰的數(shù)就如求薪水問(wèn)題中的不能同時(shí)做兩份工作,我們先來(lái)試試遞歸,用遞歸從后往前來(lái)做,我們?cè)O(shè)opt(n)是前n個(gè)數(shù)中選擇的數(shù)相加最大的值,那么當(dāng)我們考慮opt(n)時(shí),我們有兩種選擇:選或者不選數(shù)組n位置所在的數(shù),如果選擇n,那它的值就為arr[n]+opt[n-2],如果不選擇n,那它的值就為opt[n-1]。而opt(n)就是兩種選擇中的最大值。假如我們求opt(6),那么就如下圖所示:

遞歸思路

那遞歸出口就是opt(1)=arr[1],opt(2)=max(arr[2],opt(1)),因此我們可以得出如下代碼(在此代碼中由于數(shù)組從0開(kāi)始,因此在相應(yīng)的地方-1):

#include <iostream> using namespace std; int arr[7] = {1, 2, 4, 1, 7, 8, 3};int rec(int arr[], int i) {if (i == 0) return arr[0];else if (i == 1) return max(arr[0], arr[1]);else {int A = rec(arr, i - 2) + arr[i];int B = rec(arr, i - 1);return max(A, B);} } int main() {cout << rec(arr, 6); }

毫無(wú)疑問(wèn)我們也計(jì)算了重疊子問(wèn)題,由上面的動(dòng)態(tài)規(guī)劃可知,我們可以通過(guò)創(chuàng)建數(shù)組來(lái)保存相應(yīng)的值,以此減少不必要的計(jì)算,因此,動(dòng)態(tài)規(guī)劃的代碼如下:

#include <iostream> using namespace std; int arr[7] = {1, 2, 4, 1, 7, 8, 3}; int opt[7];int rec(int arr[], int j, int opt[]) {opt[0] = arr[0];opt[1] = max(arr[0], arr[1]);for (int i = 2; i < 7; i++) {int A = opt[i - 2] + arr[i];int B = opt[i - 1];opt[i] = max(A, B);}return opt[j]; } int main() {cout << rec(arr, 6, opt); }

好,我們來(lái)思考,開(kāi)辟一維內(nèi)存的原因是其中只能找到一個(gè)變量,如前n個(gè)工作的最大薪水、前n個(gè)數(shù)的最大值,那當(dāng)我們存在多個(gè)變量時(shí)又該如何求解呢?所以我們引出二維動(dòng)態(tài)規(guī)劃。

第二類(lèi)問(wèn)題(二維)

1.選取數(shù)字

給定一個(gè)數(shù),我們是否可以在如下數(shù)組中找到任意個(gè)數(shù),使選中的所有數(shù)值之和等于給定的數(shù),如果可以找到,則返回true,如果不可以找到,則返回false。

arr數(shù)組

首先我們先用遞歸的思想來(lái)看這道題,假如給定一個(gè)數(shù)9,我們?cè)鯓訉?xiě)出一個(gè)算法來(lái)讓計(jì)算機(jī)判斷數(shù)組中是否有任意個(gè)數(shù)之和為9呢?還是老套路,我們創(chuàng)建一個(gè)遞歸函數(shù),傳給它數(shù)組、數(shù)組的最后一個(gè)下標(biāo)(此題為5)、目標(biāo)值。我們從數(shù)組中第六個(gè)元素開(kāi)始看,我們有兩種選擇:第一種,我們選擇這個(gè)元素,如果選擇的話(huà),那么我們接下來(lái)調(diào)用遞歸函數(shù),它的實(shí)參就是數(shù)組、此下標(biāo)數(shù)減1、目標(biāo)數(shù)減此下標(biāo)的值(因?yàn)槲覀冞x了這個(gè)下標(biāo)上的值,所以應(yīng)該把值減去);第二種,我們不選擇這個(gè)數(shù),那么我們接下來(lái)調(diào)用遞歸函數(shù),它的實(shí)參就是數(shù)組、此下標(biāo)數(shù)減1、目標(biāo)數(shù)。如果這兩種選擇有一種能夠成功找出結(jié)果,那么就返回true,如果兩種選擇都不滿(mǎn)足,那么則返回false即可。

接下來(lái)我們要思考遞歸的中止條件:

1.當(dāng)目標(biāo)值被減為0時(shí),直接返回true

2.當(dāng)下標(biāo)數(shù)為0時(shí),如果下標(biāo)0對(duì)應(yīng)的數(shù)組值不等于此時(shí)的目標(biāo)值,則返回false

3.當(dāng)選擇此下標(biāo)值時(shí),如果此下標(biāo)對(duì)應(yīng)的數(shù)組值大于目標(biāo)值,則不能做此選擇

好,既然確定了終止條件,那么我們來(lái)看代碼:

#include <iostream> using namespace std;int arr[6] = {3, 34, 4, 12, 5, 2};int rec_subset(int arr[], int i, int s) {if (s == 0)return true;else if (i == 0)return arr[0] == s;else if (arr[i] > s)return rec_subset(arr, i - 1, s);else {bool A = rec_subset(arr, i - 1, s - arr[i]);bool B = rec_subset(arr, i - 1, s);return A or B;} } int main() {cout << rec_subset(arr, 5, 13); }

由于此遞歸重復(fù)了大量重復(fù)子問(wèn)題,接下來(lái)我們要思考的是,怎樣用動(dòng)態(tài)規(guī)劃的方式從前往后做這道題?

首先我們判斷此動(dòng)態(tài)規(guī)劃為二維問(wèn)題,因?yàn)橛袃蓚€(gè)東西可以作為二維數(shù)組的兩個(gè)下標(biāo):目標(biāo)值、arr數(shù)組中的元素的下標(biāo)。因?yàn)槲覀儚膭偛诺倪f歸可以看出目標(biāo)值從后往前是不斷遞減的,因此我們就可以從0開(kāi)始往后遞增,arr數(shù)組中的元素的個(gè)數(shù)也可以依次增加,所以作為二維數(shù)組的另一個(gè)下標(biāo)。因?yàn)槲覀兎祷氐氖遣紶栔?#xff0c;所以我們需要建立二維布爾數(shù)組如下:

初始的二維數(shù)組

這就是我們要保存每個(gè)數(shù)據(jù)的二維數(shù)組,第一行代表的是目標(biāo)值從1到9,列代表的是下標(biāo)數(shù),每個(gè)格子代表用當(dāng)前的元素可否能找出數(shù)據(jù)使其和等于目標(biāo)值,那怎么樣填此二維數(shù)組呢?

現(xiàn)在我們來(lái)思考一下我們上面所想到的終止條件:

1.當(dāng)目標(biāo)值被減為0時(shí),直接返回true,所以第一列都為true

2.當(dāng)下標(biāo)數(shù)為0時(shí),如果下標(biāo)0對(duì)應(yīng)的數(shù)組值不等于此時(shí)的目標(biāo)值,則返回false,因此第一行除了3等于第0個(gè)元素對(duì)應(yīng)的數(shù)組數(shù)據(jù)為true外,其余都為false

因此我們可以把此表改成如下:

改過(guò)后的二維數(shù)組

我們用T代表true,用F代表false,其中第0行第0列是T或者F都可以。

接下來(lái)我們就可以給二維數(shù)組里面的其他元素賦值了,比如第i行j列,我們先判斷i下標(biāo)對(duì)應(yīng)的數(shù)組元素值是否大于j(目標(biāo)元素)。

如果不大于,那么就可以分為兩種情況:第一種,其值等于行為i-1,列為j-arr[i]的值(代表選了此下標(biāo)對(duì)應(yīng)的數(shù));第二種行為i-1,列為j(代表沒(méi)有選此下標(biāo)對(duì)應(yīng)的數(shù))。當(dāng)兩種情況有一種以上為T(mén)時(shí),第i行j列的值就為T(mén),否則就為F。

如果大于,意為我們不能選擇此時(shí)對(duì)應(yīng)的元素值,因?yàn)槿绻x擇的話(huà),目標(biāo)值就變?yōu)樨?fù)數(shù)了,所以我們僅有一種選擇,那么第i行j列的值就等于i-1行j列的值。

以此類(lèi)推,填滿(mǎn)此二維數(shù)組,最終的值為二維數(shù)組最右下角的值。我們來(lái)看代碼:

#include <iostream> using namespace std;int arr[6] = {3, 34, 4, 12, 5, 2};bool dpsubset(int arr[], int s) {bool subset[6][s + 1];for (auto j : subset) {j[0] = true;}for (auto &j : subset[0]) {j = false;}subset[0][arr[0]] = true;for (int h = 1; h < 6; h++) {for (int k = 1; k < s + 1; k++) {if (arr[h] > k)subset[h][k] = subset[h - 1][k];else {bool A = subset[h - 1][k];bool B = subset[h - 1][k - arr[h]];subset[h][k] = A or B;}}}return subset[5][s]; }int main() {cout << dpsubset(arr, 13); }

由此問(wèn)題引出另外一個(gè)問(wèn)題,我們?cè)鯓忧蟮梦覀冞x擇的是哪些數(shù)呢?別急,看了下面的題目,我們就會(huì)解決了。

2.背包回溯

現(xiàn)在有四個(gè)物品,背包的總?cè)萘繛?,背包最多能裝入價(jià)值為多少的物品?都裝了哪些物品?

問(wèn)題描述

毫無(wú)疑問(wèn),這是二維動(dòng)態(tài)規(guī)劃,怎么判斷的呢?因?yàn)樗袃蓚€(gè)東西可以作為二維數(shù)組的兩個(gè)下標(biāo),一個(gè)是物品編號(hào),一個(gè)是背包容量,所以我們創(chuàng)建二維數(shù)組如下:

二維數(shù)組

其中第一行代表背包容量,第一列代表物品個(gè)數(shù),每個(gè)格子為在當(dāng)前背包容量的情況下考慮當(dāng)前的物品所能裝下的最大價(jià)值是多少。

現(xiàn)在我們來(lái)填格子,首先第二行全部為0,因?yàn)榇藭r(shí)沒(méi)有物品;其次第二列為0,因?yàn)榇藭r(shí)背包容量為0。現(xiàn)在我們來(lái)填其余表格,在填之前,我們先考慮當(dāng)前格子對(duì)應(yīng)的物品是否能裝入背包。

如果能裝下當(dāng)前物品,那么分兩種情況:第一種為裝入背包,獲得此物品的價(jià)值,然后減去它所占的空間,找對(duì)應(yīng)剩余空間容量、物品數(shù)減一對(duì)應(yīng)的最大價(jià)值(就是找之前的格子),然后兩個(gè)價(jià)值相加;第二種為不裝,那么當(dāng)前最大價(jià)值等于對(duì)應(yīng)空間容量不變、物品數(shù)量減一的最大價(jià)值(就是此格子頭頂?shù)母褡?#xff09;。最后此格子的值等于兩種情況中最大的值。

如果不能裝入背包,那么當(dāng)前最大價(jià)值等于對(duì)應(yīng)空間容量不變、物品數(shù)量減一的最大價(jià)值(就是此格子頭頂?shù)母褡?#xff09;,最后此格子的值等于此格子頭頂上的格子的值。

在我們填滿(mǎn)二維數(shù)組后,來(lái)考慮一下新的問(wèn)題:回溯,如何找到背包放的物品編號(hào)?

我們先從二維數(shù)組最右下角的格子找,如果它的值等于它頭頂上的格子,則證明它沒(méi)裝此時(shí)對(duì)應(yīng)的物品,否則就是裝了此物品,這個(gè)格子的值等于這個(gè)格子的兩個(gè)下標(biāo)分別減一和此時(shí)放入背包的物品的所占空間的格子對(duì)應(yīng)的值加上這個(gè)格子對(duì)應(yīng)的物品價(jià)值,開(kāi)辟一個(gè)數(shù)組,這樣回溯,如果找到放入的物品,則在此數(shù)組對(duì)應(yīng)的位置上設(shè)為1,沒(méi)放入此物品則在數(shù)組對(duì)應(yīng)的位置上設(shè)為0,最后輸出數(shù)組即可,大家看代碼:

/* i物品編號(hào) 1 2 3 4 w體積 2 3 4 5 v價(jià)值 3 4 5 6 */ #include <iostream> using namespace std; int weight[5] = {0, 2, 3, 4, 5}; int value[5] = {0, 3, 4, 5, 6}; int dp[5][9]; int object[5];void Dynamic() {for (int i = 1; i < 5; i++) {for (int j = 1; j < 9; j++) {if (weight[i] > j)dp[i][j] = dp[i - 1][j];elsedp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);}} }void Find(int i, int j) {if (i == 0) {for (int i = 0; i < 5; i++)cout << object[i] << " ";return;}if (dp[i][j] == dp[i - 1][j]) {object[i] = 0;Find(i - 1, j);} else if (dp[i][j] == dp[i - 1][j - weight[i]] + value[i]) {object[i] = 1;Find(i - 1, j - weight[i]);} } int main() {Dynamic();cout << dp[4][8];Find(4, 8); }

延伸

如果你看到這里,并且認(rèn)真看完上述文字后,恭喜你初步掌握了動(dòng)態(tài)規(guī)劃的常見(jiàn)問(wèn)題,最好把上述代碼反復(fù)敲幾遍,第一遍照著敲,第二表憑借理解單獨(dú)敲。馬化騰曾說(shuō)過(guò)一句話(huà),給我感觸很大,“用最笨的方式去領(lǐng)悟編程,用抄代碼的方式來(lái)培養(yǎng)感覺(jué)”。

此外大家就可以不斷在各個(gè)平臺(tái)上刷題來(lái)提升自己的感覺(jué),一起加油!

?

推薦閱讀

  • 手把手教你配置VS Code 遠(yuǎn)程開(kāi)發(fā)工具,工作效率提升N倍

  • 用大白話(huà)徹底搞懂 HBase RowKey 詳細(xì)設(shè)計(jì)

  • 后端程序員必備:書(shū)寫(xiě)高質(zhì)量SQL的30條建議

  • Go 遠(yuǎn)超 Python,機(jī)器學(xué)習(xí)人才極度稀缺,全球 16,655 位程序員告訴你這些真相!

  • 任正非談“狼文化”:華為沒(méi)有 996,更沒(méi)有 007

  • 區(qū)塊鏈必讀“上鏈”哲學(xué):“胖鏈下”與“瘦鏈上”

  • 在商業(yè)中,如何與人工智能建立共生關(guān)系?

真香,朕在看了!

總結(jié)

以上是生活随笔為你收集整理的数据结构与算法、讲解、动态规划一脸懵?看完之后轻松掌握!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。