01數據結構-初探動態規劃

01數據結構-初探動態規劃

  • 前言
  • 1.基本思想
  • 2.重疊子問題
  • 3.斐波那契數列
  • 4.備忘錄(記憶化搜索表)
    • 4.1備忘錄(記憶化搜索表)代碼實現
  • 5.DP table
    • 5.1DP table代碼實現
  • 6.練習

前言

在學習動態規劃時切忌望文生義,因為其名字與其思想關系不大,你可以自己想一個記住其思想的名字,例如:遞推公式法,狀態轉移方程法等等。與其說動態規劃是一個算法,還不如說是解決問題的方法論,動態規劃的一般形式就是求最優值,比如最長公共子序列,最大子段和,最優二叉搜索樹等等。

貪心算法:在前面我們提到過的Prim算法和Kruskal算法就借用到了貪心算法的思想,例如我們的Prim算法,每次我們都選擇選中的頂點中的邊的最小值,但是有一定的條件:選擇出來的邊不能閉環,像這種前一步的貪心和后一步的貪心沒有直接關系就比較簡單一點,例如一個序列中找最大值,最小值。

分治算法:大問題拆分成小問題,小問題之間相對獨立,每個小問題解決方案是一樣的

動態規劃:大問題拆分成小問題,小問題之間互相依賴重復,每個小問題解決方案是一樣的。

1.基本思想

動態規劃算法與分治法類似,其基本思想就是將待求解問題分解成若干子問題,先求解子問題,然后從這些子問題的解得到原問題的解。

與分治法不同的是,適合動態規劃法求解的問題,經分解得到的子問題往往不是相互獨立的。若用分治法來解這類問題,則分解得到的子問題數目太多,以至于最后解決原問題需要耗費指數時間。然而,不同子問題的數目常常只有多項式量級。

在用分治法求解時,有些子問題被重復結算了很多次。如果我們能夠保存已經解決的子問題的答案,而在需要時再找出已求得的答案,這樣就可以避免大量的重復計算,從而得到多項式時間復雜度的算法。為了達到此目的,可以用一個表來記錄所有已解決的子問題的答案,不管該子問題以后是否被用到,只要它被計算過,就將其結果填入表中。這就是動態規劃的基本思想。

基本要點:將待求解問題分解成若干子問題,先求解子問題,然后從這些子問題的解得到原問題的解;經分解得到的子問題往往不是相互獨立的;保存已經解決的子問題的答案,避免重復計算。

例如下圖我們想要f(N)的數據,假設需要知道f(x)和f(y),f(x)需要從f(x1)得知,f(y)需要從f(x2)得知,而f(x2)和f(x1)有關,那我們就可以采用動態規劃的思想,把f(x1)計算出的結果保存下來,這樣計算f(x2)的時候就可以少計算一次f(x1)。
在這里插入圖片描述

2.重疊子問題

在用遞歸算法自頂向下解決一個問題時,每次產生的子問題并不總是新問題,有些子問題被反復計算多次。動態規劃正是利用了這種子問題的重疊性質,對每個子問題只解一次,而后將其解保存到一個表格中,當再次需要解此子問題時,只是簡單地用常數時間查看一下結果。

動態規劃經分解得到的子問題往往不是相互獨立的 。如果經分解得到的子問題的解之間相互獨立,比如二分查找(Binary Search)經分解得到的子問題之間相互獨立,不存在重疊子問題,所以不適合用動態規劃,更適合分治算法。

接下來來看一個非常經典的重疊子問題:斐波那契數列。

3.斐波那契數列

我們可以理解斐波那契數列為:f(n)=f(n-1)+f(n-2)(遞推公式)。一個數字等于前面兩個數字的和,這就是一個大問題拆分成幾個小問題,我們需要把它拆分成遞歸搜索樹。
在這里插入圖片描述
如圖,如果我們想知道斐波那契數列中的第5個數字,我們需要知道第3個和第4個數字,一次向下類推可以得到一顆二叉樹,明顯到當我們拆分到2的時候,我們向下再拆一次就變成了需要知道0和1的問題,此時無法再向下拆分,我們就認為,0和1是初始狀態(你也可以認為是1和2),當我們計算從下往上運算的時候,如果我們不保存已經計算得到的結果的話,我們會大量重復的計算,例如:先看5的左子樹我們通過計算0,1計算2,再通過2,1計算3,此時3的右邊2我們還需要重復計算,因為我們沒有保存第一次計算得到的2的數值,同理當我們計算到4的時候,我們還需要計算得到5的右子樹的3,因為我們沒有保存第一次計算3時得到的數值,接下來看一看如果我們不保存值的話的時間長短。

