c++矩阵连乘的动态规划算法并输出_你在Java中用过动态规划吗?
1. 介紹
動(dòng)態(tài)規(guī)劃典型的被用于優(yōu)化遞歸算法,因?yàn)樗鼈儍A向于以指數(shù)的方式進(jìn)行擴(kuò)展。動(dòng)態(tài)規(guī)劃主要思想是將復(fù)雜問(wèn)題(帶有許多遞歸調(diào)用)分解為更小的子問(wèn)題,然后將它們保存到內(nèi)存中,這樣我們就不必在每次使用它們時(shí)重新計(jì)算它們。
要理解動(dòng)態(tài)規(guī)劃的概念,我們需要熟悉一些主題:
本文所有代碼均為java代碼實(shí)現(xiàn)。
2. 什么是動(dòng)態(tài)規(guī)劃?
動(dòng)態(tài)規(guī)劃是一種編程原理,可以通過(guò)將非常復(fù)雜的問(wèn)題劃分為更小的子問(wèn)題來(lái)解決。這個(gè)原則與遞歸很類(lèi)似,但是與遞歸有一個(gè)關(guān)鍵點(diǎn)的不同,就是每個(gè)不同的子問(wèn)題只能被解決一次。
為了理解動(dòng)態(tài)規(guī)劃,我們首先需要理解遞歸關(guān)系的問(wèn)題。每個(gè)單獨(dú)的復(fù)雜問(wèn)題可以被劃分為很小的子問(wèn)題,這表示我們可以在這些問(wèn)題之間構(gòu)造一個(gè)遞歸關(guān)系。讓我們來(lái)看一個(gè)我們所熟悉的例子:斐波拉契數(shù)列,斐波拉契數(shù)列的定義具有以下的遞歸關(guān)系:
注意:遞歸關(guān)系是遞歸地定義下一項(xiàng)是先前項(xiàng)的函數(shù)的序列的等式。Fibonacci序列就是一個(gè)很好的例子。
所以,如果我們想要找到斐波拉契數(shù)列序列中的第n個(gè)數(shù),我們必須知道序列中第n個(gè)前面的兩個(gè)數(shù)字。
但是,每次我們想要計(jì)算Fibonacci序列的不同元素時(shí),我們?cè)谶f歸調(diào)用中都有一些重復(fù)調(diào)用,如下圖所示,我們計(jì)算Fibonacci(5):
例如:如果我們想計(jì)算F(5),明顯的我們需要計(jì)算F(3)和F(4)作為計(jì)算F(5)的先決條件。然而,為了計(jì)算F(4),我們需要計(jì)算F(3)和F(2),因此我們又需要計(jì)算F(2)和F(1)來(lái)得到F(3),其他的求解諸如此類(lèi)。
這樣的話(huà)就會(huì)導(dǎo)致很多重復(fù)的計(jì)算,這些重復(fù)計(jì)算本質(zhì)上是冗余的,并且明顯的減慢了算法的效率。為了解決這種問(wèn)題,我們介紹動(dòng)態(tài)規(guī)劃。
在這種方法中,我們對(duì)解決方案進(jìn)行建模,就像我們要遞歸地解決它一樣,但我們從頭開(kāi)始解決它,記憶到達(dá)頂部采取的子問(wèn)題(子步驟)的解決方案。因此,對(duì)于Fibonacci序列,我們首先求解并記憶F(1)和F(2),然后使用兩個(gè)記憶步驟計(jì)算F(3),依此類(lèi)推。這意味著序列中每個(gè)單獨(dú)元素的計(jì)算都是O(1),因?yàn)槲覀円呀?jīng)知道前兩個(gè)元素。
當(dāng)使用動(dòng)態(tài)規(guī)劃解決問(wèn)題的時(shí)候,我們一般會(huì)采用下面三個(gè)步驟:
遵循這些規(guī)則,讓我們來(lái)看一下使用動(dòng)態(tài)規(guī)劃的算法的例子:
3. 貪心算法
下面來(lái)以這個(gè)為例子:
Given a rod of length n and an array that contains prices of all pieces of size smaller than n. Determine the maximum value obtainable by cutting up the rod and selling the pieces.3.1. 對(duì)于沒(méi)有經(jīng)驗(yàn)的開(kāi)發(fā)者可能會(huì)采取下面這種做法
這個(gè)問(wèn)題實(shí)際上是為動(dòng)態(tài)規(guī)劃量身定做的,但是因?yàn)檫@是我們的第一個(gè)真實(shí)例子,讓我們看看運(yùn)行這些代碼會(huì)遇到多少問(wèn)題:
public class naiveSolution { static int getValue(int[] values, int length) { if (length <= 0) return 0; int tmpMax = -1; for (int i = 0; i < length; i++) { tmpMax = Math.max(tmpMax, values[i] + getValue(values, length - i - 1)); } return tmpMax; } public static void main(String[] args) { int[] values = new int[]{3, 7, 1, 3, 9}; int rodLength = values.length; System.out.println("Max rod value: " + getValue(values, rodLength)); }}輸出結(jié)果:
Max rod value: 17
該解決方案雖然正確,但效率非常低,遞歸調(diào)用的結(jié)果沒(méi)有保存,所以每次有重疊解決方案時(shí),糟糕的代碼不得不去解決相同的子問(wèn)題。
3.2.動(dòng)態(tài)方法
利用上面相同的基本原理,添加記憶化并排除遞歸調(diào)用,我們得到以下實(shí)現(xiàn):
public class dpSolution { static int getValue(int[] values, int rodLength) { int[] subSolutions = new int[rodLength + 1]; for (int i = 1; i <= rodLength; i++) { int tmpMax = -1; for (int j = 0; j < i; j++) tmpMax = Math.max(tmpMax, values[j] + subSolutions[i - j - 1]); subSolutions[i] = tmpMax; } return subSolutions[rodLength]; } public static void main(String[] args) { int[] values = new int[]{3, 7, 1, 3, 9}; int rodLength = values.length; System.out.println("Max rod value: " + getValue(values, rodLength)); }}輸出結(jié)果:
Max rod value: 17
正如我們所看到的的,輸出結(jié)果是一樣的,所不同的是時(shí)間和空間復(fù)雜度。
通過(guò)從頭開(kāi)始解決子問(wèn)題,我們消除了遞歸調(diào)用的需要,利用已解決給定問(wèn)題的所有先前子問(wèn)題的事實(shí)。
性能的提升
為了給出動(dòng)態(tài)方法效率更高的觀點(diǎn)的證據(jù),讓我們嘗試使用30個(gè)值來(lái)運(yùn)行該算法。一種算法需要大約5.2秒來(lái)執(zhí)行,而動(dòng)態(tài)解決方法需要大約0.000095秒來(lái)執(zhí)行。
4. 簡(jiǎn)化的背包問(wèn)題
簡(jiǎn)化的背包問(wèn)題是一個(gè)優(yōu)化問(wèn)題,沒(méi)有一個(gè)解決方案。這個(gè)問(wèn)題的問(wèn)題是 - “解決方案是否存在?”:
Given a set of items, each with a weight w1, w2... determine the number of each item to put in a knapsack so that the total weight is less than or equal to a given limit K.
給定一組物品,每個(gè)物品的重量為w1,w2 ......確定放入背包中的每個(gè)物品的數(shù)量,以使總重量小于或等于給定的極限K
首先讓我們把元素的所有權(quán)重存儲(chǔ)在W數(shù)組中。接下來(lái),假設(shè)有n個(gè)項(xiàng)目,我們將使用從1到n的數(shù)字枚舉它們,因此第i個(gè)項(xiàng)目的權(quán)重為W [i]。我們將形成(n + 1)x(K + 1)維的矩陣M。M [x] [y]對(duì)應(yīng)于背包問(wèn)題的解決方案,但僅包括起始數(shù)組的前x個(gè)項(xiàng),并且最大容量為y
例如
假設(shè)我們有3個(gè)元素,權(quán)重分別是w1=2kg,w2=3kg,w3=4kg。利用上面的方法,我們可以說(shuō)M [1] [2]是一個(gè)有效的解決方案。這意味著我們正在嘗試用重量陣列中的第一個(gè)項(xiàng)目(w1)填充容量為2kg的背包。
在M [3] [5]中,我們嘗試使用重量陣列的前3項(xiàng)(w1,w2,w3)填充容量為5kg的背包。這不是一個(gè)有效的解決方案,因?yàn)槲覀冞^(guò)度擬合它。
4.1. 矩陣初始化
當(dāng)初始化矩陣的時(shí)候有兩點(diǎn)需要注意:
Does a solution exist for the given subproblem (M[x][y].exists) AND does the given solution include the latest item added to the array (M[x][y].includes).
給定子問(wèn)題是否存在解(M [x] [y] .exists)并且給定解包括添加到數(shù)組的最新項(xiàng)(M [x] [y] .includes)。
因此,初始化矩陣是相當(dāng)容易的,M[0][k].exists總是false,如果k>0,因?yàn)槲覀儧](méi)有把任何物品放在帶有k容量的背包里。
另一方面,M[0][0].exists = true,當(dāng)k=0的時(shí)候,背包應(yīng)該是空的,因此我們?cè)诶锩鏇](méi)有放任何東西,這個(gè)是一個(gè)有效的解決方案。
此外,我們可以說(shuō)M[k][0].exists = true,但是對(duì)于每個(gè)k來(lái)說(shuō) M[k][0].includes = false。
注意:僅僅因?yàn)閷?duì)于給定的M [x] [y]存在解決方案,它并不一定意味著該特定組合是解決方案。在M [10] [0]的情況下,存在一種解決方案 - 不包括10個(gè)元素中的任何一個(gè)。這就是M [10] [0] .exists = true但M [10] [0] .includes = false的原因。
4.2.算法原則
接下來(lái),讓我們使用以下偽代碼構(gòu)造M [i] [k]的遞歸關(guān)系:
if (M[i-1][k].exists == True): M[i][k].exists = True M[i][k].includes = Falseelif (k-W[i]>=0): if(M[i-1][k-W[i]].exists == true): M[i][k].exists = True M[i][k].includes = Trueelse: M[i][k].exists = False因此,解決方案的要點(diǎn)是將子問(wèn)題分為兩種情況:
第一種情況是不言自明的,我們已經(jīng)有了問(wèn)題的解決方案。
第二種情況是指了解第一個(gè)i-1元素的解決方案,但是容量只有一個(gè)第i個(gè)元素不滿(mǎn),這意味著我們可以添加一個(gè)第i個(gè)元素,并且我們有一個(gè)新的解決方案!
4.3. 實(shí)現(xiàn)
下面這何種實(shí)現(xiàn)方式,使得事情變得更加容易,我們創(chuàng)建了一個(gè)類(lèi)Element來(lái)存儲(chǔ)元素:
public class Element { private boolean exists; private boolean includes; public Element(boolean exists, boolean includes) { this.exists = exists; this.includes = includes; } public Element(boolean exists) { this.exists = exists; this.includes = false; } public boolean isExists() { return exists; } public void setExists(boolean exists) { this.exists = exists; } public boolean isIncludes() { return includes; } public void setIncludes(boolean includes) { this.includes = includes; }}接著,我們可以深入了解主要的類(lèi):
public class Knapsack { public static void main(String[] args) { Scanner scanner = new Scanner (System.in); System.out.println("Insert knapsack capacity:"); int k = scanner.nextInt(); System.out.println("Insert number of items:"); int n = scanner.nextInt(); System.out.println("Insert weights: "); int[] weights = new int[n + 1]; for (int i = 1; i <= n; i++) { weights[i] = scanner.nextInt(); } Element[][] elementMatrix = new Element[n + 1][k + 1]; elementMatrix[0][0] = new Element(true); for (int i = 1; i <= k; i++) { elementMatrix[0][i] = new Element(false); } for (int i = 1; i <= n; i++) { for (int j = 0; j <= k; j++) { elementMatrix[i][j] = new Element(false); if (elementMatrix[i - 1][j].isExists()) { elementMatrix[i][j].setExists(true); elementMatrix[i][j].setIncludes(false); } else if (j >= weights[i]) { if (elementMatrix[i - 1][j - weights[i]].isExists()) { elementMatrix[i][j].setExists(true); elementMatrix[i][j].setIncludes(true); } } } } System.out.println(elementMatrix[n][k].isExists()); }}唯一剩下的就是解決方案的重建,在上面的類(lèi)中,我們知道解決方案是存在的,但是我們不知道它是什么。
為了重建,我們使用下面的代碼:
List solution = new ArrayList<>(n);if (elementMatrix[n][k].isExists()) { int i = n; int j = k; while (j > 0 && i > 0) { if (elementMatrix[i][j].isIncludes()) { solution.add(i); j = j - weights[i]; } i = i - 1; }}System.out.println("The elements with the following indexes are in the solution:" + (solution.toString()));輸出:
Insert knapsack capacity: 12 Insert number of items: 5 Insert weights: 9 7 4 10 3 true The elements with the following indexes are in the solution: [5, 1]背包問(wèn)題的一個(gè)簡(jiǎn)單變化是在沒(méi)有價(jià)值優(yōu)化的情況下填充背包,但現(xiàn)在每個(gè)單獨(dú)項(xiàng)目的數(shù)量無(wú)限。
通過(guò)對(duì)現(xiàn)有代碼進(jìn)行簡(jiǎn)單調(diào)整,可以解決這種變化:
// Old code for simplified knapsack problemelse if (j >= weights[i]) { if (elementMatrix[i - 1][j - weights[i]].isExists()) { elementMatrix[i][j].setExists(true); elementMatrix[i][j].setIncludes(true); }}// New code, note that we're searching for a solution in the same// row (i-th row), which means we're looking for a solution that// already has some number of i-th elements (including 0) in it's solutionelse if (j >= weights[i]) { if (elementMatrix[i][j - weights[i]].isExists()) { elementMatrix[i][j].setExists(true); elementMatrix[i][j].setIncludes(true); }}5. 傳統(tǒng)的背包問(wèn)題
利用以前的兩種變體,現(xiàn)在讓我們來(lái)看看傳統(tǒng)的背包問(wèn)題,看看它與簡(jiǎn)化版本的不同之處:
Given a set of items, each with a weight w1, w2... and a value v1, v2... determine the number of each item to include in a collection so that the total weight is less than or equal to a given limit k and the total value is as large as possible.在簡(jiǎn)化版中,每個(gè)解決方案都同樣出色。但是,現(xiàn)在我們有一個(gè)找到最佳解決方案的標(biāo)準(zhǔn)(也就是可能的最大值)。請(qǐng)記住,這次我們每個(gè)項(xiàng)目都有無(wú)限數(shù)量,因此項(xiàng)目可以在解決方案中多次出現(xiàn)。
在實(shí)現(xiàn)中,我們將使用舊的類(lèi)Element,其中添加了私有字段value,用于存儲(chǔ)給定子問(wèn)題的最大可能值:
public class Element { private boolean exists; private boolean includes; private int value; // appropriate constructors, getters and setters}實(shí)現(xiàn)非常相似,唯一的區(qū)別是現(xiàn)在我們必須根據(jù)結(jié)果值選擇最佳解決方案:
public static void main(String[] args) { // Same code as before with the addition of the values[] array System.out.println("Insert values: "); int[] values = new int[n + 1]; for (int i=1; i <= n; i++) { values[i] = scanner.nextInt(); } Element[][] elementMatrix = new Element[n + 1][k + 1]; // A matrix that indicates how many newest objects are used // in the optimal solution. // Example: contains[5][10] indicates how many objects with // the weight of W[5] are contained in the optimal solution // for a knapsack of capacity K=10 int[][] contains = new int[n + 1][k + 1]; elementMatrix[0][0] = new Element(0); for (int i = 1; i <= n; i++) { elementMatrix[i][0] = new Element(0); contains[i][0] = 0; } for (int i = 1; i <= k; i++) { elementMatrix[0][i] = new Element(0); contains[0][i] = 0; } for (int i = 1; i <= n; i++) { for (int j = 0; j <= k; j++) { elementMatrix[i][j] = new Element(elementMatrix[i - 1][j].getValue()); contains[i][j] = 0; elementMatrix[i][j].setIncludes(false); elementMatrix[i][j].setValue(M[i - 1][j].getValue()); if (j >= weights[i]) { if ((elementMatrix[i][j - weights[i]].getValue() > 0 || j == weights[i])) { if (elementMatrix[i][j - weights[i]].getValue() + values[i] > M[i][j].getValue()) { elementMatrix[i][j].setIncludes(true); elementMatrix[i][j].setValue(M[i][j - weights[i]].getValue() + values[i]); contains[i][j] = contains[i][j - weights[i]] + 1; } } } System.out.print(elementMatrix[i][j].getValue() + "/" + contains[i][j] + " "); } System.out.println(); } System.out.println("Value: " + elementMatrix[n][k].getValue());}輸出:
Insert knapsack capacity: 12 Insert number of items: 5 Insert weights: 9 7 4 10 3 Insert values: 1 2 3 4 5 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 1/1 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 2/1 0/0 1/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 3/1 0/0 0/0 2/0 6/2 1/0 0/0 5/1 9/3 0/0 0/0 0/0 0/0 3/0 0/0 0/0 2/0 6/0 1/0 4/1 5/0 9/0 0/0 0/0 0/0 5/1 3/0 0/0 10/2 8/1 6/0 15/3 13/2 11/1 20/4 Value: 206. Levenshtein Distance
另一個(gè)使用動(dòng)態(tài)規(guī)劃的非常好的例子是Edit Distance或Levenshtein Distance。
Levenshtein Distance就是兩個(gè)字符串A,B,我們需要使用原子操作將A轉(zhuǎn)換為B:
這個(gè)問(wèn)題是通過(guò)有條理地解決起始字符串的子串的問(wèn)題來(lái)處理的,逐漸增加子字符串的大小,直到它們等于起始字符串。
我們用于此問(wèn)題的遞歸關(guān)系如下:
如果a == b則c(a,b)為0,如果a = = b則c(a,b)為1。
實(shí)現(xiàn):
public class editDistance { public static void main(String[] args) { String s1, s2; Scanner scanner = new Scanner(System.in); System.out.println("Insert first string:"); s1 = scanner.next(); System.out.println("Insert second string:"); s2 = scanner.next(); int n, m; n = s1.length(); m = s2.length(); // Matrix of substring edit distances // example: distance[a][b] is the edit distance // of the first a letters of s1 and b letters of s2 int[][] distance = new int[n + 1][m + 1]; // Matrix initialization: // If we want to turn any string into an empty string // the fastest way no doubt is to just delete // every letter individually. // The same principle applies if we have to turn an empty string // into a non empty string, we just add appropriate letters // until the strings are equal. for (int i = 0; i <= n; i++) { distance[i][0] = i; } for (int j = 0; j <= n; j++) { distance[0][j] = j; } // Variables for storing potential values of current edit distance int e1, e2, e3, min; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { e1 = distance[i - 1][j] + 1; e2 = distance[i][j - 1] + 1; if (s1.charAt(i - 1) == s2.charAt(j - 1)) { e3 = distance[i - 1][j - 1]; } else { e3 = distance[i - 1][j - 1] + 1; } min = Math.min(e1, e2); min = Math.min(min, e3); distance[i][j] = min; } } System.out.println("Edit distance of s1 and s2 is: " + distance[n][m]); }}輸出:
Insert first string: man Insert second string: machine Edit distance of s1 and s2 is: 3如果你想了解更多關(guān)于Levenshtein Distance的解決方案,我們?cè)诹硗獾囊黄恼轮杏胮ython實(shí)現(xiàn)了 Levenshtein Distance and Text Similarity in Python, 使用這個(gè)邏輯,我們可以將許多字符串比較算法歸結(jié)為簡(jiǎn)單的遞歸關(guān)系,它使用Levenshtein Distance的基本公式
7. 最長(zhǎng)共同子序列(LCS)
這個(gè)問(wèn)題描述如下:
Given two sequences, find the length of the longest subsequence present in both of them. A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous.給定兩個(gè)序列,找到兩個(gè)序列中存在的最長(zhǎng)子序列的長(zhǎng)度。子序列是以相同的相對(duì)順序出現(xiàn)的序列,但不一定是連續(xù)的.
闡明:
如果我們有兩個(gè)字符串s1="MICE"和s2="MINCE
總結(jié)
以上是生活随笔為你收集整理的c++矩阵连乘的动态规划算法并输出_你在Java中用过动态规划吗?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: seo vue 动态路由_VUE项目SE
- 下一篇: vue点击其它侧边栏收缩_企业微信聊天侧