This commit is contained in:
krahets
2026-08-18 03:09:26 +08:00
parent 0375f25b66
commit fde23f369e
158 changed files with 312 additions and 282 deletions
@@ -4658,7 +4658,7 @@
<li>動態規劃也對問題進行遞迴分解,但與分治演算法的主要區別是,動態規劃中的子問題是相互依賴的,在分解過程中會出現許多重疊子問題。</li>
<li>回溯演算法在嘗試和回退中窮舉所有可能的解,並透過剪枝避免不必要的搜尋分支。原問題的解由一系列決策步驟構成,我們可以將每個決策步驟之前的子序列看作一個子問題。</li>
</ul>
<p>實際上,動態規劃常用來求解最佳化問題,它們不僅包含重疊子問題,還具有另外兩大特性:最優子結構、無後效性。</p>
<p>實際上,動態規劃常用來求解最佳化問題,它們不僅包含重疊子問題,還具有另外兩大特性:最優子結構、無後效性。</p>
<h2 id="1421">14.2.1 &nbsp; 最優子結構<a class="headerlink" href="#1421" title="Permanent link">&para;</a></h2>
<p>我們對爬樓梯問題稍作改動,使之更加適合展示最優子結構概念。</p>
<div class="admonition question">
@@ -4763,7 +4763,7 @@
<p>換句話說,如果問題包含明確的決策概念,並且解是透過一系列決策產生的,那麼它就滿足決策樹模型,通常可以使用回溯來解決。</p>
<p>在此基礎上,動態規劃問題還有一些判斷的“加分項”。</p>
<ul>
<li>問題包含最大(小)或最多(少)等最佳化描述。</li>
<li>問題包含最大(小)或最多(少)等最佳化描述。</li>
<li>問題的狀態能夠使用一個串列、多維矩陣或樹來表示,並且一個狀態與其周圍的狀態存在遞推關係。</li>
</ul>
<p>相應地,也存在一些“減分項”。</p>
@@ -4700,7 +4700,7 @@
</ul>
<p>也就是說,我們在字串 <span class="arithmatex">\(s\)</span> 中進行的每一輪決策(編輯操作),都會使得 <span class="arithmatex">\(s\)</span><span class="arithmatex">\(t\)</span> 中剩餘的待匹配字元發生變化。因此,狀態為當前在 <span class="arithmatex">\(s\)</span><span class="arithmatex">\(t\)</span> 中考慮的第 <span class="arithmatex">\(i\)</span> 和第 <span class="arithmatex">\(j\)</span> 個字元,記為 <span class="arithmatex">\([i, j]\)</span></p>
<p>狀態 <span class="arithmatex">\([i, j]\)</span> 對應的子問題:<strong><span class="arithmatex">\(s\)</span> 的前 <span class="arithmatex">\(i\)</span> 個字元更改為 <span class="arithmatex">\(t\)</span> 的前 <span class="arithmatex">\(j\)</span> 個字元所需的最少編輯步數</strong></p>
<p>至此,得到一個尺寸為 <span class="arithmatex">\((i+1) \times (j+1)\)</span> 的二維 <span class="arithmatex">\(dp\)</span> 表。</p>
<p>至此,得到一個尺寸為 <span class="arithmatex">\((n+1) \times (m+1)\)</span> 的二維 <span class="arithmatex">\(dp\)</span> 表。</p>
<p><strong>第二步:找出最優子結構,進而推導出狀態轉移方程</strong></p>
<p>考慮子問題 <span class="arithmatex">\(dp[i, j]\)</span> ,其對應的兩個字串的尾部字元為 <span class="arithmatex">\(s[i-1]\)</span><span class="arithmatex">\(t[j-1]\)</span> ,可根據不同編輯操作分為圖 14-29 所示的三種情況。</p>
<ol>
@@ -5709,7 +5709,7 @@ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1])
<p align="center"> 圖 14-20 &nbsp; 0-1 背包問題的動態規劃過程 </p>
<h3 id="4">4. &nbsp; 空間最佳化<a class="headerlink" href="#4" title="Permanent link">&para;</a></h3>
<p>由於每個狀態都只與其上一行的狀態有關,因此我們可以使用兩個陣列滾動前進,將空間複雜度從 <span class="arithmatex">\(O(n^2)\)</span> 降至 <span class="arithmatex">\(O(n)\)</span></p>
<p>由於每個狀態都只與其上一行的狀態有關,因此我們可以使用兩個陣列滾動前進,將空間複雜度從 <span class="arithmatex">\(O(n \times cap)\)</span> 降至 <span class="arithmatex">\(O(cap)\)</span></p>
<p>進一步思考,我們能否僅用一個陣列實現空間最佳化呢?觀察可知,每個狀態都是由正上方或左上方的格子轉移過來的。假設只有一個陣列,當開始走訪第 <span class="arithmatex">\(i\)</span> 行時,該陣列儲存的仍然是第 <span class="arithmatex">\(i-1\)</span> 行的狀態。</p>
<ul>
<li>如果採取正序走訪,那麼走訪到 <span class="arithmatex">\(dp[i, j]\)</span> 時,左上方 <span class="arithmatex">\(dp[i-1, 1]\)</span> ~ <span class="arithmatex">\(dp[i-1, j-1]\)</span> 值可能已經被覆蓋,此時就無法得到正確的狀態轉移結果。</li>
@@ -4651,7 +4651,7 @@
<p><strong>編輯距離問題</strong></p>
<ul>
<li>編輯距離(Levenshtein 距離)用於衡量兩個字串之間的相似度,其定義為從一個字串到另一個字串的最少編輯步數,編輯操作包括新增、刪除、替換。</li>
<li>編輯距離問題的狀態定義為將 <span class="arithmatex">\(s\)</span> 的前 <span class="arithmatex">\(i\)</span> 個字元更改為 <span class="arithmatex">\(t\)</span> 的前 <span class="arithmatex">\(j\)</span> 個字元所需的最少編輯步數。當 <span class="arithmatex">\(s[i] \ne t[j]\)</span> 時,具有三種決策:新增、刪除、替換,它們都有相應的剩餘子問題。據此便可以找出最優子結構與構建狀態轉移方程。而當 <span class="arithmatex">\(s[i] = t[j]\)</span> 時,無須編輯當前字元。</li>
<li>編輯距離問題的狀態定義為將 <span class="arithmatex">\(s\)</span> 的前 <span class="arithmatex">\(i\)</span> 個字元更改為 <span class="arithmatex">\(t\)</span> 的前 <span class="arithmatex">\(j\)</span> 個字元所需的最少編輯步數。當 <span class="arithmatex">\(s[i-1] \ne t[j-1]\)</span> 時,具有三種決策:新增、刪除、替換,它們都有相應的剩餘子問題。據此便可以找出最優子結構與構建狀態轉移方程。而當 <span class="arithmatex">\(s[i-1] = t[j-1]\)</span> 時,無須編輯當前字元。</li>
<li>在編輯距離中,狀態依賴其正上方、正左方、左上方的狀態,因此空間最佳化後正序或倒序走訪都無法正確地進行狀態轉移。為此,我們利用一個變數暫存左上方狀態,從而轉化到與完全背包問題等價的情況,可以在空間最佳化後進行正序走訪。</li>
</ul>