<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>最优化 on Zeqiang Fang | 方泽强</title><link>https://zeqiang.fun/categories/%E6%9C%80%E4%BC%98%E5%8C%96/</link><description>Recent content in 最优化 on Zeqiang Fang | 方泽强</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sat, 06 Jun 2020 00:00:00 +0000</lastBuildDate><atom:link href="https://zeqiang.fun/categories/%E6%9C%80%E4%BC%98%E5%8C%96/" rel="self" type="application/rss+xml"/><item><title>贝叶斯优化 (Bayesian Optimization)</title><link>https://zeqiang.fun/cn/2020/06/bayesian-optimization/</link><pubDate>Sat, 06 Jun 2020 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2020/06/bayesian-optimization/</guid><description><![CDATA[
        <blockquote>
<p>本文内容主要参考自：</p>
<ol>
<li><a href="https://zhuanlan.zhihu.com/p/139478368">从高斯分布到高斯过程、高斯过程回归、贝叶斯优化</a></li>
<li><a href="https://distill.pub/2019/visual-exploration-gaussian-processes/">A Visual Exploration of Gaussian Processes</a></li>
<li><a href="https://www.aidanscannell.com/post/gaussian-process-regression/">Gaussian Process Regression</a></li>
<li><a href="https://distill.pub/2020/bayesian-optimization/">Exploring Bayesian Optimization</a></li>
</ol>
</blockquote>
<h2 id="高斯分布">高斯分布</h2>
<h3 id="一元高斯分布">一元高斯分布</h3>
<p>若随机变量 <code>$X$</code> 服从一个均值为 <code>$\mu$</code>，方差为 <code>$\sigma^2$</code> 的高斯分布，则记为：</p>
<p><code>$$ X \sim N \left(\mu, \sigma^2\right) $$</code></p>
<p>其概率密度函数为：</p>
<p><code>$$ f \left(x\right) = \dfrac{1}{\sigma \sqrt{2 \pi}} e^{- \dfrac{\left(x - \mu\right)^2}{2 \sigma^2}} $$</code></p>
<figure>
  <img data-src="/images/cn/2020-06-06-bayesian-optimization/univariate-gaussian-distribution.png" class="lazyload"/>
  <figcaption><p class="figcaption">图片来源：https://zh.wikipedia.org/wiki/正态分布</p></figcaption>
</figure>
<h3 id="二元高斯分布">二元高斯分布</h3>
<p>若随机变量 <code>$X, Y$</code> 服从均值为 <code>$\mu = \left(\mu_X, \mu_Y\right)^{\top}$</code>，方差为 <code>$\mu = \left(\sigma_X, \sigma_Y\right)^{\top}$</code> 的高斯分布，则记为：</p>
<p><code>$$ \left(X, Y\right) \sim \mathcal{N} \left(\mu, \sigma\right) $$</code></p>
<p>其概率密度函数为：</p>
<p><code>$$ f(x, y)=\frac{1}{2 \pi \sigma_{X} \sigma_{Y} \sqrt{1-\rho^{2}}} e^{-\dfrac{1}{2\left(1-\rho^{2}\right)}\left[\dfrac{\left(x-\mu_{X}\right)^{2}}{\sigma_{X}^{2}}+\dfrac{\left(y-\mu_{Y}\right)^{2}}{\sigma_{Y}^{2}}-\dfrac{2 \rho\left(x-\mu_{X}\right)\left(y-\mu_{X}\right)}{\sigma_{X} \sigma_{Y}}\right]} $$</code></p>
<p>其中，<code>$\rho$</code> 是 <code>$X$</code> 和 <code>$Y$</code> 之间的相关系数，<code>$\sigma_X &gt; 0$</code> 且 <code>$\sigma_Y &gt; 0$</code>。</p>
<figure>
  <img data-src="/images/cn/2020-06-06-bayesian-optimization/bivariate-gaussian-distribution.png" class="lazyload"/>
  <figcaption><p class="figcaption">图片来源：Bayesian tracking of multiple point targets using expectation maximization</p></figcaption>
</figure>
<h3 id="多元高斯分布">多元高斯分布</h3>
<p>若 <code>$K$</code> 维随机向量 <code>$X = \left[X_1, \cdots, X_K\right]^{\top}$</code> 服从多元高斯分布，则必须满足如下三个等价条件：</p>
<ol>
<li>任何线性组合 <code>$Y = a_1 X_1 + \cdots a_K X_K$</code> 均服从高斯分布。</li>
<li>存在随机向量 <code>$Z = \left[Z_1, \cdots, Z_L\right]^{\top}$</code>（每个元素服从独立标准高斯分布），向量 <code>$\mu = \left[\mu_1, \cdots, \mu_K\right]^{\top}$</code> 以及 <code>$K \times L$</code> 的矩阵 <code>$A$</code>，满足 <code>$X = A Z + \mu$</code>。</li>
<li>存在 <code>$\mu$</code> 和一个对称半正定矩阵 <code>$\Sigma$</code> 满足 <code>$X$</code> 的特征函数 <code>$\phi_X \left(u; \mu, \Sigma\right) = \exp \left(i \mu^{\top} u - \dfrac{1}{2} u^{\top} \Sigma u\right)$</code></li>
</ol>
<p>如果 <code>$\Sigma$</code> 是非奇异的，则概率密度函数为：</p>
<p><code>$$ f \left(x_1, \cdots, x_k\right) = \dfrac{1}{\sqrt{\left(2 \pi\right)^k \lvert\Sigma\rvert}} e^{- \dfrac{1}{2} \left(x - \mu\right)^{\top} \Sigma^{-1} \left(x - \mu\right)} $$</code></p>
<p>其中 <code>$\lvert\Sigma\rvert$</code> 表示协方差矩阵的行列式。</p>
<h3 id="边缘化和条件化">边缘化和条件化</h3>
<p>高斯分布具有一个优秀的代数性质，即在边缘化和条件化下是闭合的，也就是说从这些操作中获取的结果分布也是高斯的。**边缘化（Marginalization）<strong>和</strong>条件化（Conditioning）**都作用于原始分布的子集上：</p>
<p><code>$$ P_{X, Y}=\left[\begin{array}{l} X \\ Y \end{array}\right] \sim \mathcal{N}(\mu, \Sigma)=\mathcal{N}\left(\left[\begin{array}{l} \mu_{X} \\ \mu_{Y} \end{array}\right],\left[\begin{array}{l} \Sigma_{X X} \Sigma_{X Y} \\ \Sigma_{Y X} \Sigma_{Y Y} \end{array}\right]\right) $$</code></p>
<p>其中，<code>$X$</code> 和 <code>$Y$</code> 表示原始随机变量的子集。</p>
<p>对于随机向量 <code>$X$</code> 和 <code>$Y$</code> 的高斯概率分布 <code>$P \left(X, Y\right)$</code>，其边缘概率分布为：</p>
<p><code>$$ \begin{array}{l} X \sim \mathcal{N}\left(\mu_{X}, \Sigma_{X X}\right) \\ Y \sim \mathcal{N}\left(\mu_{Y}, \Sigma_{Y Y}\right) \end{array} $$</code></p>
<p><code>$X$</code> 和 <code>$Y$</code> 两个子集各自只依赖于 <code>$\mu$</code> 和 <code>$\Sigma$</code> 中它们对应的值。因此从高斯分布中边缘化一个随机变量仅需从 <code>$\mu$</code> 和 <code>$\Sigma$</code> 中舍弃相应的变量即可：</p>
<p><code>$$ p_{X}(x)=\int_{y} p_{X, Y}(x, y) d y=\int_{y} p_{X | Y}(x | y) p_{Y}(y) d y $$</code></p>
<p>条件化可以用于得到一个变量在另一个变量条件下的概率分布：</p>
<p><code>$$ \begin{array}{l} X | Y \sim \mathcal{N}\left(\mu_{X}+\Sigma_{X Y} \Sigma_{Y Y}^{-1}\left(Y-\mu_{Y}\right), \Sigma_{X X}-\Sigma_{X Y} \Sigma_{Y Y}^{-1} \Sigma_{Y X}\right) \\ Y | X \sim \mathcal{N}\left(\mu_{Y}+\Sigma_{Y X} \Sigma_{X X}^{-1}\left(X-\mu_{X}\right), \Sigma_{Y Y}-\Sigma_{Y X} \Sigma_{X X}^{-1} \Sigma_{X Y}\right) \end{array} $$</code></p>
<p>需要注意新的均值仅依赖于作为条件的变量，协方差矩阵和这个变量无关。</p>
<p>边缘化可以理解为在高斯分布的一个维度上的累加，条件化可以理解为在多元分布上切一刀从而获得一个维数更少的高斯分布，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-06-06-bayesian-optimization/marginalization-and-conditioning.png" class="lazyload"/>
  