這就是一個很簡單的遞歸代碼,我就不過多敘述

#include <stdio.h>
#include <time.h>
#include <stdlib.h>unsigned int fib01(unsigned int n) {if (n == 0) {return 0;}if (n == 1) {return 1;}return fib01(n - 1) + fib01(n - 2);
}// 1 1 2 3 5
void test01() {clock_t start = clock();unsigned int x = fib01(40);clock_t end = clock();printf("cost time: %f s。fib01 = %u\n", (double)(end - start) / CLOCKS_PER_SEC, x);
}int main() {test01();return 0;
}

結果:

D:\work\DataStruct\cmake-build-debug\06_DP\fib.exe
cost time: 0.504000 s。fib01 = 102334155進程已結束,退出代碼為 0

現在將數字改為46

#include <stdio.h>
#include <time.h>
#include <stdlib.h>unsigned int fib01(unsigned int n) {if (n == 0) {return 0;}if (n == 1) {return 1;}return fib01(n - 1) + fib01(n - 2);
}// 1 1 2 3 5
void test01() {clock_t start = clock();unsigned int x = fib01(46);clock_t end = clock();printf("cost time: %f s。fib01 = %u\n", (double)(end - start) / CLOCKS_PER_SEC, x);
}int main() {test01();return 0;
}

結果:

D:\work\DataStruct\cmake-build-debug\06_DP\fib.exe
cost time: 8.964000 s。fib01 = 1836311903進程已結束,退出代碼為 0

可以看到僅僅只是增加了6個數字,時間一下子增加了8秒之多,這是因為,我們每增加一個,都會多重復計算兩層的數字,時間復雜度近似于n2
在這里插入圖片描述
我們有兩種方法解決這種問題,第一種是備忘錄(記憶化搜索表),第二種是DP table,第一種方法是把大問題拆分成小問題,自頂向下解決問題,而第二種方法是拿到一個大問題,先想有哪些小問題需要我們解決,是自底向上解決問題。兩種方法的時間復雜度都差不多,但第一種思路顯然更符合人類的思考方式,所以如果是競賽的同學多考慮第一種方法。

4.備忘錄(記憶化搜索表)

備忘錄方法為每一個子問題建立一個記錄項,初始時,該記錄項存入一個特殊的值,表示該子問題尚未被解決(比如斐波那契數的備忘錄版本中將其設置為-1)。

在求解過程中,對每個待求解的子問題,首先查看其相應的記錄項。若記錄項中存儲的是初始化時存入的特殊值,則表示該子問題是第一次遇到,此時計算出該子問題的解,并保存在相應的記錄項中,以備以后查看。若記錄項中存儲的已不是初始化時存入的特殊值,則表示該子問題已被計算過,其相應的記錄項存儲的是該子問題的答 案。此時,只要從記錄項中取出該子問題的答案即可,而不必重新計算。

4.1備忘錄(記憶化搜索表)代碼實現

#include <stdio.h>
#include <time.h>
#include <stdlib.h>// mem1[i] 記憶了第i個斐波那契數列的值
static unsigned int *mem1;
unsigned int fib02(unsigned int n) {if (n == 0) {return 0;}if (n == 1) {return 1;}if (mem1[n] == -1) {mem1[n] = fib02(n - 1) + fib02(n - 2);}return mem1[n];
}void test02() {int n = 46;mem1 = malloc(sizeof(unsigned int) * (n + 1));// 初始化這個表里,存儲一個以后算法中永遠不會出現的狀態for (int i = 0; i <= n; ++i) {mem1[i] = -1;		}clock_t start = clock();unsigned int x = fib02(n);clock_t end = clock();printf("cost time: %f s。fib02 = %u\n", (double)(end - start) / CLOCKS_PER_SEC, x);free(mem1);
}

結果:

D:\work\DataStruct\cmake-build-debug\06_DP\fib.exe
cost time: 0.000000 s。fib02 = 1836311903進程已結束,退出代碼為 0

可以看到,用的時間極短,我們存儲了已經計過的值后,當遇到相同的值時我們直接返回就行不再需要重復計算,這大大減少了遞歸搜索樹的節點,所以時間效率很高。

