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

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

生活随笔

當(dāng)前位置: 首頁(yè) >

矩阵连乘 动态规划_Java动态规划

發(fā)布時(shí)間:2023/12/2 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 矩阵连乘 动态规划_Java动态规划 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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ī)劃的概念,我們需要熟悉一些主題:

  • 什么是動(dòng)態(tài)規(guī)劃?
  • 貪心算法
  • 簡(jiǎn)化的背包問(wèn)題
  • 傳統(tǒng)的背包問(wèn)題
  • Levenshtein Distance
  • LCS-最長(zhǎng)的共同子序列
  • 利用動(dòng)態(tài)規(guī)劃的其他問(wèn)題
  • 結(jié)論
  • 本文所有代碼均為java代碼實(shí)現(xiàn)。

    2. 什么是動(dòng)態(tài)規(guī)劃?

    動(dòng)態(tài)規(guī)劃是一種編程原理,可以通過(guò)將非常復(fù)雜的問(wèn)題劃分為更小的子問(wèn)題來(lái)解決。這個(gè)原則與遞歸很類似,但是與遞歸有一個(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),其他的求解諸如此類。

    這樣的話就會(huì)導(dǎo)致很多重復(fù)的計(jì)算,這些重復(fù)計(jì)算本質(zhì)上是冗余的,并且明顯的減慢了算法的效率。為了解決這種問(wèn)題,我們介紹動(dòng)態(tài)規(guī)劃。

    在這種方法中,我們對(duì)解決方案進(jìn)行建模,就像我們要遞歸地解決它一樣,但我們從頭開始解決它,記憶到達(dá)頂部采取的子問(wèn)題(子步驟)的解決方案。

    因此,對(duì)于Fibonacci序列,我們首先求解并記憶F(1)和F(2),然后使用兩個(gè)記憶步驟計(jì)算F(3),依此類推。這意味著序列中每個(gè)單獨(dú)元素的計(jì)算都是O(1),因?yàn)槲覀円呀?jīng)知道前兩個(gè)元素。

    當(dāng)使用動(dòng)態(tài)規(guī)劃解決問(wèn)題的時(shí)候,我們一般會(huì)采用下面三個(gè)步驟:

  • 確定適用于所述問(wèn)題的遞歸關(guān)系
  • 初始化內(nèi)存、數(shù)組、矩陣的初始值
  • 確保當(dāng)我們進(jìn)行遞歸調(diào)用(可以訪問(wèn)子問(wèn)題的答案)的時(shí)候它總是被提前解決。
  • 遵循這些規(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)的開發(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ò)從頭開始解決子問(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)題分為兩種情況:

  • 對(duì)于容量k,當(dāng)存在第一個(gè)i-1元素的解決方案
  • 對(duì)于容量k-W [i],當(dāng)?shù)谝粋€(gè)i-1元素存在解決方案
  • 第一種情況是不言自明的,我們已經(jīng)有了問(wèn)題的解決方案。

    第二種情況是指了解第一個(gè)i-1元素的解決方案,但是容量只有一個(gè)第i個(gè)元素不滿,這意味著我們可以添加一個(gè)第i個(gè)元素,并且我們有一個(gè)新的解決方案!

    4.3. 實(shí)現(xiàn)

    下面這何種實(shí)現(xiàn)方式,使得事情變得更加容易,我們創(chuàng)建了一個(gè)類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; }}

    接著,我們可以深入了解主要的類:

    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()); }}

    唯一剩下的就是解決方案的重建,在上面的類中,我們知道解決方案是存在的,但是我們不知道它是什么。

    為了重建,我們使用下面的代碼:

    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)中,我們將使用舊的類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: 20

    6. Levenshtein Distance

    另一個(gè)使用動(dòng)態(tài)規(guī)劃的非常好的例子是Edit Distance或Levenshtein Distance。

    Levenshtein Distance就是兩個(gè)字符串A,B,我們需要使用原子操作將A轉(zhuǎn)換為B:

  • 字符串刪除
  • 字符串插入
  • 字符替換(從技術(shù)上講,它不止一個(gè)操作,但為了簡(jiǎn)單起見,我們稱之為原子操作)
  • 這個(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é)

    以上是生活随笔為你收集整理的矩阵连乘 动态规划_Java动态规划的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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