</figure>
<h2 id="高斯过程">高斯过程</h2>
<p>**高斯过程（Gaussian Process）**是观测值出现在一个连续域（例如时间或空间）的随机过程。在高斯过程中，连续输入空间中每个点都是与一个正态分布的随机变量相关联。此外，这些随机变量的每个有限集合都有一个多元正态分布，换句话说它们的任意有限线性组合是一个正态分布。高斯过程的分布是所有那些（无限多个）随机变量的联合分布，正因如此，它是连续域（例如时间或空间）上函数的分布。</p>
<p>简单而言，高斯过程即为一系列随机变量，这些随机变量的任意有限集合均为一个多元高斯分布。从<strong>一元高斯分布</strong>到<strong>多元高斯分布</strong>相当于增加了空间维度，从<strong>高斯分布</strong>到<strong>高斯过程</strong>相当于引入了时间维度。一个高斯过程可以被均值函数 <code>$m \left(x\right)$</code> 和协方差函数 <code>$K \left(x, x'\right)$</code> 共同唯一确定：</p>
<p><code>$$ \begin{aligned} m(x) &amp;=\mathbb{E}[f(x)] \\ K\left(x, x'\right) &amp;=\mathbb{E}\left[(f(x)-m(x))\left(f\left(x^{\prime}\right)-m\left(x^{\prime}\right)\right)\right] \end{aligned} $$</code></p>
<p>则高斯过程可以表示为：</p>
<p><code>$$ f \left(x\right) \sim \mathcal{GP} \left(m \left(x\right), K \left(x, x'\right)\right) $$</code></p>
<p>均值函数决定了样本出现的整体位置，如果为零则表示以 <code>$y = 0$</code> 为基准线。协方差函数描述了不同点之间的关系，从而可以利用输入的训练数据预测未知点的值。常用的协方差函数有：</p>
<ul>
<li>常数：<code>$K_c \left(x, x'\right) = C$</code></li>
<li>线性：<code>$K_L \left(x, x'\right) = x^{\top} x'$</code></li>
<li>高斯噪声：<code>$K_{GN} \left(x, x'\right) = \sigma^2 \delta_{x, x'}$</code></li>
<li>指数平方：<code>$K_{\mathrm{SE}}\left(x, x^{\prime}\right)=\exp \left(-\dfrac{|d|^{2}}{2 \ell^{2}}\right)$</code></li>
<li>Ornstein-Uhlenbeck：<code>$K_{\mathrm{OU}}\left(x, x^{\prime}\right)=\exp \left(-\dfrac{|d|}{\ell}\right)$</code></li>
<li>Matérn：<code>$K_{\text {Matern }}\left(x, x^{\prime}\right)=\dfrac{2^{1-\nu}}{\Gamma(\nu)}\left(\dfrac{\sqrt{2 \nu}|d|}{\ell}\right)^{\nu} K_{\nu}\left(\dfrac{\sqrt{2 \nu}|d|}{\ell}\right)$</code></li>
<li>周期：<code>$K_{\mathrm{P}}\left(x, x^{\prime}\right)=\exp \left(-\dfrac{2 \sin ^{2}\left(\dfrac{d}{2}\right)}{\ell^{2}}\right)$</code></li>
<li>有理平方：<code>$K_{\mathrm{RQ}}\left(x, x^{\prime}\right)=\left(1+|d|^{2}\right)^{-\alpha}, \quad \alpha \geq 0$</code></li>
</ul>
<h2 id="高斯过程回归">高斯过程回归</h2>
<p>回归任务的目标是给定一个输入变量 <code>$x \in \mathbb{R}^D$</code> 预测一个或多个连续目标变量 <code>$y$</code> 的值。更确切的说，给定一个包含 <code>$N$</code> 个观测值的训练集 <code>$\mathbf{X} = \left\{x_n\right\}^N_1$</code> 和对应的目标值 <code>$\mathbf{Y} = \left\{y_n\right\}^N_1$</code>，回归的目标是对于一个新的 <code>$x$</code> 预测对应的 <code>$y$</code>。目标值和观测值之间通过一个映射进行关联：</p>
<p><code>$$ f: X \to Y $$</code></p>
<p>在贝叶斯模型中，我们通过观测数据 <code>$\mathcal{D} = \left\{\left(\mathbf{x}_n, \mathbf{y}_n\right)\right\}^N_{n=1}$</code> 更新先验分布 <code>$P \left(\mathbf{\Theta}\right)$</code>。通过贝叶斯公式我们可以利用先验概率 <code>$P \left(\mathbf{\Theta}\right)$</code> 和似然函数 <code>$P \left(\mathcal{D} | \mathbf{\Theta}\right)$</code> 推导出后验概率：</p>
<p><code>$$ p\left(\mathbf{\Theta} | \mathcal{D}\right)=\frac{p\left(\mathcal{D} | \mathbf{\Theta}\right) p\left(\mathbf{\Theta}\right)}{p\left(\mathcal{D}\right)} $$</code></p>
<p>其中 <code>$p\left(\mathcal{D}\right)$</code> 为边际似然。在贝叶斯回归中我们不仅希望获得未知输入对应的预测值 <code>$\mathbf{y}_*$</code> ，还希望知道预测的不确定性。因此我们需要利用联合分布和边缘化模型参数 <code>$\mathbf{\Theta}$</code> 来构造预测分布：</p>
<p><code>$$ p\left(\mathbf{y}_{*} | \mathbf{x}_{*}, \mathcal{D}\right)=\int p\left(\mathbf{y}_{*}, \mathbf{\Theta} | \mathbf{x}_{*}, \mathcal{D}\right) \mathrm{d} \Theta=\int p\left(\mathbf{y}_{*} | \mathbf{x}_{*}, \mathbf{\Theta}, \mathcal{D}\right) p(\mathbf{\Theta} | \mathcal{D}) \mathrm{d} \mathbf{\Theta} $$</code></p>
<p>通常情况下，由于积分形式 <code>$p \left(\Theta | \mathcal{D}\right)$</code> 不具有解析可解性（Analytically Tractable）：</p>
<p><code>$$ p\left(\mathcal{D}\right)=\int p\left(\mathcal{D} | \mathbf{\Theta}\right) p\left(\mathbf{\Theta}\right) d \Theta $$</code></p>
<p>但在高斯似然和高斯过程先验的前提下，后验采用函数的高斯过程的形式，同时是解析可解的。</p>
<p>对于高斯过程回归，我们构建一个贝叶斯模型，首先定义函数输出的先验为一个高斯过程：</p>
<p><code>$$ p \left(f | \mathbf{X}, \theta\right) = \mathcal{N} \left(\mathbf{0}, K \left(\mathbf{X}, \mathbf{X}\right)\right) $$</code></p>
<p>其中 <code>$K \left(\cdot, \cdot\right)$</code> 为协方差函数，<code>$\theta$</code> 为过程的超参数。假设数据已经变换为零均值，因此我们不需要在先验中设置均值函数，则令似然形式如下：</p>
<p><code>$$ p \left(\mathbf{Y} | f\right) \sim \mathcal{N} \left(f, \sigma^2_n \mathbf{I}\right) $$</code></p>
<p>假设观测值为独立同分布的高斯噪音的累加，则整个模型的联合分布为：</p>
<p><code>$$ p \left(\mathbf{Y} , f | \mathbf{X}, \theta\right) = p \left(\mathbf{Y} | f\right) p \left(f | \mathbf{X}, \theta\right) $$</code></p>
<p>虽然我们并不关心变量 <code>$f$</code>，但由于我们需要对不确定性进行建模，我们仍需考虑 <code>$\mathbf{Y}$</code> 和 <code>$f$</code> 以及 <code>$f$</code> 和 <code>$\mathbf{X}$</code> 之间的关系。高斯过程作为一个非参数模型，其先验分布构建于映射 <code>$f$</code> 之上，<code>$f$</code> 仅依赖于核函数的超参数 <code>$\theta$</code>，且这些超参数可以通过数据进行估计。我们可以将超参数作为先验，即：</p>
<p><code>$$ p \left(\mathbf{Y} , f | \mathbf{X}, \theta\right) = p \left(\mathbf{Y} | f\right) p \left(f | \mathbf{X}, \theta\right) p \left(\theta\right) $$</code></p>
<p>然后进行贝叶斯推断和模型选择，但是通常情况下这是不可解的。David MacKay 引入了一个利用最优化边际似然来估计贝叶斯平均的框架，即计算如下积分：</p>
<p><code>$$ p \left(\mathbf{Y} | \mathbf{X}, \theta\right) = \int p \left(\mathbf{Y} | f\right) p \left(f | \mathbf{X}, \theta\right) df $$</code></p>
<p>其中，高斯似然 <code>$p \left(\mathbf{Y} | f\right)$</code> 表示模型拟合数据的程度，<code>$p \left(f | \mathbf{X}, \theta\right)$</code> 为高斯过程先验。经过边缘化后，<code>$\mathbf{Y}$</code> 不在依赖于 <code>$f$</code> 而仅依赖于 <code>$\theta$</code>。</p>
<p>假设采用零均值函数，对于一个高斯过程先验，我们仅需指定一个协方差函数。以指数平方协方差函数为例，选择一系列测试输入点 <code>$X_*$</code>，利用协方差矩阵和测试输入点可以生成一个高斯向量：</p>
<p><code>$$ \mathbf{f}_* \sim \mathcal{N} \left(\mathbf{0}, K \left(X_*, X_*\right)\right) $$</code></p>
<p>从高斯先验中进行采样，我们首先需要利用标准正态来表示多元正态：</p>
<p><code>$$ \mathbf{f}_* \sim \mu + \mathbf{B} \mathcal{N} \left(0, \mathbf{I}\right) $$</code></p>
<p>其中，<code>$\mathbf{BB}^{\top} = K \left(X_*, X_*\right)$</code>，<code>$\mathbf{B}$</code> 本质上是协方差矩阵的平方根，可以通过 <a href="https://zh.wikipedia.org/wiki/Cholesky%E5%88%86%E8%A7%A3">Cholesky 分解</a>获得。</p>
<figure>
  <img data-src="/images/cn/2020-06-06-bayesian-optimization/gp-prior.png" class="lazyload"/>
  
</figure>
<p>上图（左）为从高斯先验中采样的 10 个序列，上图（右）为先验的协方差。如果输入点 <code>$x_n$</code> 和 <code>$x_m$</code> 接近，则对应的 <code>$f \left(x_n\right)$</code> 和 <code>$f \left(x_m\right)$</code> 相比于不接近的点是强相关的。</p>
<p>我们关注的并不是这些随机的函数，而是如何将训练数据中的信息同先验进行合并。假设观测数据为 <code>$\left\{\left(\mathbf{x}_{i}, f_{i}\right) | i=1, \ldots, n\right\}$</code>，则训练目标 <code>$\mathbf{f}$</code> 和测试目标 <code>$\mathbf{f}_*$</code> 之间的联合分布为：</p>
<p><code>$$ \left[\begin{array}{l} \mathbf{f} \\ \mathbf{f}_{*} \end{array}\right] \sim \mathcal{N}\left(\mathbf{0},\left[\begin{array}{ll} K(X, X) &amp; K\left(X, X_{*}\right) \\ K\left(X_{*}, X\right) &amp; K\left(X_{*}, X_{*}\right) \end{array}\right]\right) $$</code></p>
<p>根据观测值对联合高斯先验分布进行条件化处理可以得到高斯过程回归的关键预测方程：</p>
<p><code>$$ \mathbf{f}_{*} | X, X_{*}, \mathbf{f} \sim \mathcal{N}\left(\overline{\mathbf{f}}_{*}, \operatorname{cov}\left(\mathbf{f}_{*}\right)\right) $$</code></p>
<p>其中</p>
<p><code>$$ \begin{aligned} \overline{\mathbf{f}}_{*} &amp; \triangleq \mathbb{E}\left[\mathbf{f}_{*} | X, X_{*}, \mathbf{f}\right]=K\left(X_{*}, X\right) K(X, X)^{-1} \mathbf{f} \\ \operatorname{cov}\left(\mathbf{f}_{*}\right) &amp;=K\left(X_{*}, X_{*}\right)-K\left(X_{*}, X\right) K(X, X)^{-1} K\left(X, X_{*}\right) \end{aligned} $$</code></p>
<p>函数值可以通过对联合后验分布采样获得。</p>
<p>我们以三角函数作为给定的函数，并随机采样一些训练数据 <code>$\left\{\left(\mathbf{x}_{i}, f_{i}\right) | i=1, \ldots, n\right\}$</code>，如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-06-06-bayesian-optimization/underlying-functions-and-training-points.png" class="lazyload"/>
  
</figure>
<p>我们希望将训练数据和高斯过程先验进行合并得到联合后验分布，我们可以通过在观测值上条件化联合高斯先验分布，预测的均值和协方差为：</p>
<p><code>$$ \begin{aligned} \overline{\mathbf{f}}_{*} &amp;=K\left(X_{*}, X\right) K(X, X)^{-1} \mathbf{f} \\ \operatorname{cov}\left(\mathbf{f}_{*}\right) &amp;=K\left(X_{*}, X_{*}\right)-K\left(X_{*}, X\right) K(X, X)^{-1} K\left(X, X_{*}\right) \end{aligned} $$</code></p>
<p><a href="http://www.gaussianprocess.org/gpml/">Rasmussen 和 Williams</a> 给出了一个实现高斯过程回归的实用方法：</p>


<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/pseudocode@latest/build/pseudocode.min.css">


<div><pre class="pseudocode">
\begin{algorithm}
\caption{高斯过程回归算法}
\begin{algorithmic}
\REQUIRE \\
    输入 $\mathbf{X}$ \\
    目标 $\mathbf{y}$ \\
    协方差函数 $k$ \\
    噪音水平 $\sigma^2_n$ \\
    测试输入 $\mathbf{x}_*$
\ENSURE \\
    均值 $\bar{f}_*$ \\
    方差 $\mathbb{V}\left[f_{*}\right]$
\FUNCTION{GaussianProcessRegression}{$\mathbf{X}, \mathbf{y}, k, \sigma^2_n, \mathbf{x}_*$}
\STATE $L \gets \text{cholesky} \left(K + \sigma^2_n I\right)$
\STATE $\alpha \gets L^{\top} \setminus \left(L \setminus \mathbf{y}\right)$
\STATE $\bar{f}_* \gets \mathbf{k}^{\top}_* \alpha$
\STATE $\mathbf{v} \gets L \setminus \mathbf{k}_*$
\STATE $\mathbb{V}\left[f_{*}\right] \gets k \left(\mathbf{x}_*, \mathbf{x}_*\right) - \mathbf{v}^{\top} \mathbf{v}$
\RETURN $\bar{f}_*, \mathbb{V}\left[f_{*}\right]$
\ENDFUNCTION
\end{algorithmic}
\end{algorithm}
</pre></div>

<p>高斯过程后验和采样的序列如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-06-06-bayesian-optimization/gp-posterior.png" class="lazyload"/>
  
</figure>
<p>先验的协方差矩阵和后验的协方差矩阵可视化如下图所示：</p>
<figure>
  <img data-src="/images/cn/2020-06-06-bayesian-optimization/prior-posterior-convariance.png" class="lazyload"/>
  
</figure>
<p>本小结代码请参见<a href="https://github.com/leovan/leovan.me/blob/master/scripts/cn/2020-06-06-bayesian-optimization/gaussian-process-regression.py">这里</a>。</p>
<h2 id="贝叶斯优化">贝叶斯优化</h2>
<h3 id="主动学习">主动学习</h3>
<p>在很多机器学习问题中，数据标注往往需要耗费很大成本。**主动学习（Active Learning）**在最大化模型准确率时最小化标注成本，例如对不确定性最高的数据进行标注。由于我们仅知道少量数据点，因此我们需要一个代理模型（Surrogate Model）来建模真正的模型。高斯过程因其灵活性和具有估计不确定性估计的特性不失为一个常用的代理模型。</p>
<p>在估计 <code>$f \left(x\right)$</code> 的过程中，我们希望最小化评估的次数，因此我们可以通过主动学习来“智能”地选择下一个评估的数据点。通过不断的选择具有最高不确定性的数据点来获得 <code>$f \left(x\right)$</code> 更准确的估计，直至收敛或达到停止条件。下图展示了利用主动学习估计真实数据分布的过程：</p>

  
  <link rel="stylesheet" href="/css/figure-slider.css"/>


<div class="figure-slider">
  <div class="figure-slider-parameters">
    <span class="base-url">/images/cn/2020-06-06-bayesian-optimization/</span>
    <span class="image-filename-prefix">active-gp-</span>
    <span class="image-format">png</span>
    <span class="milliseconds">300</span>
  </div>
  <div class="figure-slider-image-container">
    <img class="figure-slider-image" src='/images/cn/2020-06-06-bayesian-optimization/active-gp-0.png'>
  </div>
  <div class="figure-slider-controls">
    <div class="figure-slider-buttons">
      <button class="figure-slider-button-previous">
        <span class="mdi mdi-skip-previous"></span>
      </button>
    </div>
    <div class="figure-slider-scroll-bar-container">
      <input class="figure-slider-scroll-bar" type="range" min='0' max='9' value='0'>
    </div>
    <div class="figure-slider-buttons">
      <button class="figure-slider-button-next">
        <span class="mdi mdi-skip-next"></span>
      </button>
    </div>
    <div class="figure-slider-buttons">
      <button class="figure-slider-button-play-pause figure-slider-button-play">
        <span class="mdi mdi-play"></span>
      </button>
    </div>
  </div>
</div>

<h3 id="贝叶斯优化问题">贝叶斯优化问题</h3>
<p>贝叶斯优化的核心问题是：基于现有的已知情况，如果选择下一步评估的数据点？在主动学习中我们选择不确定性最大的点，但在贝叶斯优化中我们需要在探索不确定性区域（探索）和关注已知具有较优目标值的区域之间进行权衡（开发）。这种评价的依据称之为<strong>采集函数（Acquisition Functions）</strong>，采集函数通过当前模型启发式的评估是否选择一个数据点。</p>
<p>贝叶斯优化的目标是找到一个函数 <code>$f: \mathbb{R}^d \mapsto \mathbb{R}$</code> 最大值（或最小值）对应的位置 <code>$x \in \mathbb{R}^d$</code>。为了解决这个问题，我们遵循如下算法：</p>
<ol>
<li>选择一个代理模型用于建模真实函数 <code>$f$</code> 和定义其先验。</li>
<li>给定观测集合，利用贝叶斯公式获取后验。</li>
<li>利用采集函数 <code>$\alpha \left(x\right)$</code> 确性下一个采样点 <code>$x_t = \arg\max_x \alpha \left(x\right)$</code>。</li>
<li>将采样的点加入观测集合，重复步骤 2 直至收敛或达到停止条件。</li>
</ol>
<h3 id="采集函数">采集函数</h3>
<ul>
<li>Probability of Improvement (PI)</li>
</ul>
<p>Probability of Improvement (PI) 采集函数会选择具有最大可能性提高当前最大的 <code>$f \left(x^{+}\right)$</code> 值的点作为下一个查询点，即：</p>
<p><code>$$ x_{t+1} = \arg\max \left(\alpha_{PI} \left(x\right)\right) = \arg\max \left(P \left(f \left(x\right)\right) \geq \left(f \left(x^{+}\right) + \epsilon\right)\right) $$</code></p>
<p>其中，<code>$P \left(\cdot\right)$</code> 表示概率，<code>$\epsilon$</code> 为一个较小的正数，<code>$x^{+} = \arg\max_{x_i \in x_{1:t}} f \left(x_i\right)$</code>，<code>$x_i$</code> 为第 <code>$i$</code> 步查询点的位置。如果采用高斯过程作为代理模型，上式则转变为：</p>
<p><code>$$ x_{t+1} = \arg\max_x \Phi \left(\dfrac{\mu_t \left(x\right) - f \left(x^{+}\right) - \epsilon}{\sigma_t \left(x\right)}\right) $$</code></p>
<p>其中，<code>$\Phi \left(\cdot\right)$</code> 表示标准正态分布累积分布函数。PI 利用 <code>$\epsilon$</code> 来权衡探索和开发，增加 <code>$\epsilon$</code> 的值会更加倾向进行探索。</p>
<ul>
<li>Expected Improvement (EI)</li>
</ul>
<p>PI 仅关注了有多大的可能性能够提高，而没有关注能够提高多少。Expected Improvement (EI) 则会选择具有最大期望提高的点作为下一个查询点，即：</p>
<p><code>$$ x_{t+1} = \arg\min_x \mathbb{E} \left(\left\|h_{t+1} \left(x\right) - f \left(x^*\right)\right\| | \mathcal{D}_t\right) $$</code></p>
<p>其中，<code>$f$</code> 为真实函数，<code>$h_{t+1}$</code> 为代理模型在 <code>$t+1$</code> 步的后验均值，<code>$\mathcal{D}_t = \left\{\left(x_i, f\left(x_i\right)\right)\right\}, \forall x \in x_{1:t}$</code> 为训练数据，<code>$x^*$</code> 为 <code>$f$</code> 取得最大值的真实位置。</p>
<p>上式中我们希望选择能够最小化与最大目标值之间距离的点，由于我们并不知道真实函数 <code>$f$</code>，Mockus <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 提出了一种解决办法：</p>
<p><code>$$ x_{t+1} = \arg\max_x \mathbb{E} \left(\max \left\{0, h_{t+1} \left(x\right) - f \left(x^{+}\right)\right\} | \mathcal{D}_t\right) $$</code></p>
<p>其中，<code>$f \left(x^{+}\right)$</code> 为到目前为止遇见的最大函数值，如果采用高斯过程作为代理模型，上式则转变为：</p>
<p><code>$$ \begin{aligned} EI(x) &amp;= \left\{\begin{array}{ll} \left(\mu_{t}(x)-f\left(x^{+}\right)-\epsilon\right) \Phi(Z)+\sigma_{t}(x) \phi(Z), &amp; \text { if } \sigma_{t}(x)&gt;0 \\ 0 &amp; \text { if } \sigma_{t}(x)=0 \end{array}\right. \\ Z &amp;= \frac{\mu_{t}(x)-f\left(x^{+}\right)-\epsilon}{\sigma_{t}(x)} \end{aligned} $$</code></p>
<p>其中 <code>$\Phi \left(\cdot\right)$</code> 表示标准正态分布累积分布函数，<code>$\phi \left(\cdot\right)$</code> 表示标准正态分布概率密度函数。类似 PI，EI 也可以利用 <code>$\epsilon$</code> 来权衡探索和开发，增加 <code>$\epsilon$</code> 的值会更加倾向进行探索。</p>
<ul>
<li>对比和其他采集函数</li>
</ul>
<figure>
  <img data-src="/images/cn/2020-06-06-bayesian-optimization/pi-vs-ei.svg" class="lazyload"/>
  
</figure>
<p>上图展示了在仅包含一个训练观测数据 <code>$\left(0.5, f \left(0.5\right)\right)$</code> 情况下不同点的采集函数值。可以看出 <code>$\alpha_{EI}$</code> 和 <code>$\alpha_{PI}$</code> 的最大值分别为 0.3 和 0.47。选择一个具有较小的 <code>$\alpha_{PI}$</code> 和一个较大的 <code>$\alpha_{EI}$</code> 的点可以理解为一个高的风险和高的回报。因此，当多个点具有相同的 <code>$\alpha_{EI}$</code> 时，我们应该优先选择具有较小风险（高 <code>$\alpha_{PI}$</code>）的点，类似的，当多个点具有相同的 <code>$\alpha_{PI}$</code> 时，我们应该优先选择具有较大回报（高 <code>$\alpha_{EI}$</code>）的点。</p>
<p>其他采集函数还有 Thompson Sampling <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>，Upper Confidence Bound (UCB)，Gaussian Process Upper Confidence Bound (GP-UCB) <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>，Entropy Search <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>，Predictive Entropy Search <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> 等，细节请参见原始论文或 A Tutorial on Bayesian Optimization <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>。</p>
<h2 id="开放资源">开放资源</h2>
<ul>
<li><a href="https://github.com/scikit-optimize/scikit-optimize">scikit-optimize/scikit-optimize</a></li>
<li><a href="https://github.com/hyperopt/hyperopt">hyperopt/hyperopt</a></li>
<li><a href="https://github.com/automl/SMAC3">automl/SMAC3</a></li>
<li><a href="https://github.com/fmfn/BayesianOptimization">fmfn/BayesianOptimization</a></li>
<li><a href="https://github.com/pytorch/botorch">pytorch/botorch</a></li>
<li><a href="https://github.com/GPflow/GPflowOpt">GPflow/GPflowOpt</a></li>
<li><a href="https://github.com/keras-team/keras-tuner">keras-team/keras-tuner</a></li>
<li><a href="https://github.com/tobegit3hub/advisor">tobegit3hub/advisor</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Mockus, J. B., &amp; Mockus, L. J. (1991). Bayesian approach to global optimization and application to multiobjective and constrained problems. <em>Journal of Optimization Theory and Applications, 70</em>(1), 157-172.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Thompson, W. R. (1933). On the likelihood that one unknown probability exceeds another in view of the evidence of two samples. <em>Biometrika, 25</em>(3/4), 285-294.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Auer, P. (2002). Using confidence bounds for exploitation-exploration trade-offs. <em>Journal of Machine Learning Research, 3</em>(Nov), 397-422.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Hennig, P., &amp; Schuler, C. J. (2012). Entropy search for information-efficient global optimization. <em>Journal of Machine Learning Research, 13</em>(Jun), 1809-1837.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Hernández-Lobato, J. M., Hoffman, M. W., &amp; Ghahramani, Z. (2014). Predictive entropy search for efficient global optimization of black-box functions. <em>In Advances in neural information processing systems</em> (pp. 918-926).&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Frazier, P. I. (2018). A tutorial on bayesian optimization. <em>arXiv preprint arXiv:1807.02811</em>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>启发式算法 (Heuristic Algorithms)</title><link>https://zeqiang.fun/cn/2019/04/heuristic-algorithms/</link><pubDate>Fri, 05 Apr 2019 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2019/04/heuristic-algorithms/</guid><description><![CDATA[
        <h1 id="启发式算法-heuristic-algorithms">启发式算法 (Heuristic Algorithms)</h1>
<p><strong>启发式算法 (Heuristic Algorithms)</strong> 是相对于最优算法提出的。一个问题的最优算法是指求得该问题每个实例的最优解. 启发式算法可以这样定义 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>：一个基于直观或经验构造的算法，在可接受的花费 (指计算时间、占用空间等) 下给出待解决组合优化问题每一个实例的一个可行解，该可行解与最优解的偏离程度不一定事先可以预计。</p>
<p>在某些情况下，特别是实际问题中，最优算法的计算时间使人无法忍受或因问题的难度使其计算时间随问题规模的增加以指数速度增加，此时只能通过启发式算法求得问题的一个可行解。</p>
<p>利用启发式算法进行目标优化的一些优缺点如下：</p>
<table>
  <thead>
      <tr>
          <th>优点</th>
          <th>缺点</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. 算法简单直观，易于修改 <br/>2. 算法能够在可接受的时间内给出一个较优解</td>
          <td>1. 不能保证为全局最优解 <br/>2. 算法不稳定，性能取决于具体问题和设计者经验</td>
      </tr>
  </tbody>
</table>
<p>启发式算法简单的划分为如下三类：<strong>简单启发式算法 (Simple Heuristic Algorithms)</strong>，<strong>元启发式算法 (Meta-Heuristic Algorithms)</strong> 和 <strong>超启发式算法 (Hyper-Heuristic Algorithms)</strong>。</p>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/heuristic-algorithms.png" alt="Heuristic-Algorithms"></p>
<h1 id="简单启发式算法-simple-heuristic-algorithms">简单启发式算法 (Simple Heuristic Algorithms)</h1>
<h2 id="贪心算法-greedy-algorithm">贪心算法 (Greedy Algorithm)</h2>
<p>贪心算法是指一种在求解问题时总是采取当前状态下最优的选择从而得到最优解的算法。贪心算法的基本步骤定义如下：</p>
<ol>
<li>确定问题的最优子结构。</li>
<li>设计递归解，并保证在任一阶段，最优选择之一总是贪心选择。</li>
<li>实现基于贪心策略的递归算法，并转换成迭代算法。</li>
</ol>
<p>对于利用贪心算法求解的问题需要包含如下两个重要的性质：</p>
<ol>
<li>最优子结构性质。当一个问题具有最优子结构性质时，可用 <a href="/cn/2018/11/computational-complexity-and-dynamic-programming">动态规划</a> 法求解，但有时用贪心算法求解会更加的简单有效。同时并非所有具有最优子结构性质的问题都可以利用贪心算法求解。</li>
<li>贪心选择性质。所求问题的整体最优解可以通过一系列局部最优的选择 (即贪心选择) 来达到。这是贪心算法可行的基本要素，也是贪心算法与动态规划算法的主要区别。</li>
</ol>
<p>贪心算法和动态规划算法之间的差异如下表所示：</p>
<table>
  <thead>
      <tr>
          <th>贪心算法</th>
          <th>动态规划</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>每个阶段可以根据选择当前状态最优解快速的做出决策</td>
          <td>每个阶段的选择建立在子问题的解之上</td>
      </tr>
      <tr>
          <td>可以在子问题求解之前贪婪的做出选择</td>
          <td>子问题需先进行求解</td>
      </tr>
      <tr>
          <td>自顶向下的求解</td>
          <td>自底向上的求解 (也可采用带备忘录的自顶向下方法)</td>
      </tr>
      <tr>
          <td>通常情况下简单高效</td>
          <td>效率可能比较低</td>
      </tr>
  </tbody>
</table>
<h2 id="局部搜索-local-search-和爬山算法-hill-climbing">局部搜索 (Local Search) 和爬山算法 (Hill Climbing)</h2>
<p>局部搜索算法基于贪婪思想，从一个候选解开始，持续地在其<strong>邻域</strong>中搜索，直至邻域中没有更好的解。对于一个优化问题：</p>
<p><code>$$ \min f \left(x\right), x \in \mathbb{R}^n $$</code></p>
<p>其中，<code>$f \left(x\right)$</code> 为目标函数。搜索可以理解为从一个解移动到另一个解的过程，令 <code>$s \left(x\right)$</code> 表示通过移动得到的一个解，<code>$S \left(x\right)$</code> 为从当前解出发所有可能的解的集合 (邻域)，则局部搜索算法的步骤描述如下：</p>
<ol>
<li>初始化一个可行解 <code>$x$</code>。</li>
<li>在当前解的邻域内选择一个移动后的解 <code>$s \left(x\right)$</code>，使得 <code>$f \left(s \left(x\right)\right) &lt; f \left(x\right), s \left(x\right) \in S \left(x\right)$</code>，如果不存在这样的解，则 <code>$x$</code> 为最优解，算法停止。</li>
<li>令 <code>$x = s \left(x\right)$</code>，重复步骤 2。</li>
</ol>
<p>当我们的优化目标为最大化目标函数 <code>$f \left(x\right)$</code> 时，这种局部搜索算法称之为爬山算法。</p>
<h1 id="元启发式算法-meta-heuristic-algorithms">元启发式算法 (Meta-Heuristic Algorithms)</h1>
<p>元启发式算法 (Meta-Heuristic Algorithms) 是启发式算法的改进，通常使用随机搜索技巧，可以应用在非常广泛的问题上，但不能保证效率。本节部分内容参考了《智能优化方法》<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> 和《现代优化计算方法》<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p>
<h2 id="禁忌搜索-tabu-search">禁忌搜索 (Tabu Search)</h2>
<p>禁忌搜索 (Tabu Search) 是由 Glover <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> 提出的一种优化方法。禁忌搜索通过在解邻域内搜索更优的解的方式寻找目标的最优解，在搜索的过程中将搜索历史放入禁忌表 (Tabu List) 中从而避免重复搜索。禁忌表通过模仿人类的记忆功能，禁忌搜索因此得名。</p>
<p>在禁忌搜索算法中，禁忌表用于防止搜索过程出现循环，避免陷入局部最优。对于一个给定长度的禁忌表，随着新的禁忌对象的不断进入，旧的禁忌对象会逐步退出，从而可以重新被访问。禁忌表是禁忌搜索算法的核心，其功能同人类的短时记忆功能相似，因此又称之为“短期表”。</p>
<p>在某些特定的条件下，无论某个选择是否包含在禁忌表中，我们都接受这个选择并更新当前解和历史最优解，这个选择所满足的特定条件称之为渴望水平。</p>
<p>一个基本的禁忌搜索算法的步骤描述如下：</p>
<ol>
<li>给定一个初始可行解，将禁忌表设置为空。</li>
<li>选择候选集中的最优解，若其满足渴望水平，则更新渴望水平和当前解；否则选择未被禁忌的最优解。</li>
<li>更新禁忌表。</li>
<li>判断是否满足停止条件，如果满足，则停止算法；否则转至步骤 2。</li>
</ol>
<h2 id="模拟退火-simulated-annealing">模拟退火 (Simulated Annealing)</h2>
<p>模拟退火 (Simulated Annealing) 是一种通过在邻域中寻找目标值相对小的状态从而求解全局最优的算法，现代的模拟退火是由 Kirkpatrick 等人于 1983 年提出 <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>。模拟退火算法源自于对热力学中退火过程的模拟，在给定一个初始温度下，通过不断降低温度，使得算法能够在多项式时间内得到一个近似最优解。</p>
<p>对于一个优化问题 <code>$\min f \left(x\right)$</code>，模拟退火算法的步骤描述如下：</p>
<ol>
<li>给定一个初始可行解 <code>$x_0$</code>，初始温度 <code>$T_0$</code> 和终止温度 <code>$T_f$</code>，令迭代计数为 <code>$k$</code>。</li>
<li>随机选取一个邻域解 <code>$x_k$</code>，计算目标函数增量 <code>$\Delta f = f \left(x_k\right) - f \left(x\right)$</code>。若 <code>$\Delta f &lt; 0$</code>，则令 <code>$x = x_k$</code>；否则生成随机数 <code>$\xi = U \left(0, 1\right)$</code>，若随机数小于转移概率 <code>$P \left(\Delta f, T\right)$</code>，则令 <code>$x = x_k$</code>。</li>
<li>降低温度 <code>$T$</code>。</li>
<li>若达到最大迭代次数 <code>$k_{max}$</code> 或最低温度 <code>$T_f$</code>，则停止算法；否则转至步骤 2。</li>
</ol>
<p>整个算法的伪代码如下：</p>


<div><pre class="pseudocode">
\begin{algorithm}
\caption{模拟退火算法}
\begin{algorithmic}
\STATE $x \gets x_0$
\STATE $T \gets T_0$
\STATE $k \gets 0$
\WHILE{$k \leq k_{max}$ \AND $T \geq T_f$}
    \STATE $x_k \gets $ \CALL{neighbor}{$s$}
    \STATE $\Delta f = f \left(x_k\right) - f \left(x\right)$
    \IF{$\Delta f < 0$ \OR \CALL{random}{$0, 1$} $ \leq P \left(\Delta f, T\right)$}
        \STATE $x \gets x_k$
    \ENDIF
    \STATE $T \gets $ \CALL{cooling}{$T, k, k_{max}$}
    \STATE $k \gets k + 1$
\ENDWHILE
\end{algorithmic}
\end{algorithm}
</pre></div>

<p>在进行邻域搜索的过程中，当温度较高时，搜索的空间较大，反之搜索的空间较小。类似的，当 <code>$\Delta f &gt; 0$</code> 时，转移概率的设置也同当前温度的大小成正比。常用的降温函数有两种：</p>
<ol>
<li><code>$T_{k+1} = T_k * r$</code>，其中 <code>$r \in \left(0.95, 0.99\right)$</code>，<code>$r$</code> 设置的越大，温度下降越快。</li>
<li><code>$T_{k+1} = T_k - \Delta T$</code>，其中 <code>$\Delta T$</code> 为每一步温度的减少量。</li>
</ol>
<p>初始温度和终止温度对算法的影响较大，相关参数设置的细节请参见参考文献。</p>
<p>模拟退火算法是对局部搜索和爬山算法的改进，我们通过如下示例对比两者之间的差异。假设目标函数如下：</p>
<p><code>$$ f \left(x, y\right) = e^{- \left(x^2 + y^2\right)} + 2 e^{- \left(\left(x - 1.7\right)^2 + \left(y - 1.7\right)^2\right)} $$</code></p>
<p>优化问题定义为：</p>
<p><code>$$ \max f \left(x, y\right), x \in \left[-2, 4\right], y \in \left[-2, 4\right] $$</code></p>
<p>我们分别令初始解为 <code>$\left(1.5, -1.5\right)$</code> 和 <code>$\left(3.5, 0.5\right)$</code>，下图 (上) 为爬山算法的结果，下图 (下) 为模拟退火算法的结果。</p>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/hill-climbing.png" alt="Hill Climbing"></p>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/simulated-annealing.png" alt="Simulated Annealing"></p>
<p>其中，<span style="color:#FFF; background-color:#000;"><strong>白色</strong></span> 的大点为初始解位置，<span style="color:#F71893;"><strong>粉色</strong></span> 的大点为求解的最优解位置，颜色从白到粉描述了迭代次数。从图中不难看出，由于局部最大值的存在，从不同的初始解出发，爬山算法容易陷入局部最大值，而模拟退火算法则相对稳定。</p>
<h2 id="遗传算法-genetic-algorithm">遗传算法 (Genetic Algorithm)</h2>
<p>遗传算法 (Genetic Algorithm, GA) 是由 John Holland 提出，其学生 Goldberg 对整个算法进行了进一步完善 <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>。算法的整个思想来源于达尔文的进化论，其基本思想是根据问题的目标函数构造一个适应度函数 (Fitness Function)，对于种群中的每个个体 (即问题的一个解) 进行评估 (计算适应度)，选择，交叉和变异，通过多轮的繁殖选择适应度最好的个体作为问题的最优解。算法的整个流程如下所示：</p>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/ga-process.png" alt="GA-Process"></p>
<h3 id="初始化种群">初始化种群</h3>
<p>在初始化种群时，我们首先需要对每一个个体进行编码，常用的编码方式有二进制编码，实值编码 <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>，矩阵编码 <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>，树形编码等。以二进制为例 (如下不做特殊说明时均以二进制编码为例)，对于 <code>$p \in \left\{0, 1, \dotsc, 100\right\}$</code> 中 <code>$p_i = 50$</code> 可以表示为：</p>
<p><code>$$ x_i = 50_{10} = 0110010_{2} $$</code></p>
<p>对于一个具体的问题，我们需要选择合适的编码方式对问题的解进行编码，编码后的个体可以称之为一个染色体。则一个染色体可以表示为：</p>
<p><code>$$ x = \left(p_1, p_2, \dotsc, p_m\right) $$</code></p>
<p>其中，<code>$m$</code> 为染色体的长度或编码的位数。初始化种群个体共 <code>$n$</code> 个，对于任意一个个体染色体的任意一位 <code>$i$</code>，随机生成一个随机数 <code>$\text{rand} \in U \left(0, 1\right)$</code>，若 <code>$\text{rand} &gt; 0.5$</code>，则 <code>$p_i = 1$</code>，否则 <code>$p_i = 0$</code>。</p>
<h3 id="计算适应度">计算适应度</h3>
<p>适应度为评价个体优劣程度的函数 <code>$f\left(x\right)$</code>，通常为问题的目标函数，对最小化优化问题 <code>$f\left(x\right) = - \min \sum{\mathcal{L} \left(\hat{y}, y\right)}$</code>，对最大化优化问题 <code>$f\left(x\right) = \max \sum{\mathcal{L} \left(\hat{y}, y\right)}$</code>，其中 <code>$\mathcal{L}$</code> 为损失函数。</p>
<h3 id="选择">选择</h3>
<p>对于种群中的每个个体，计算其适应度，记第 <code>$i$</code> 个个体的适应度为 <code>$F_i = f\left(x_i\right)$</code>。则个体在一次选择中被选中的概率为：</p>
<p><code>$$ P_i = \dfrac{F_i}{\sum_{i=1}^{n}{F_i}} $$</code></p>
<p>为了保证种群的数量不变，我们需要重复 <code>$n$</code> 次选择过程，单次选择采用轮盘赌的方法。利用计算得到的被选中的概率计算每个个体的累积概率：</p>
<p><code>$$ \begin{equation} \begin{split} CP_0 &amp;= 0 \\ CP_i &amp;= \sum_{j=1}^{i}{P_i} \end{split} \end{equation} $$</code></p>
<p>对于如下一个示例：</p>
<table>
  <thead>
      <tr>
          <th>指标 \ 个体</th>
          <th><code>$x_1$</code></th>
          <th><code>$x_2$</code></th>
          <th><code>$x_3$</code></th>
          <th><code>$x_4$</code></th>
          <th><code>$x_5$</code></th>
          <th><code>$x_6$</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>适应度 (F)</td>
          <td>100</td>
          <td>60</td>
          <td>60</td>
          <td>40</td>
          <td>30</td>
          <td>20</td>
      </tr>
      <tr>
          <td>概率 (P)</td>
          <td>0.322</td>
          <td>0.194</td>
          <td>0.194</td>
          <td>0.129</td>
          <td>0.097</td>
          <td>0.064</td>
      </tr>
      <tr>
          <td>累积概率 (CP)</td>
          <td>0.322</td>
          <td>0.516</td>
          <td>0.71</td>
          <td>0.839</td>
          <td>0.936</td>
          <td>1</td>
      </tr>
  </tbody>
</table>
<p>每次选择时，随机生成 <code>$\text{rand} \in U \left(0, 1\right)$</code>，当 <code>$CP_{i-1} \leq \text{rand} \leq CP_i$</code> 时，选择个体 <code>$x_i$</code>。选择的过程如同在下图的轮盘上安装一个指针并随机旋转，每次指针停止的位置的即为选择的个体。</p>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/ga-roulette-wheel.png" alt="GA-Roulette-Wheel"></p>
<h3 id="交叉">交叉</h3>
<p>交叉运算类似于染色体之间的交叉，常用的方法有单点交叉，多点交叉和均匀交叉等。</p>
<ul>
<li>单点交叉：在染色体中选择一个切点，然后将其中一部分同另一个染色体的对应部分进行交换得到两个新的个体。交叉过程如下图所示：</li>
</ul>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/ga-crossover-one-point.png" alt="GA-Crossover-One-Point"></p>
<ul>
<li>多点交叉：在染色体中选择多个切点，对其任意两个切点之间部分以概率 <code>$P_c$</code> 进行交换，其中 <code>$P_c$</code> 为一个较大的值，例如 <code>$P_m = 0.9$</code>。两点交叉过程如下图所示：</li>
</ul>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/ga-crossover-two-points.png" alt="GA-Crossover-Two-Points"></p>
<ul>
<li>均匀交叉：染色体任意对应的位置以一定的概率进行交换得到新的个体。交叉过程如下图所示：</li>
</ul>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/ga-crossover-uniform.png" alt="GA-Crossover-Uniform"></p>
<h3 id="变异">变异</h3>
<p>变异即对于一个染色体的任意位置的值以一定的概率 <code>$P_m$</code> 发生变化，对于二进制编码来说即反转该位置的值。其中  <code>$P_m$</code> 为一个较小的值，例如 <code>$P_m = 0.05$</code>。</p>
<h3 id="小结">小结</h3>
<p>在整个遗传运算的过程中，不同的操作发挥着不同的作用：</p>
<ol>
<li>选择：优胜劣汰，适者生存。</li>
<li>交叉：丰富种群，持续优化。</li>
<li>变异：随机扰动，避免局部最优。</li>
</ol>
<p>除此之外，对于基本的遗传算法还有多种优化方法，例如：精英主义，即将每一代中的最优解原封不动的复制到下一代中，这保证了最优解可以存活到整个算法结束。</p>
<h3 id="示例-商旅问题">示例 - 商旅问题</h3>
<p>以 <a href="https://zh.wikipedia.org/zh/%E6%97%85%E8%A1%8C%E6%8E%A8%E9%94%80%E5%91%98%E9%97%AE%E9%A2%98">商旅问题</a> 为例，利用 GA 算法求解中国 34 个省会城市的商旅问题。求解代码利用了 <a href="https://deap.readthedocs.io/en/master/">Deap</a> 库，结果可视化如下图所示：</p>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/ga-tsp.gif" alt="GA-TSP"></p>
<p>一个更有趣的例子是利用 GA 算法，使用不同颜色和透明度的多边形的叠加表示一张图片，在线体验详见 <a href="http://alteredqualia.com/visualization/evolve/">这里</a>，下图为不同参数下的蒙娜丽莎图片的表示情况：</p>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/ga-mona-lisa.png" alt="GA-Mona-Lisa"></p>
<h2 id="蚁群算法-ant-colony-optimization-aco">蚁群算法 (Ant Colony Optimization, ACO)</h2>
<p>1991 年，意大利学者 Dorigo M. 等人在第一届欧洲人工生命会议 (ECAL) 上首次提出了蚁群算法。1996 年 Dorigo M. 等人发表的文章 “Ant system: optimization by a colony of cooperating agents” <sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> 为蚁群算法奠定了基础。在自然界中，蚂蚁会分泌一种叫做信息素的化学物质，蚂蚁的许多行为受信息素的调控。蚂蚁在运动过程中能够感知其经过的路径上信息素的浓度，蚂蚁倾向朝着信息素浓度高的方向移动。以下图为例 <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup>：</p>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/aco-shortest-path.png" alt="ACO Shortest Path"></p>
<p>蚂蚁从蚁巢 (N) 出发到达食物源所在地 (F)，取得食物后再折返回蚁巢。整个过程中蚂蚁有多种路径可以选择，单位时间内路径上通过蚂蚁的数量越多，则该路径上留下的信息素浓度越高。因此，最短路径上走过的蚂蚁数量越多，则后来的蚂蚁选择该路径的机率就越大，从而蚂蚁通过信息的交流实现了寻找食物和蚁巢之间最短路的目的。</p>
<h2 id="粒子群算法-particle-swarm-optimization-pso">粒子群算法 (Particle Swarm Optimization, PSO)</h2>
<p>Eberhart, R. 和 Kennedy, J. 于 1995 年提出了粒子群优化算法 <sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup> <sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup>。粒子群算法模仿的是自然界中鸟群和鱼群等群体的行为，其基本原理描述如下：</p>
<p>一个由 <code>$m$</code> 个粒子 (Particle) 组成的群体 (Swarm) 在 <code>$D$</code> 维空间中飞行，每个粒子在搜索时，考虑自己历史搜索到的最优解和群体内 (或邻域内) 其他粒子历史搜索到的最优解，在此基础上进行位置 (状态，也就是解) 的变化。令第 <code>$i$</code> 个粒子的位置为 <code>$x_i$</code>，速度为 <code>$v_i$</code>，历史搜索的最优解对应的点为 <code>$p_i$</code>，群体内 (或邻域内) 所有粒子历史搜索到的最优解对应的点为 <code>$p_g$</code>，则粒子的位置和速度依据如下公式进行变化：</p>
<p><code>$$ \begin{equation} \begin{split} v^{k+1}_i &amp;= \omega v^k_i + c_1 \xi \left(p^k_i - x^k_i\right) + c_2 \eta \left(p^k_g - x^k_i\right) \\ x^{k+1}_i &amp;= x^k_i + v^{k+1}_i \end{split} \end{equation} $$</code></p>
<p>其中，<code>$\omega$</code> 为惯性参数；<code>$c_1$</code> 和 <code>$c_2$</code> 为学习因子，其一般为正数，通常情况下等于 2；<code>$\xi, \eta \in U \left[0, 1\right]$</code>。学习因子使得粒子具有自我总结和向群体中优秀个体学习的能力，从而向自己的历史最优点以及群体内或邻域内的最优点靠近。同时，粒子的速度被限制在一个最大速度 <code>$V_{max}$</code> 范围内。</p>
<p>对于 Rosenbrock 函数</p>
<p><code>$$ f \left(x, y\right) = \left(1 - x\right)^2 + 100 \left(y - x^2\right)^2 $$</code></p>
<p>当 <code>$x \in \left[-2, 2\right], y \in \left[-1, 3\right]$</code>，定义优化问题为最小化目标函数，最优解为 <code>$\left(0, 0\right)$</code>。利用 <a href="https://github.com/ljvmiranda921/pyswarms">PySwarms</a> 扩展包的优化过程可视化如下：</p>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/rosenbrock-pso.gif" alt="Rosenbrock PSO"></p>
<p>其中，<code>$m = 50, \omega = 0.8, c_1 = 0.5, c_2 = 0.3$</code>，迭代次数为 200。</p>
<p>本节相关示例代码详见 <a href="https://github.com/leovan/leovan.me/tree/master/scripts/cn/2019-04-05-heuristic-algorithms/">这里</a>。</p>
<h1 id="超启发式算法-hyper-heuristic-algorithms">超启发式算法 (Hyper-Heuristic Algorithms)</h1>
<p>超启发式算法 (Hyper-Heuristic Algorithms) 提供了一种高层次启发式方法，通过管理或操纵一系列低层次启发式算法 (Low-Level Heuristics，LLH)，以产生新的启发式算法。这些新启发式算法被用于求解各类组合优化问题 <sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">12</a></sup>。</p>
<p>下图给出了超启发式算法的概念模型。该模型分为两个层面：在问题域层面上，应用领域专家根据自己的背景知识，在智能计算专家协助下，提供一系列 LLH 和问题的定义、评估函数等信息；在高层次启发式方法层面上，智能计算专家设计高效的管理操纵机制，运用问题域所提供的 LLH 算法库和问题特征信息，构造出新的启发式算法。</p>
<p><img src="/images/cn/2019-04-05-heuristic-algorithms/hyper-heuristic-algorithms-model.png" alt="Hyper-Heuristic-Algorithms"></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>邢文训, &amp; 谢金星. (2005). <em>现代优化计算方法</em>. 清华大学出版社.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>汪定伟, 王俊伟, 王洪峰, 张瑞友, &amp; 郭哲. (2007). <em>智能优化方法</em>.  高等教育出版社.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Glover, F. W., &amp; Laguna, M. (1997). <em>Tabu Search</em>. Springer US.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Kirkpatrick, S., Gelatt, C. D., &amp; Vecchi, M. P. (1983). Optimization by Simulated Annealing. <em>Science</em>, 220(4598), 671–680.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://en.wikipedia.org/wiki/Genetic_algorithm">https://en.wikipedia.org/wiki/Genetic_algorithm</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Michalewicz, Z., Janikow, C. Z., &amp; Krawczyk, J. B. (1992). A modified genetic algorithm for optimal control problems. <em>Computers &amp; Mathematics with Applications</em>, 23(12), 83-94.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>Gottlieb, J., &amp; Paulmann, L. (1998, May). Genetic algorithms for the fixed charge transportation problem. In <em>Evolutionary Computation Proceedings, 1998. IEEE World Congress on Computational Intelligence., The 1998 IEEE International Conference on</em> (pp. 330-335). IEEE.&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>Dorigo, M., Maniezzo, V., &amp; Colorni, A. (1996). Ant system: optimization by a colony of cooperating agents. <em>IEEE Transactions on Systems, man, and cybernetics, Part B: Cybernetics</em>, 26(1), 29-41.&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>Toksari, M. D. (2016). A hybrid algorithm of Ant Colony Optimization (ACO) and Iterated Local Search (ILS) for estimating electricity domestic consumption: Case of Turkey. <em>International Journal of Electrical Power &amp; Energy Systems</em>, 78, 776-782.&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>Eberhart, R., &amp; Kennedy, J. (1995, November). Particle swarm optimization. In <em>Proceedings of the IEEE international conference on neural networks</em> (Vol. 4, pp. 1942-1948).&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p>Eberhart, R., &amp; Kennedy, J. (1995, October). A new optimizer using particle swarm theory. In <em>MHS'95. Proceedings of the Sixth International Symposium on Micro Machine and Human Science</em> (pp. 39-43). IEEE.&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:12">
<p>江贺. (2011). 超启发式算法：跨领域的问题求解模式. <em>中国计算机学会通讯</em>, 7(2), 63-70&#160;<a href="#fnref:12" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item><item><title>计算复杂性 (Computational Complexity) 与动态规划 (Dynamic Programming)</title><link>https://zeqiang.fun/cn/2018/11/computational-complexity-and-dynamic-programming/</link><pubDate>Sun, 18 Nov 2018 00:00:00 +0000</pubDate><guid>https://zeqiang.fun/cn/2018/11/computational-complexity-and-dynamic-programming/</guid><description><![CDATA[
        <h2 id="计算复杂性">计算复杂性</h2>
<p><strong>计算复杂性 (Computational Complexity)</strong> 是用于对一个问题求解所需的资源 (通常为 <strong>空间</strong> 和 <strong>时间</strong>) 的度量。在评估一个算法的时候，除了算法本身的准确性以外，同时需要关注算法运行的时间以及占用的内存，从而根据实际情况选择合适的算法。</p>
<h3 id="函数的增长">函数的增长</h3>
<p>计算复杂性中的空间和时间的评估方法类似，在此我们更多的以时间复杂度为例。算法的运行时间刻画了算法的效率，对于一个输入规模为 <code>$n$</code> 的问题，定义一个算法求解该问题 <strong>最坏情况</strong> 下的运行时间为 <code>$T \left(n\right)$</code>，我们可以使用一些 <strong>渐进记号</strong> 更加方便地对其进行描述。</p>
<ul>
<li><strong><code>$\Theta$</code> 记号</strong></li>
</ul>
<p>对于一个给定的函数 <code>$g \left(n\right)$</code>，<code>$\Theta \left(g \left(n\right)\right)$</code> 可以表示如下函数的集合：</p>
<p><code>$$ \Theta \left(g \left(n\right)\right) = \left\{f \left(n\right): \exists c_1 &gt; 0, c_2 &gt; 0, n_0 &gt; 0, s.t. \forall n \geq n_0, 0 \leq c_1 g \left(n\right) \leq f \left(n\right) \leq c_2 g \left(n\right) \right\} $$</code></p>
<p>也就是说当 <code>$n$</code> 足够大时，函数 <code>$f \left(n\right)$</code> 能够被 <code>$c_1 g \left(n\right)$</code> 和 <code>$c_2 g \left(n\right)$</code> 夹在中间，我们称 <code>$g \left(n\right)$</code> 为 <code>$f \left(n\right)$</code> 的一个 <strong>渐进紧确界 (Asymptotically Tight Bound)</strong>。</p>
<ul>
<li><strong><code>$O$</code> 记号</strong></li>
</ul>
<p><code>$\Theta$</code> 记号给出了一个函数的上界和下界，当只有一个 <strong>渐进上界</strong> 时，可使用 <code>$O$</code> 记号。<code>$O \left(g \left(n\right)\right)$</code> 表示的函数集合为：</p>
<p><code>$$ O \left(g \left(n\right)\right) = \left\{f \left(n\right): \exists c &gt; 0, n_0 &gt; 0, s.t. \forall n \geq n_0, 0 \leq f \left(n\right) \leq c g \left(n\right)\right\} $$</code></p>
<p><code>$O$</code> 记号描述的为函数的上界，因此可以用它来限制算法在最坏情况下的运行时间。</p>
<ul>
<li><strong><code>$\Omega$</code> 记号</strong></li>
</ul>
<p><code>$\Omega$</code> 记号提供了 <strong>渐进下界</strong>，其表示的函数集合为：</p>
<p><code>$$ \Omega \left(g \left(n\right)\right) = \left\{f \left(n\right): \exists c &gt; 0, n_0 &gt; 0, s.t. \forall n \geq n_0, 0 \leq c g \left(n\right) \leq f \left(n\right)\right\} $$</code></p>
<p>根据上面的三个渐进记号，不难证明如下定理：</p>
<div class="blockquote" style='border-left: 4px solid #369BE5;'><strong>定理 1</strong> 对于任意两个函数 <code>$f \left(n\right)$</code> 和 <code>$g \left(n\right)$</code>，有 <code>$f \left(n\right) = \Theta \left(g \left(n\right)\right)$</code>，当且仅当 <code>$f \left(n\right) = O \left(g \left(n\right)\right)$</code> 且 <code>$f \left(n\right) = \Omega \left(g \left(n\right)\right)$</code>。</div>
<ul>
<li><strong><code>$o$</code> 记号</strong></li>
</ul>
<p><code>$O$</code> 记号提供的渐进上界可能是也可能不是渐进紧确的，例如 <code>$2n^2 = O \left(n^2\right)$</code> 是渐进紧确的，但 <code>$2n = O \left(n^2\right)$</code> 是非渐进紧确的。我们使用 <code>$o$</code> 记号表示非渐进紧确的上界，其表示的函数集合为：</p>
<p><code>$$ o \left(g \left(n\right)\right) = \left\{f \left(n\right): \forall c &gt; 0, \exists n_0 &gt; 0, s.t. \forall n \geq n_0, 0 \leq f \left(n\right) &lt; c g \left(n\right)\right\} $$</code></p>
<ul>
<li><strong><code>$\omega$</code> 记号</strong></li>
</ul>
<p><code>$\omega$</code> 记号与 <code>$\Omega$</code> 记号的关系类似于 <code>$o$</code> 记号与 <code>$O$</code> 记号的关系，我们使用 <code>$\omega$</code> 记号表示一个非渐进紧确的下界，其表示的函数集合为：</p>
<p><code>$$ \omega \left(g \left(n\right)\right) = \left\{f \left(n\right): \forall c &gt; 0, \exists n_0 &gt; 0, s.t. \forall n \geq n_0, 0 \leq c g \left(n\right) &lt; f \left(n\right)\right\} $$</code></p>
<h3 id="np-完全性">NP 完全性</h3>
<p>计算问题可以按照在不同计算模型下所需资源的不同予以分类，从而得到一个对算法问题“难度”的类别，这就是复杂性理论中复杂性类概念的来源 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。对于输入规模为 <code>$n$</code> 的问题，一个算法在最坏情况下的运行时间为 <code>$O \left(n^k\right)$</code>，其中 <code>$k$</code> 为一个确定的常数，我们称这类算法为 <strong>多项式时间的算法</strong>。</p>
<p>本节我们将介绍四类问题：P 类问题，NP 类问题，NPC 类问题和 NPH 类问题。</p>
<ul>
<li><strong>P 类问题</strong></li>
</ul>
<p>P 类问题 (Polynomial Problem，多项式问题) 是指能在多项式时间内 <strong>解决</strong> 的问题。</p>
<ul>
<li><strong>NP 类问题</strong></li>
</ul>
<p>NP 类问题 (Non-Deteministic Polynomial Problem，非确定性多项式问题) 是指能在多项式时间内被 <strong>证明</strong> 的问题，也就是可以在多项式时间内对于一个给定的解验证其是否正确。所有的 P 类问题都是 NP 类问题，但目前 (截至 2018 年，下文如不做特殊说明均表示截至到该时间) 人类还未证明 <code>$P \neq NP$</code> 还是 <code>$P = NP$</code>。</p>
<ul>
<li><strong>NPC 类问题 (NP-Complete Problems)</strong></li>
</ul>
<p>在理解 NPC 类问题之前，我们需要引入如下几个概念：</p>
<ol>
<li><strong>最优化问题 (Optimization Problem)</strong> 与 <strong>判定问题 (Decision Problem)</strong>：最优化问题是指问题的每一个可行解都关联一个值，我们希望找到具有最佳值的可行解。判定问题是指问题的答案仅为“是”或“否”的问题。NP 完全性仅适用于判定问题，但通过对最优化问题强加一个界，可以将其转换为判定问题。</li>
<li><strong>归约 (Reduction)</strong>：假设存在一个判定问题 A，该问题的输入称之为实例，我们希望能够在多项式时间内解决该问题。假设存在另一个不同的判定问题 B，并且已知能够在多项式时间内解决该问题，同时假设存在一个过程，它可以将 A 的任何实例 <code>$\alpha$</code> 转换成 B 的某个实例 <code>$\beta$</code>，转换操作需要在多项式时间内完成，同时两个实例的解是相同的。则我们称这一过程为多项式 <strong>规约算法 (Reduction Algorithm)</strong>。通过这个过程，我们可以将问题 A 的求解“归约”为对问题 B 的求解，从而利用问题 B 的“易求解性”来证明 A 的“易求解性”。</li>
</ol>
<p>从而我们可以定义 NPC 类问题为：首先 NPC 类问题是一个 NP 类问题，其次所有的 NP 类问题都可以用多项式时间归约到这类问题。因此，只要找到 NPC 类问题的一个多项式时间的解，则所有的 NP 问题都可以通过多项式时间归约到该问题，并用多项式时间解决该问题，从而使得 <code>$NP = P$</code>，但目前，NPC 类问题并没有找到一个多项式时间的算法。</p>
<ul>
<li><strong>NPH 类问题 (NP-Hard Problems)</strong></li>
</ul>
<p>NPH 类问题定义为所有的 NP 类问题都可以通过多项式时间归约到这类问题，但 NPH 类问题不一定是 NP 类问题。NPH 类问题同样很难找到多项式时间的解，由于 NPH 类问题相比较 NPC 类问题放松了约束，因此即便 NPC 类问题找到了多项式时间的解，NPH 类问题仍可能无法在多项式时间内求解。</p>
<p>下图分别展示了 <code>$P \neq NP$</code> 和 <code>$P = NP$</code> 两种假设情况下四类问题之间的关系：</p>
<p><img src="/images/cn/2018-11-18-computational-complexity-and-dynamic-programming/p-np-np-complete-np-hard.svg" alt=""></p>
<h2 id="动态规划">动态规划</h2>
<p><strong>动态规划 (Dynamic Programming, DP)</strong> 算法通常基于一个递归公式和一个或多个初始状态，并且当前子问题的解可以通过之前的子问题构造出来。动态规划算法求解问题的时间复杂度仅为多项式复杂度，相比其他解法，例如：回溯法，暴利破解法所需的时间要少。动态规划中的 “Programming” 并非表示利用计算机编程，而是一种表格法。动态规划对于每个子问题只求解一次，将解保存在一个表格中，从而避免不必要的重复计算。</p>
<p>动态规划算法的适用情况如下 <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>：</p>
<ol>
<li><strong>最优子结构性质</strong>，即问题的最优解由相关子问题的最优解组合而成，子问题可以独立求解。</li>
<li><strong>无后效性</strong>，即每个状态均不会影响之前的状态。</li>
<li><strong>子问题重叠性质</strong>，即在用递归算法自顶向下对问题进行求解时，每次产生的子问题并不总是新问题，有些子问题会被重复计算多次。</li>
</ol>
<p>一个动态规划算法的核心包含两个部分：<strong>状态</strong> 和 <strong>状态转移方程</strong>。状态即一个子问题的表示，同时这个表示需要具备 <strong>无后效性</strong>。状态转移方程用于描述状态之间的关系，也就是如何利用之前的状态构造出当前的状态进而求解。</p>
<p>动态规划有两种等价的实现方法：</p>
<ol>
<li><strong>带备忘的自顶向下法 (Top-Down with Memoization)</strong>，该方法采用自然的递归形式编写过程，但会保留每个子问题的解，当需要一个子问题的解时会先检查是否保存过，如果有则直接返回该结果。</li>
<li><strong>自底向上法 (Bottom-Up Method)</strong>，该方法需要恰当的定义子问题“规模”，任何子问题的求解都值依赖于“更小”的子问题的求解，从而可以按照子问题的规模从小到大求解。</li>
</ol>
<p>两种方法具有相同的渐进运行时间，在某些特殊的情况下，自顶向下的方法并未真正递归地考虑所有可能的子问题；自底向上的方法由于没有频繁的递归调用，时间复杂性函数通常具有更小的系数。</p>
<h3 id="背包问题">背包问题</h3>
<p><strong>背包问题 (Knapsack problem)</strong> 是一种组合优化的 NPC 类问题。问题可以描述为：给定一组物品，每种物品都有自己的重量和价值，在限定的总重量内，合理地选择物品使得总价值最高。</p>
<p>形式化的定义，我们有 <code>$n$</code> 种物品，物品 <code>$j$</code> 的重量为 <code>$w_j$</code>，价值为 <code>$p_j$</code>，假定所有物品的重量和价值都是非负的，背包所能承受的最大重量为 <code>$W$</code>。如果限定每种物品只能选择 0 个或 1 个，则该问题称为 <strong>0-1 背包问题</strong>；如果限定物品 <code>$j$</code> 最多只能选择 <code>$b_j$</code> 个，则该问题称为 <strong>有界背包问题</strong>；如果不限定每种物品的数量，则该问题称为 <strong>无界背包问题</strong>。最优化问题可以表示为：</p>
<p><code>$$ \begin{equation} \begin{split} \text{maximize} &amp; \sum_{j=1}^{n}{p_j x_j} \\ s.t. &amp; \sum_{j=1}^{n}{w_j x_j} \leq W, x_j \in \left\{0, 1, ..., b_j\right\} \end{split} \end{equation} $$</code></p>
<p>以 0-1 背包问题为例，用 <code>$d_{i, w}$</code> 表示取 <code>$i$</code> 件商品填充一个最大承重 <code>$w$</code> 的背包的最大价值，问题的最优解即为 <code>$d_{n, W}$</code>。不难写出 0-1 背包问题的状态转移方程如下：</p>
<p><code>$$ d_{i, w} =  \begin{cases} d_{i - 1, w}, &amp; w &lt; w_i \\ \max \left(d_{i - 1, w}, d_{i - 1, w - w_i} + p_i\right), &amp; w \geq w_i \\ 0, &amp; i w = 0 \end{cases} $$</code></p>
<p>一个 0-1 背包问题的具体示例如下：背包承受的最大重量 <code>$W = 10$</code>，共有 <code>$n = 5$</code> 种物品，编号分别为 <code>$A, B, C, D, E$</code>，重量分别为 <code>$2, 2, 6, 5, 4$</code>，价值分别为 <code>$6, 3, 5, 4, 6$</code>，利用 BP 求解该问题，不同 <code>$i, w$</code> 情况下的状态如下表所示 (计算过程详见 <a href="https://github.com/leovan/leovan.me/tree/master/scripts/cn/2018-11-18-computational-complexity-and-dynamic-programming/0-1-knapsack-dp.py">这里</a>)：</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center">i \ w</th>
          <th style="text-align: center">1</th>
          <th style="text-align: center">2</th>
          <th style="text-align: center">3</th>
          <th style="text-align: center">4</th>
          <th style="text-align: center">5</th>
          <th style="text-align: center">6</th>
          <th style="text-align: center">7</th>
          <th style="text-align: center">8</th>
          <th style="text-align: center">9</th>
          <th style="text-align: center">10</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">1</td>
          <td style="text-align: center">NA</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
      </tr>
      <tr>
          <td style="text-align: center">2</td>
          <td style="text-align: center">NA</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
      </tr>
      <tr>
          <td style="text-align: center">3</td>
          <td style="text-align: center">NA</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, C) <br/> 8 - 11</td>
          <td style="text-align: center">(A, C) <br/> 8 - 11</td>
          <td style="text-align: center">(A, B, C) <br/> 10 - 14</td>
      </tr>
      <tr>
          <td style="text-align: center">4</td>
          <td style="text-align: center">NA</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, D) <br/> 7 - 10</td>
          <td style="text-align: center">(A, C) <br/> 8 - 11</td>
          <td style="text-align: center">(A, B, D) <br/> 9 - 13</td>
          <td style="text-align: center">(A, B, C) <br/> 10 - 14</td>
      </tr>
      <tr>
          <td style="text-align: center">5</td>
          <td style="text-align: center">NA</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A) <br/> 2 - 6</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, B) <br/> 4 - 9</td>
          <td style="text-align: center">(A, E) <br/> 6 - 12</td>
          <td style="text-align: center">(A, E) <br/> 6 - 12</td>
          <td style="text-align: center">(A, B, E) <br/> 8 - 15</td>
          <td style="text-align: center">(A, B, E) <br/> 8 - 15</td>
          <td style="text-align: center">(A, B, E) <br/> 8 - 15</td>
      </tr>
  </tbody>
</table>
<p>其中，NA 表示未选取任何物品，单元格上部括号中的为选取物品的编号，单元格下部分别为选取物品的总重量和总价值。</p>
<h3 id="最长公共子序列与最长公共子串">最长公共子序列与最长公共子串</h3>
<p>给定一个序列 <code>$X = \left\{x_1, x_2, \dotsc, x_m\right\}$</code>，另一个序列 <code>$Z = \left\{z_1, z_2, \dotsc, z_k\right\}$</code> 在满足如下条件时称其为 <code>$X$</code> 的一个 <strong>子序例 (Subsequence)</strong>，即存在一个严格递增的 <code>$X$</code> 的下标序列 <code>$\left\{i_1, i_2, \dotsc, i_k\right\}$</code>，对于所有的 <code>$j = 1, 2, \dotsc, k$</code>，满足 <code>$x_{i_j} = z_j$</code>。给定两个序例 <code>$X$</code> 和 <code>$Y$</code>，如果 <code>$Z$</code> 既是 <code>$X$</code> 的子序列，也是 <code>$Y$</code> 的子序列，则称它为 <code>$X$</code> 和 <code>$Y$</code> 的 <strong>公共子序列 (Common Subsequence)</strong>。<strong>最长公共子序列 (Longest Common Subsequence)</strong> 问题为给定两个序列 <code>$X = \left\{x_1, x_2, \dotsc, x_m\right\}$</code> 和 <code>$Y = \left\{y_1, y_2, \dotsc, y_n\right\}$</code>，求 <code>$X$</code> 和 <code>$Y$</code> 最长的公共子序列。</p>
<p>我们可以按如下递归的方式求解最长公共子序列问题：</p>
<ol>
<li>当 <code>$x_i = y_j$</code> 时，求解 <code>$X = \left\{x_1, x_2, \dotsc, x_{i-1}\right\}$</code> 和 <code>$Y = \left\{y_1, y_2, \dotsc, y_{j-1}\right\}$</code> 的最长公共子序列，在其尾部添加 <code>$x_i$</code> 和 <code>$y_j$</code> 即为当前状态下的最长公共子序列。</li>
<li>当 <code>$x_i \neq y_j$</code> 时，我们则需求解 <code>$X = \left\{x_1, x_2, \dotsc, x_{i-1}\right\}$</code> 和 <code>$Y = \left\{y_1, y_2, \dotsc, y_j\right\}$</code> 与 <code>$X = \left\{x_1, x_2, \dotsc, x_i\right\}$</code> 和 <code>$Y = \left\{y_1, y_2, \dotsc, y_{j-1}\right\}$</code> 两种情况下最长的公共子序列作为当前状态下的最长公共子序列。</li>
</ol>
<p>用 <code>$c_{i, j}$</code> 表示<code>$X = \left\{x_1, x_2, \dotsc, x_i\right\}$</code> 和 <code>$Y = \left\{y_1, y_2, \dotsc, y_j\right\}$</code> 情况下的最长公共子序列的长度，则状态转移方程如下：</p>
<p><code>$$ c_{i, w} =  \begin{cases} c_{i - 1, j - 1} + i, &amp; x_i = y_j \\ \max \left(c_{i, j - 1}, c_{i - 1, j}\right), &amp; x_i \neq y_j \\ 0, &amp; i j = 0 \end{cases} $$</code></p>
<p>例如：给定序列 <code>$X = \left\{A, B, C, B, D, A, B\right\}$</code> 和序列 <code>$Y = \left\{B, D, C, A, B, A\right\}$</code>，不同状态下最长公共子序列如下表所示 (计算过程详见 <a href="https://github.com/leovan/leovan.me/tree/master/scripts/cn/2018-11-18-computational-complexity-and-dynamic-programming/longest-common-subsequence-dp.py">这里</a>)：</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center"><code>$j$</code></th>
          <th style="text-align: center">0</th>
          <th style="text-align: center">1</th>
          <th style="text-align: center">2</th>
          <th style="text-align: center">3</th>
          <th style="text-align: center">4</th>
          <th style="text-align: center">5</th>
          <th style="text-align: center">6</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center"><code>$i$</code></td>
          <td style="text-align: center"></td>
          <td style="text-align: center"><code>$y_j$</code></td>
          <td style="text-align: center"><strong>B</strong></td>
          <td style="text-align: center">D</td>
          <td style="text-align: center"><strong>C</strong></td>
          <td style="text-align: center">A</td>
          <td style="text-align: center"><strong>B</strong></td>
          <td style="text-align: center"><strong>A</strong></td>
      </tr>
      <tr>
          <td style="text-align: center">0</td>
          <td style="text-align: center"><code>$x_i$</code></td>
          <td style="text-align: center">0</td>
          <td style="text-align: center">0</td>
          <td style="text-align: center">0</td>
          <td style="text-align: center">0</td>
          <td style="text-align: center">0</td>
          <td style="text-align: center">0</td>
          <td style="text-align: center">0</td>
      </tr>
      <tr>
          <td style="text-align: center">1</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center"><strong>0</strong></td>
          <td style="text-align: center">0 (↑)</td>
          <td style="text-align: center">0 (↑)</td>
          <td style="text-align: center">0 (↑)</td>
          <td style="text-align: center">1 (↖)</td>
          <td style="text-align: center">1 (←)</td>
          <td style="text-align: center">1 (↖)</td>
      </tr>
      <tr>
          <td style="text-align: center">2</td>
          <td style="text-align: center"><strong>B</strong></td>
          <td style="text-align: center">0</td>
          <td style="text-align: center"><strong>1 (↖)</strong></td>
          <td style="text-align: center"><strong>1 (←)</strong></td>
          <td style="text-align: center">1 (←)</td>
          <td style="text-align: center">1 (↑)</td>
          <td style="text-align: center">2 (↖)</td>
          <td style="text-align: center">2 (←)</td>
      </tr>
      <tr>
          <td style="text-align: center">3</td>
          <td style="text-align: center"><strong>C</strong></td>
          <td style="text-align: center">0</td>
          <td style="text-align: center">1 (↑)</td>
          <td style="text-align: center">1 (↑)</td>
          <td style="text-align: center"><strong>2 (↖)</strong></td>
          <td style="text-align: center"><strong>2 (←)</strong></td>
          <td style="text-align: center">2 (↑)</td>
          <td style="text-align: center">2 (↑)</td>
      </tr>
      <tr>
          <td style="text-align: center">4</td>
          <td style="text-align: center"><strong>B</strong></td>
          <td style="text-align: center">0</td>
          <td style="text-align: center">1 (↖)</td>
          <td style="text-align: center">1 (↑)</td>
          <td style="text-align: center">2 (↑)</td>
          <td style="text-align: center">2 (↑)</td>
          <td style="text-align: center"><strong>3 (↖)</strong></td>
          <td style="text-align: center">3 (←)</td>
      </tr>
      <tr>
          <td style="text-align: center">5</td>
          <td style="text-align: center">D</td>
          <td style="text-align: center">0</td>
          <td style="text-align: center">1 (↑)</td>
          <td style="text-align: center">2 (↖)</td>
          <td style="text-align: center">2 (↑)</td>
          <td style="text-align: center">2 (↑)</td>
          <td style="text-align: center"><strong>3 (↑)</strong></td>
          <td style="text-align: center">3 (↑)</td>
      </tr>
      <tr>
          <td style="text-align: center">6</td>
          <td style="text-align: center"><strong>A</strong></td>
          <td style="text-align: center">0</td>
          <td style="text-align: center">1 (↑)</td>
          <td style="text-align: center">2 (↑)</td>
          <td style="text-align: center">2 (↑)</td>
          <td style="text-align: center">3 (↖)</td>
          <td style="text-align: center">3 (↑)</td>
          <td style="text-align: center"><strong>4 (↖)</strong></td>
      </tr>
      <tr>
          <td style="text-align: center">7</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">0</td>
          <td style="text-align: center">1 (↖)</td>
          <td style="text-align: center">2 (↑)</td>
          <td style="text-align: center">2 (↑)</td>
          <td style="text-align: center">3 (↑)</td>
          <td style="text-align: center">4 (↖)</td>
          <td style="text-align: center"><strong>4 (↑)</strong></td>
      </tr>
  </tbody>
</table>
<p>其中，每个单元格前面的数字为最长公共子序列的长度，后面的符号为还原最长公共子序列使用的备忘录符号。</p>
<p><strong>最长公共子串 (Longest Common Substring)</strong> 同最长公共子序列问题略有不同，子序列不要求字符是连续的，而子串要求字符必须是连续的。例如：给定序列 <code>$X = \left\{A, B, C, B, D, A, B\right\}$</code> 和序列 <code>$Y = \left\{B, D, C, A, B, A\right\}$</code>，最长公共子序列为 <code>$\left\{B, C, B, A\right\}$</code>，而最长公共子串为 <code>$\left\{A, B\right\}$</code> 或 <code>$\left\{B, D\right\}$</code>。用 <code>$c_{i, j}$</code> 表示<code>$X = \left\{x_1, x_2, \dotsc, x_i\right\}$</code> 和 <code>$Y = \left\{y_1, y_2, \dotsc, y_j\right\}$</code> 情况下的最长公共子串的长度，则状态转移方程如下：</p>
<p><code>$$ c_{i, w} =  \begin{cases} c_{i - 1, j - 1} + i, &amp; x_i = y_j \\ 0, &amp; x_i \neq y_j \\ 0, &amp; i j = 0 \end{cases} $$</code></p>
<p>利用动态规划可以在 <code>$\Theta \left(nm\right)$</code> 的时间复杂度内求解，利用广义后缀树 <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> 可以进一步降低问题求解的时间复杂度 <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>。</p>
<h3 id="floyd-warshall-算法">Floyd-Warshall 算法</h3>
<p><strong>Floyd-Warshall 算法</strong> 是一种求解任意两点之间 <strong>最短路</strong> 的算法，相比 <strong>Dijkstra 算法</strong> <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>，Floyd-Warshall 算法可以处理有向图或负权图 (但不可以存在负权回路) 的情况 <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>。</p>
<p>用 <code>$d_{i, j}^{\left(k\right)}$</code> 表示从 <code>$i$</code> 到 <code>$j$</code> 路径上最大节点的标号为 <code>$k$</code> 的最短路径的长度。有：</p>
<ol>
<li><code>$d_{i, j}^{\left(k\right)} = d_{i, k}^{\left(k-1\right)} + d_{k, j}^{\left(k-1\right)}$</code>，若最短路径经过点 <code>$k$</code>。</li>
<li><code>$d_{i, j}^{\left(k\right)} = d_{i, j}^{\left(k-1\right)}$</code>，若最短路径不经过点 <code>$k$</code>。</li>
</ol>
<p>则状态转移方程如下：</p>
<p><code>$$ d_{i, j}^{\left(k\right)} =  \begin{cases} w_{i, j}, &amp; k = 0 \\ \min \left(d_{i, j}^{\left(k-1\right)}, d_{i, k}^{\left(k-1\right)} + d_{k, j}^{\left(k-1\right)}\right), &amp; k \leq 1 \end{cases} $$</code></p>
<p>以下图所示的最短路问题为例：</p>
<p><img src="/images/cn/2018-11-18-computational-complexity-and-dynamic-programming/shortest-path.png" alt=""></p>
<p>Floyd-Warshall 算法的求解伪代码如下所示：</p>


<div><pre class="pseudocode">
\begin{algorithm}
\caption{Floyd-Warshall 算法}
\begin{algorithmic}
\REQUIRE \\
    边集合 $w$ \\
    顶点数量 $c$
\ENSURE \\
    距离矩阵 $d$ \\
    备忘录矩阵 $m$
\FUNCTION{Floyd-Warshall}{$w, c$}
\FOR{$i$ = $1$ to $c$}
    \FOR{$j$ = $1$ to $c$}
        \STATE $d_{i, j} \gets \infty$
    \ENDFOR
\ENDFOR
\FOR{$i$ = $1$ to $c$}
    \STATE $d_{i, i} \gets 0$
\ENDFOR
\FORALL{$w_{i, j}$}
    \STATE $d_{i, j} \gets w_{i, j}$
\ENDFOR
\FOR{$k$ = $1$ to $c$}
    \FOR{$i$ = $1$ to $c$}
        \FOR{$j$ = $1$ to $c$}
            \IF{$d_{i, j} > d_{i, k} + d_{k, j}$}
                \STATE $d_{i, j} \gets d_{i, k} + d_{k, j}$
                \STATE $m_{i, j} \gets k$
            \ENDIF
        \ENDFOR
    \ENDFOR
\ENDFOR
\ENDFUNCTION
\end{algorithmic}
\end{algorithm}
</pre></div>

<p>通过备忘录矩阵 <code>$m$</code>，恢复从点 <code>$i$</code> 到点 <code>$j$</code> 的过程如下所示：</p>


<div><pre class="pseudocode">
\begin{algorithm}
\caption{Floyd-Warshall-Path 算法}
\begin{algorithmic}
\REQUIRE \\
    备忘录矩阵 $m$ \\
    起点 $i$ \\
    终点 $j$ \\
    路径 $p$
\FUNCTION{Floyd-Warshall-Path}{$m, i, j, p$}
\IF{$i == j$}
    \RETURN
\ENDIF
\IF{$m_{i, j} == 0$}
    \STATE $p \gets p \cup j$
\ELSE
    \STATE Floyd-Warshall-Path($m, i, m_{i, j}, p$)
    \STATE Floyd-Warshall-Path($m, m_{i, j}, j, p$)
\ENDIF
\ENDFUNCTION
\end{algorithmic}
\end{algorithm}
</pre></div>

<blockquote>
<p>文章部分内容参考了 Thomas H. Cormen 等人的《算法导论》</p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://zh.wikipedia.org/zh/%E8%AE%A1%E7%AE%97%E5%A4%8D%E6%9D%82%E6%80%A7%E7%90%86%E8%AE%BA">https://zh.wikipedia.org/zh/计算复杂性理论</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://zh.wikipedia.org/zh/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92">https://zh.wikipedia.org/zh/动态规划</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://zh.wikipedia.org/zh/%E5%90%8E%E7%BC%80%E6%A0%91">https://zh.wikipedia.org/zh/后缀树</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://zh.wikipedia.org/zh/%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E4%B8%B2">https://zh.wikipedia.org/zh/最长公共子串</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://zh.wikipedia.org/zh/%E6%88%B4%E5%85%8B%E6%96%AF%E7%89%B9%E6%8B%89%E7%AE%97%E6%B3%95">https://zh.wikipedia.org/zh/戴克斯特拉算法</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://zh.wikipedia.org/zh/Floyd-Warshall%E7%AE%97%E6%B3%95">https://zh.wikipedia.org/zh/Floyd-Warshall算法</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

        ]]></description></item></channel></rss>