5.DP table

DP table就是動態規劃算法自底向上建立的一個表格,用于保存每一個子問題的解,并返回表中的最后一個解。比如斐波那契數,我們先計算 fib(0),然后 fib(1),然后 fib(2),然后 fib(3),以此類推,直至計算出fib(n)。

比如我們計算 fib(5),先由 fib(0) + fib(1) 得到 fib(2),再由 fib(1) + fib(2) 得到 fib(3),再由 fib(2) + fib(3) 得到 fib(4),最后由 fib(3) + fib(4) 得到 fib(5)。

也就是說,我們只需要存儲子問題的解,而不需要重復計算子問題的解。

5.1DP table代碼實現

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
unsigned int fib03(unsigned int n) {unsigned int *mem = malloc(sizeof(unsigned int) * (n + 1));//遞推mem[0] = 0;mem[1] = 1;for (int i = 2; i <= n; ++i) {mem[i] = mem[i - 1] + mem[i - 2];}unsigned int result = mem[n];free(mem);return result;
}void test03() {int n = 146;clock_t start = clock();unsigned int x = fib03(n);clock_t end = clock();printf("cost time: %f s。fib03 = %u\n", (double)(end - start) / CLOCKS_PER_SEC, x);
}int main() {test03();return 0;
}
D:\work\DataStruct\cmake-build-debug\06_DP\fib.exe
cost time: 0.000000 s。fib03 = 2620762145進程已結束,退出代碼為 0

可以看到即使數字變成了146,我們的時間效率還是很高,因為我們已經保存了很多節點的數字。

6.練習

問題描述:給定三個數{1,3,5},請問使用這三個數,有多少種方式可以構造出一個給定的數n(假設這里n等于6)(允許重復和不同順序)
在這里插入圖片描述
如圖,這個題的遞歸搜索樹大致如圖,我沒有畫完全,大致能懂意思就行。

我們可以先寫出遞歸的函數:

int combine01(int n) {if (n < 0) {return 0;}if (n == 1 || n == 0) {return 1;}return combine01(n - 1) + combine01(n - 3) + combine01(n - 5);
}

當n小于0的時候很明顯沒有組合方式,當n等于1或者n等于0的時候只有一種組合方式。

  1. 最后一步采用操作1(步長為1)
    如果最后一步是操作1,那么在此之前需要解決規模為 n-1 的問題

所以有 combine01(n-1) 種方式

  1. 最后一步采用操作3(步長為3)
    如果最后一步是操作3,那么在此之前需要解決規模為 n-3 的問題

所以有 combine01(n-3) 種方式

  1. 最后一步采用操作5(步長為5)
    如果最后一步是操作5,那么在此之前需要解決規模為 n-5 的問題

所以有 combine01(n-5) 種方式

總和起來就是我們所有的方法數,接下來先采用今天講的備忘錄(記憶化搜索表)實現:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
static int solve(int n, int *mem) {if (n < 0) {return 0;}if (n == 1 || n == 0) {return 1;}if (mem[n] == -1) {mem[n] = solve(n - 1, mem) + solve(n - 3, mem) + solve(n - 5, mem);}return mem[n];
}int combine02(int n) {int result;int *mem = malloc(sizeof(int) * (n + 1));for (int i = 0; i <= n; ++i) {mem[i] = -1;}result = solve(n, mem);free(mem);return result;
}int main() {printf("%d\n", combine03(6));return 0;
}

結果:

D:\work\DataStruct\cmake-build-debug\06_DP\cunNum.exe
8進程已結束,退出代碼為 0

再來用DP table的方法:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
static int solve(int n, int *mem) {if (n < 0) {return 0;}if (n == 1 || n == 0) {return 1;}if (mem[n] == -1) {mem[n] = solve(n - 1, mem) + solve(n - 3, mem) + solve(n - 5, mem);}return mem[n];
}
int combine03(int n) {int *mem = malloc(sizeof(int) * (n + 1));mem[1] = 1;mem[2] = 1;mem[3] = 2;mem[4] = 3;mem[5] = 5;for (int i = 6; i <= n; ++i) {mem[i] = mem[i - 1] + mem[i - 3] + mem[i - 5];}int result = mem[n];free(mem);return result;
}int main() {printf("%d\n", combine03(6));return 0;

結果:

D:\work\DataStruct\cmake-build-debug\06_DP\cunNum.exe
8進程已結束,退出代碼為 0

大家可以在LeetCode刷類似的題目,大概先寫這些吧,今天的博客就先寫到這,謝謝您的觀看。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/99190.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/99190.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/99190.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[智能算法]可微的神經網絡搜索算法-FBNet

一、概述 相較于基于強化學習的NAS&#xff0c;可微NAS能直接使用梯度下降更新模型結構超參數&#xff0c;其中較為有名的算法就是DARTS&#xff0c;其具體做法如下。 首先&#xff0c;用戶需要定義一些候選模塊&#xff0c;這些模塊內部結構可以互不相同&#xff08;如設置不同…

Elasticsearch安裝啟動常見問題全解析

文章目錄&#x1f4da; Elasticsearch 安裝與啟動問題總結一、核心問題概覽二、詳細問題分析與解決方案1. &#x1f510; **權限問題&#xff1a;AccessDeniedException**? 錯誤日志&#xff1a;&#x1f4cc; 原因&#xff1a;? 解決方案&#xff1a;2. ?? **配置沖突&…

Uniapp中使用renderjs實現OpenLayers+天地圖的展示與操作

Uniapp中自帶的地圖組件對支持的地圖服務略有局限&#xff0c;同時&#xff0c;該組件在樣式布局上層級過高且無法控制&#xff0c;無法滿足部分高度自定義化的需求。故引入renderjs視圖層工具搭配OpenLayers框架對地圖功能進行實現&#xff0c;但由于renderjs的限制&#xff0…

從C++開始的編程生活(8)——內部類、匿名對象、對象拷貝時的編譯器優化和內存管理

前言 本系列文章承接C語言的學習&#xff0c;需要有C語言的基礎才能學會哦~ 第8篇主要講的是有關于C的內部類、匿名對象、對象拷貝時的編譯器優化和內存管理。 C才起步&#xff0c;都很簡單&#xff01;&#xff01; 目錄 前言 內部類 性質 匿名對象 性質 ※對象拷貝時的…

MT5追大速率回測BUG

將MT5策略測試器中的回測速率調到最大(最快速度),**確實非常容易導致出現不符合策略邏輯的秒級成交(閃電交易)**。這并非MT5的“bug”,而是由**回測引擎的工作方式**與**策略代碼的編寫方法**在高速運行下不匹配所導致的。 --- ### 為什么最大速率會導致問題? MT5回測…

[數據結構——lesson10.堆及堆的調整算法]

引言 上節我們學習完二叉樹后[數據結構——lesson9.二叉樹]&#xff0c;這節我們將學習數據結構——堆 學習目標 1.堆的概念及結構 堆是一種特殊的完全二叉樹結構&#xff0c;在計算機科學和數據結構中廣泛應用&#xff0c;特別是在堆排序算法和優先隊列的實現中&#xff0c;…

九識智能與北控北斗合作研發的L4級燃氣超微量高精準泄漏檢測無人車閃耀服貿會,守護城市安全

2025年9月10日至14日&#xff0c;2025年中國國際服務貿易交易會將于北京首鋼園舉辦。在這場國際盛會上&#xff0c;九識智能與北京北控北斗科技投資有限公司&#xff08;以下簡稱“北控北斗”&#xff09;合作研發的L4級燃氣超微量高精準泄漏檢測無人車及相關系統解決方案&…

【C語言入門】手把手教你實現順序棧

棧是計算機科學中最基礎且重要的數據結構之一&#xff0c;它遵循"后進先出"&#xff08;LIFO&#xff09;的原則。想象一下一疊盤子&#xff0c;你只能從最上面取放&#xff0c;這就是棧的直觀體現。本文將用C語言帶你一步步實現一個順序棧&#xff0c;即使你是編程小…

北斗導航 | ARAIM(高級接收機自主完好性監測)算法在民航LPV-200進近中的具體實現流程

要詳細說明ARAIM(高級接收機自主完好性監測)算法在民航LPV-200進近中的具體實現流程,需結合ARAIM的核心邏輯(多星座融合、多假設解分離、風險優化分配)與LPV-200的嚴格要求(垂直保護級VPL≤35米、垂直告警限VAL=35米、有效監測門限EMT≤15米等),以下是 step-by-step 的…

AIPex:AI + 自然語言驅動的瀏覽器自動化擴展

AIPex:AI + 自然語言驅動的瀏覽器自動化擴展 引言 一、快速上手 1.1 安裝AIPex擴展 1.2 首次配置 1.3 界面介紹 第二章:30+工具詳解 2.1 標簽頁管理工具集 ??? **get_all_tabs - 全局標簽頁概覽** ?? **switch_to_tab - 智能標簽頁切換** ?? **標簽頁批量操作** ?? …

機器學習模型可信度與交叉驗證:通俗講解

先從一個故事說起&#xff1a;農場里的火雞科學家&#xff0c;觀察了一年發現“每天上午11點必有食物”&#xff0c;結果感恩節當天&#xff0c;它沒等到食物&#xff0c;反而成了人類的食物。這個故事告訴我們&#xff1a;只靠過去的經驗下結論&#xff0c;很可能出錯——機器…

HTML5和CSS3新增的一些屬性

1、HTML5新增特性這些新特性都有兼容性問題&#xff0c;基本是IE9以上版本瀏覽器才支持1&#xff09;新增語義化標簽2&#xff09;新增多媒體標簽音頻&#xff1a;<audio>視頻&#xff1a;<video>&#xff08;1&#xff09;視頻<video>---盡量使用mp4格式<…

Redis的RedLock

RedLock算法深度解析RedLock是Redis作者針對分布式環境設計的多節點鎖算法&#xff0c;核心目標是解決單點Redis在分布式鎖場景中的可靠性缺陷。傳統方案的局限性單節點Redis鎖的問題單點故障&#xff1a;單個Redis實例宕機導致所有鎖服務不可用可靠性不足&#xff1a;無法保證…

SpringMVC @RequestMapping的使用演示和細節 詳解

目錄 一、RequestMapping是什么&#xff1f; 二、RequestMapping 的使用演示 1.RequestMapping在方法上的使用&#xff1a; 2.RequestMapping同時在類和方法上使用&#xff1a; 3.RequestMapping指定請求參數&#xff1a; 4.RequestMapping使用Ant風格URL&#xff1a; 5.Requ…

flutter項目 -- 換logo、名稱 、簽名、打包

1、換logo, 透明底&#xff0c;下面5個尺寸&#xff0c;需要UI設計2、換名沒配置型的改名方式如下 打開app/src/main/AndroidManifest.xml3、簽名 運行 flutter doctor -vD:\project\Apk\keystore 自己建立的keystore文件夾&#xff0c; 注意命令后是 megoai-release-key(自…

【貪心算法】day9

&#x1f4dd;前言說明&#xff1a; 本專欄主要記錄本人的貪心算法學習以及LeetCode刷題記錄&#xff0c;按專題劃分每題主要記錄&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代碼&#xff1b;&#xff08;2&#xff09;優質解法 優質代碼&#xff1b;&#xff…

linux C 語言開發 (八) 進程基礎

文章的目的為了記錄使用C語言進行linux 開發學習的經歷。開發流程和要點有些記憶模糊&#xff0c;趕緊記錄&#xff0c;防止忘記。 相關鏈接&#xff1a; linux C 語言開發 (一) Window下用gcc編譯和gdb調試 linux C 語言開發 (二) VsCode遠程開發 linux linux C 語言開發 (…

從零學算法1094

1094.拼車 車上最初有 capacity 個空座位。車 只能 向一個方向行駛&#xff08;也就是說&#xff0c;不允許掉頭或改變方向&#xff09; 給定整數 capacity 和一個數組 trips , trips[i] [numPassengersi, fromi, toi] 表示第 i 次旅行有 numPassengersi 乘客&#xff0c;接他…

B2B企業營銷型AI Agent服務商推薦:誰更專業?如何選型?

一、引言&#xff1a;為什么B2B企業需要營銷型AI Agent&#xff1f;在當前競爭激烈的B2B市場中&#xff0c;企業普遍面臨幾大挑戰&#xff1a;線索獲取難&#xff1a;獲客成本持續上升&#xff0c;高質量線索難以篩選。銷售周期長&#xff1a;從初步接觸到簽單&#xff0c;往往…

算法-雙指針5.6

目錄 &#x1f33f;力扣611-有效三角形得個數 &#x1f9ca;題目鏈接&#xff1a;https://leetcode.cn/problems/valid-triangle-number/description/ &#x1f9ca;題目描述&#xff1a;?編輯 &#x1f9ca;解題思路&#xff1a; &#x1f9ca;解題代碼&#xff1a; &a…