算法设计与分析——动态规划

1.动态规划基础

1.1动态规划的基本思想

        动态规划建立在最优原则的基础上,在每一步决策上列出可能的局部解,按某些条件舍弃不能得到最优解的局部解,通过逐层筛选减少计算量。每一步都经过筛选,以每一步的最优性来保证全局的最优性。具体来说,动态规划算法仍然是将待求解的问题的若干子问题,采用列表技术,将从小到大的子问题的计算答案存储于一张表中,由于将原问题分解后的各个子问题可能存在重复,所以当重复遇到该子问题时,只需要查表继续问题的求解,而不需要重复计算。所以动态规划算法的基本思想是记录子问题并不断填表。

1.2动态规划的基本要素

        通常一个可以用动态规划算法求解的问题应该具有3个要素:最优子结构、无后效性和子问题重叠性。

        最优子结构:动态规划算法的关键在于正确的找出基本的递推关系式和恰当的边界条件。要做到这一点,必须将原问题分解为几个相互联系的阶段,在每一个子问题的求解中,均利用它前面子问题的最优化结果,依次进行,最有一个子问题所得的最优解就是整个问题的最优解。

        无后效性:将各个阶段依次排好之后,一旦某阶段的状态已经确定,它以前各阶段的状态无法直接影响未来的决策,并且当前状态的决策只是对以往决策的总结。

        子问题重叠性:动态规划计算最优值时,每次计算所产生的子问题并不总是新问题,有些问题被重复计算多次,但是动态规划将这些子问题的解存放在表格中,不需要重复计算,提高了程序的效率。

1.3动态规划的基本方法

        动态规划问题千奇百怪,有诸多变种,但是动态规划具有比较鲜明的特征,即最优子结构和重叠子问题。解决动态规划问题的思路很重要,掌握下面五步之后再加以练习能够解决许多动态规划问题。

  1. 确定dp的含义:dp数组中存放的是每个子问题的最优解。
  2. 推导动态转移方程:在动态规划问题中
  3. dp的初始化
  4. 遍历顺序
  5. 打印表格

2.矩阵连乘问题

        给定n个矩阵\left \{ {A_{1},A_{2},...,A_{n}} \right \},其中矩阵A_{i}的维数为p_{i-1}×p_{i},且A_{i}A_{i+1}是可乘的,考察这n个矩阵的连乘积A_{1}A_{2}...A{n}。由于矩阵乘法满足结合律,所以计算矩阵的连乘可以有许多不同的计算次序,这种计算次序可以用加括号的方式来确定。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少?

        设有四个矩阵A、B、C、D,它们的维数分别是:50×10,10×40,40×30,30×5,其完全加括号方式为:(A((BC)D)),(A(B(CD))),((AB)(CD)),(((AB)C)D),((A(BC))D)所需的乘法次数分别为16000,10500,36000,87500,34500。

        对于n个矩阵的连乘积,设其不同的计算次序为p(n)。每种加括号方式都可以分解为两个矩阵的加括号方式:(A_{1}...A_{k})(A_{k+1}...A_{n}),其递推式为:

                                                      p(n)=\sum_{k=1}^{n-1}p(k)p(n-k)

卡特兰数是组合数学中一个常出现在各种计数问题中的数列。其递推式如下:

                                                h(n)=\sum_{k=0}^{n-1}h(k)h(n-k-1)

 该递推关系的解为:C_{n}=\frac{(2n)!}{(n+1)!n!}

 卡特兰数的渐近增长为 C_{n}~\frac{4^{n}}{n^{3/2}\sqrt{\pi }}

2.1分析最优子结构

        设n个矩阵连乘的最佳计算次序为(A_{1}...A_{k})(A_{k+1}...A_{n}),则(A_{1}...A_{k})(A_{k+1}...A_{n})连乘的计算次序都是最优的。矩阵连乘计算次序问题的最优解包含着子问题的最优解。这种性质称为最优子结构性质,问题的最优子结构性质是该问题可用DP方法求解的显著特征。

2.2递归关系建立

        将矩阵连乘积(A_{i}A_{i+1}...A_{j})记为A[i:j],这里i\leqslant jA[i:j]的总计算量为:A[i:k]的计算量加上A[k+1:j]的计算量,再加上A[i:k]A[k+1:j]相乘的计算量。

        设计算A[i:j]的最佳计算次序所对应的乘法次数为m[i:j],则原问题的最优解为m[1:n]

        当i=j时,A[i:j]=A_i,因此m_i=0,i=1,2,...,n

        当i<j时,m[i,j]=min_{i\leqslant k\leqslant j}(m[i,k]+m[k+1,j] + p_{i-1}p_{k}p_{j})

2.3代码分析

#include<stdio.h>
#define N 7
void MatrixChain(int *p,int n,int m[][N],int s[][N])
{
    for(int i = 0; i <= n; ++i) m[i][i] = 0;//自乘的消耗为0
    for(int r = 2; r <= n; ++r)
    {
        for(int i = 1; i <= n - r + 1; ++i)
        {
            int j = i + r - 1;
            m[i][j] = m[i+1][j] + p[i-1]*p[i]*p[j];//试探性的赋值。
            s[i][j] = i;
            for(int k = i + 1; k < j; ++k)
            {
                int t = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
                if(t < m[i][j])
                {
                    m[i][j] = t;      
                    s[i][j] = k;
                }
            }
        }
    }
}

void Traceback(int i, int j, int s[][N]){
    if(i == j) printf("A%d",i);
    else
    {
        printf("(");
        Traceback(i,s[i][j],s);
        Traceback(s[i][j]+1,j,s);
        printf(")");
     }
}      
 
int main()
{
    int p[N]={30,35,15,5,10,20,24};
    int m[N][N],s[N][N];
    MatrixChain(p,N-1,m,s);
    printf("矩阵的最佳乘积方式为:");
    Traceback(1,6,s);
    return 0;
}

3.电路布线问题

        在一块电路板的上、下两端分别有n个接线柱。根据电路设计,要求用导线(i,\pi (i))将上端接线柱与下端接线柱相连,如图所示:

                

其中\pi(i)\{1,2,...,n\}的一个排列,导线(i,\pi (i))称为该电路的第i条连线。对于任何1\leqslant i< j\leqslant n,第i条连线和第j条连线相交的充分且必要条件是\pi(i)>\pi(j)

        在制作电路板时,要求将这n条连线分布到若干绝缘层上。在同一层上的连线不可相交。电路布线问题要确定将那些连线安排在第一层上,使得该层上要有尽可能多的连线。换句话说,该问题要确定导线集Nets最大不相交子集

3.1最优子结构分析

        记N(i,j)=\{t|(t,\pi (t))\in Nets,t\leqslant i,\pi (t)\leqslant j \}N(i,j)的最大不相交子集为MNS(i,j)Size(i,j)=|MNS(i,j)|

(1)当i=1时,MNS(1,j)=N(1,j)=\{(1,\pi(1))\}

(2)当i>1时,

        若j<\pi(i)。此时,(i,\pi(i)\nsubseteq N(i,j))。所以N(i,j) = N(i-1,j),从而Size(i,j)=Size(i-1,j)

        若j\geq \pi(i)。此时,

        如果(i,\pi(i)\in MNS(i,j)),则对任意的(t,\pi(t)\in \in MNS(i,j))t<i\pi(t)<\pi(i)。在这种情况下MNS(i,j)-\{(i,\pi(i))\}N(i-1,\pi(i)-1)的最大不相交子集,从而Size(i,j)=Size(i-1,\pi(i)-1)+1

        如果(i,\pi(i)\nsubseteq MNS(i,j)),则对任意的(t,\pi(t)\in \in MNS(i,j)),有t<i。从而MNS(i-1,j)\subseteq N(i,j)。因此,Size(i,j)\geqslant Size(i-1,j)。另一方面有MNS(i,j)\subseteq N(i-1,j),因此又有Size(i,j)\leqslant Size(i-1,j),从而有Size(i,j)= Size(i-1,j)

3.2递归关系建立

(1)当i=1时,Size(1,j)=\left\{\begin{matrix} 0~~~~~ j<\pi(1) & \\ 1~~~~~ j \geqslant\pi(1)& \end{matrix}\right.

(2)当i>1时,

                                Size(i,j)=\left\{\begin{matrix} Size(i-1,j) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~j< \pi(i)& \\ max\{Size(i-1,j),Size(i-1,\pi(i)-1)+1\}~~~~~j\geqslant \pi(i)& \end{matrix}\right.

        电路布线问题的最优值为Size(n,n)

3.3代码分析

void MNS(int C[],int n,int **size)
{
    for(int j = 0; j < C[1];++j) size[1][j] = 0;
    for(int j = C[1]; j <= n;++j) size[1][j] = 1;
    for(int i = 2; i < n; ++i)
    {
        for(int j = 1; j < C[i]; ++j)    
            size[i][j] = size[i-1][j];
        for(int j = C[i]; j <= n; ++j)    
            size[i][j] = max(size[i-1][j],size[i-1][C[i]-1]+1);
    }
    size[n][n] = max(size[n-1][n],size[n-1][C[n]-1]+1);
}

4.最长公共子序列 

        若给定子序列X=\{x_{1},x_{2},...,x_{m}\},则Z=\{z_{1},z_{2},...z_{k}\}是X的子序列是指存在一个严格递增下标序列\{i_{1},i_{2},...i_{k}\}使得对于所有的j=1,2,...,kz_{j}=x_{i_{j}}

        给定两个序列XY,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列XY公共子序列。我们的问题是给定两个序列X=\{x_{1},x_{2},...,x_{m}\}Y=\{y_{1},y_{2},...,y_{n}\},找出XY最长公共子序列

4.1最优子结构分析

        设序列X=\{x_{1},x_{2},...,x_{m}\}Y=\{y_{1},y_{2},...,y_{n}\}的最长公共子序列为Z_{k}=\{z_{1},z_{2},...z_{k}\},则

(1)若x_{m}=y_{n},则z_{k}=x_{m}=y_{n},且Z_{k-1}X_{m-1}Y_{n-1}的最长公共子序列。

(2)若x_{m}\neq y_nz_k\neq x_m,则Z_kX_{m-1}Y_n的最长公共子序列。

(3)若x_{m}\neq y_nz_k\neq y_n,则Z_kX_{m}Y_{n-1}的最长公共子序列。

由此可见,两个序列的最长公共子序列包含了这两个序列的前缀的最长公共子序列。因此,最长公共子序列问题具有最优子结构性质

4.2递归关系建立

       设二维数组c[i][j]记录序列X_iY_j的最长公共子序列的长度。其中X=\{x_{1},x_{2},...,x_{m}\}Y=\{y_{1},y_{2},...,y_{n}\}。当i=0j=0时,空序列是它们的最长公共子序列,此时c[i][j]=0其它情况下:

        c[i][j]=\left\{\begin{matrix} 0~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~i=0~or~j=0\\ c[i-1][j-1]+1~~~~~~~~~~~~~~~~~~i,j>0~and~x_i=y_j\\ max\{c[i][j-1],c[i-1][j]\}~~~~~~i,j>0~and~x_i\neq y_j\end{matrix}\right.

4.3代码分析

void LCSLength()
{
    for(int i = 1; i <= m; ++i) c[i][0] = 0;//存放各个子问题的最优值
    for(int j = 1; j <= n; ++j) b[0][j] = 0;//存放各个子问题最优值的来源
    for(int i = 1; i < m; ++i)
    {
        for(int j = 1; i <= n; ++j)
        {
            if(x[i]==y[j])
            {
                c[i][j] = c[i-1][j-1] + 1;
                b[i][j] = 1;
            }
            else if(c[i-1][j] >= c[i][j-1])
            {
                c[i-1] = c[i-1][j];
                b[i][j] = 3;
            }
            else 
            {
                c[i][j] = c[i][j-1];
                b[i][j] = 2;
            }
        }
    }
}
                

5.图像压缩问题

        图像的变位压缩存储格式将所给的像素点序列\{p_1,p_2,...p_n\},0\leqslant p_i\leqslant 255分割m个连续段S_1,S_2,...,S_m。第i个像素段S_i(1\leqslant i\leqslant m)中,有l[i]个像素,且该段中每个像素都只用b[i]位表示。设t[i]=\sum_{k=1}^{i-1}l[k],则第i个像素段S_i

                                                      S_i=\{p_{t[i]+1},...,p_{t[i]+l[i]}\} ~~~1\leqslant i\leqslant m

h_i=[log(max(p_k)+1)],则h_i\leqslant b[i]\leqslant 8。因此需要用3位表示b[i],如果限制1\leqslant l[i]\leqslant 255,则需要用8位来表示b[i]。因此第i个像素段所需要的存储空间为l[i]*b[i]+11位。按此格式存储像素序列\{p_1,p_2,...p_n\},则需要\sum_{i=1}^{m}l[i]*b[i]+11m位的空间。

        图像压缩问题要求确定像素序列\{p_1,p_2,...p_n\}的最优分段,使得依此分段所需要的存储空间最少。每个分段的长度不超过255位。

5.1最优子结构分析

        设l[i]b[i](1\leqslant i\leqslant m)\{p_1,p_2,...p_n\}的最优分段。显而易见,l[1]b[1]\{p_1,...p_{l[1]}\}的最优分段。图像压缩问题满足最优子结构性质。

        设s[i]1\leqslant i\leqslant n,是像素序列\{p_1,...,p_i\}的最优分段所需的存储位数。

                                        s[i]=min\{s[i-k]+k*bmax(i-k+1,i)\}+11

其中bmax(i,j)=log(max(p_k)+1)

5.2代码分析

void Compress(int n, int p[], int s[], int l[], int b[]) {
    const int Lmax = 256;
    const int header = 11;
    s[0] = 0;
    for (int i = 1; i <= n; i++) {
        b[i] = length(p[i]); // 计算像素点 p[i] 需要的存储位数
        int bmax = b[i];
        s[i] = s[i - 1] + bmax; // 赋初值
        l[i] = 1;
        for (int k = 2; k <= i && k <= Lmax; k++) {
            if (bmax < b[i - k + 1]) {
                bmax = b[i - k + 1];
            }
            if (s[i] > s[i - k] + k * bmax) {
                s[i] = s[i - k] + k * bmax;
                l[i] = k;
            }
        }
        s[i] += header; // 添加头部信息的开销
    }
}

6.凸多边形最优三角剖分

        凸多边形:一个简单多边形及其内部构成一个闭凸集时,称该简单多边形为凸多边形,即凸多边形边界上或内部的任意两点所连成的直线段上所有点均在凸多边形的内部或边界上。

        为方便描述,用多边形顶点的逆时针序列表示凸多边形,即p=\{v_0,v_1,...v_n\},表示具有n+1条边的凸多边形。  

        若v_iv_j是多边形上的不相邻的两个顶点,则线段v_iv_j称为多边形的一条弦。弦将多边形分割成两个多边形\{v_i,v_{i+1},...v_j\}\{v_j,v_{j+1},...v_i\}

        多边形的三角剖分是将多边形分割成互不相交的三角形的弦的集合T。

        凸多边形的最优三角剖分:给定凸多边形P,以及定义在由多边形的边和弦组成的三角形上的权函数w,要求确定该凸多边形的三角剖分,使得该三角剖分中诸三角形上权之和为最小。

6.1最优子结构分析

        假设存在一个凸多边形的最优剖分P,它的一个子凸多边形Q不是最优剖分。也就是说存在一个代价更小的三角剖分Q^{'}。如果是这样的话,使用Q^{'}替换Q,在保证其它子三角剖分不变的情况下,会产生一个新的整体三角剖分P^{'},它的代价更小,则与P是最优三角剖分的假设矛盾。所以,凸多边形的最优三角剖分具有最优子结构性。

6.2递归关系建立

        定义t[i][j]1\leqslant i\leqslant j\leqslant n为凸子多边形\{v_{i-1},v_i,...,v_j\}的最优三角剖分所对应的权函数值,取其最优值。为方便起见,退化的多边形\{v_{i-1},v_i\}具有权值0。根据此定义,要凸多边形(n+1)P的最有权值为t[1][n]

        t[i][j]的值可以利用最优子结构性质递归地计算。当j-i\geqslant > 1时,凸子多边形至少有3个顶点。由最优子结构性质,t[i][j]的值应为t[i][k]+t[k+1][j]+v_{i-1}v_kv_jv_{i-1}v_kv_j代表该三角形的权值,其中(i\leqslant k \leqslant j-1)。因此

                t[i][j]=\left\{\begin{matrix} 0~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~i=j\\ min\{t[i][k]+t[k+1][j]+w(v_{i-1}v_kv_j)\}~~~~~~i<j\end{matrix}\right.

6.3代码分析

        

void MinWeightTriangulation(int *weights, int n) {
    int t[N][N] = {0}; // 用于存储子问题的最优解
    int s[N][N] = {0}; // 用于存储分割点
    // 初始化
    for (int i = 1; i < n; i++) {
        t[i][i] = 0;
    }
    // 动态规划计算
    for (int r = 2; r < n; r++) {
        for (int i = 1; i < n - r + 1; i++) {
            int j = i + r - 1;
            t[i][j] = t[i + 1][j] + get_weight(i - 1, i, j, weights);
            s[i][j] = i;

            // 尝试所有分割点
            for (int k = i + 1; k < j; k++) {
                int u = t[i][k] + t[k + 1][j] + get_weight(i - 1, k, j, weights);
                if (u < t[i][j]) {
                    t[i][j] = u;
                    s[i][j] = k;
                }
            }
        }
    }
}

7.0-1背包问题 

        给定n个物品和1个背包。物品i的重量是w_i,其价值为v_i,背包的容量为W。如何选择装入背包的物品,使得装入背包中物品的总价值最大? 通常称物体不可分割的背包问题为0-1背包问题。

        问题的形式化描述为,给定W>0w_i>0v_i>01\leqslant i\leqslant n,要求找出n元0-1向量(x_1,x_2,...,x_n),满足:

                                                        max\sum_{i=1}^{n}v_ix_i

                                                        \left\{\begin{matrix} \sum_{i=1}^{n}w_ix_i\leqslant W\\ x_i\in \{0,1\},1\leqslant i\leqslant n \end{matrix}\right.

7.1最优子结构性分析  

        假设(x_1,x_2,...,x_n)是所给0-1背包问题的已给最优解,则(x_2,...,x_n)是下面相应子问题的一个最优解:

                                                        max\sum_{i=2}^{n}v_ix_i

                                                        \left\{\begin{matrix} \sum_{i=2}^{n}w_ix_i\leqslant W-w_1x_1\\ x_i\in \{0,1\},2\leqslant i\leqslant n \end{matrix}\right.

7.2递归关系建立

        令C[i][j]表示子问题\left\{\begin{matrix} \sum_{k=1}^{i}w_kx_k\leqslant j\\ x_k\in \{0,1\} ~~~~~1\leqslant k\leqslant i \end{matrix}\right.的最优解。C[i-1][j-w_ix_i]表示该问题的子问题\left\{\begin{matrix} \sum_{k=1}^{i-1}w_kx_k\leqslant j-w_ix_i\\ x_k,x_k\in \{0,1\} ~~~~~1\leqslant k\leqslant i-1 \end{matrix}\right.的最优解。        

        最优解的递归关系式为:

                                C[i][0]=c[0][j]=0, ~~1\leqslant i\leqslant n,~~1\leqslant j\leqslant W

                                C[i][j]=\left\{\begin{matrix} C[i-1][j]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~j<w_i\\ max\{C[i-1][j],C[i-1][j-w_i]+v_i\}~~~j\geqslant w_i \end{matrix}\right.

7.3代码分析

void knapsack(int W, int* p, int *w, int size)
{
    int C[size][W];//用于存储子问题的最优解
    for(int i = 0; i < size; ++i)
        for(int j = 0; j < W; ++j)
            C[i][j] = 0;
    for(int i = 1; i < size; ++i)
        for(int j = 1; j < W; ++j)
        {

            if(w[i-1] < j)
            {
                C[i][j]=max(C[i-1][j],C[i-1][j-w[i-1]+p[i-1]);
            }

            else C[i][j] = C[i-1][j];
        }
}
        

8.最优二叉查找树

        给定n个关键字组成的有序序列S=\{s_1,s_2,...,s_n\},用这些关键字构造一棵二叉查找树 T,该树具有性质:存储于每个节点的元素大于左子树中任一个节点中的元素,小于其右子树中任意节点的元素。

        通常用平均比较次数来作为衡量不同二叉查找树查找效率的标准。设在表示为S=\{s_1,s_2,...,s_n\}的二叉查找树T中,元素s_i的结点深度为c_i(1\leqslant i\leqslant n),查找概率为p_i;虚节点为\{e_0,e_1,...,e_n\}e_j的结点深度为d_j,查找概率为q_j(0\leqslant j\leqslant n)。那么平均比较次数通常被定义为:

                                        C=\sum_{i=1}^{n}p_i(1+c_i)+\sum_{j=0}^{n}q_jd_j

        最优二叉查找树是在所有表示有序序列的二叉查找树中,具有最小平均比较次数的二叉树。

8.1最优子结构分析

        将由实结点\{s_1,s_2,...,s_n\}和虚结点\{e_0,e_1,...,e_n\}构成的二叉查找树记为T(1,n)。设定元素s_k作为该树的根结点,1\leqslant k\leqslant n。则二叉查找树T(1,n)的左子树由实结点\{s_1,s_2,...,s_{k-1}\}和虚结点\{e_0,e_1,...,e_k-1\}组成,记为T(1,k-1),而右子树由实结点\{s_{k-1},...,s_{n}\}和虚结点\{e_{k-1},...,e_{n}\}组成,记为T(k+1,n) 。

        如果T(1,n)是最优二叉查找树,假设它的左子树不是一个最优二叉查找树,也就是说存在另一个二叉查找树有更小的查找次数,那么在右子树不变的情况下,拥有该左子树的二叉查找树的效率比原树更高,那么原树就不是最优二叉查找树。则左子树T(1,k-1)和右子树T(k+1,n)也是最优二叉查找树。

8.2递归关系建立

        设T(1,n)的一棵由实结点\{s_1,s_2,...,s_j\}和虚节点\{e_0,e_1,...,e_j\}构成的最优二叉查找子树为T(i,j),则C'^{}[i][j]表示 T[i][j]的平均比较次数。选定结点作为T[i][j]的根结点,则左子树为T(i,k-1),右子树T(k+1,j),相应的比较次数分别为C'[i][k-1]C'[k+1][j]。用p[n]表示查找实结点的概率,用q[n]表示需节点的查找概率。

                        w_{ij}C'[i][j]=w_{i(k-1)}C'i][k-1]+w_{(k+1)j}C'[k+1][j]+w_{ij}

其中w_{ij}=\sum_{m=i}^{j}p_m+\sum_{i=i-1}^{j}q_t~~~~~1\leqslant i\leqslant j\leqslant n

C[i][j]=w_{ij}C'[i][j]

得到:C[i][j]=w_{ij}+min\{C[i][k-1]+C[k+1][j]\}

其中w_{ij}=w_{i(j-1)}+p_j+q_j

8.3代码分析

#include <iostream>
#include <vector>
#include <climits>

using namespace std;

void build_optimal_bst(vector<int> s, vector<double> p, vector<double> q) {
    int n = s.size();
    
    // 初始化 C 和 R 数组
    vector<vector<double>> C(n + 1, vector<double>(n + 1, 0));
    vector<vector<int>> R(n + 1, vector<int>(n + 1, 0));
    
    // 计算 W 数组
    vector<vector<double>> W(n + 1, vector<double>(n + 1, 0));
    for (int i = 1; i <= n; ++i) {
        W[i][i - 1] = q[i - 1];
    }
    
    // 动态规划填充 C 和 R 数组
    for (int l = 1; l <= n; ++l) {  // 子树长度从1到n
        for (int i = 0; i <= n - l; ++i) {
            int j = i + l;
            C[i][j] = numeric_limits<double>::max();
            for (int r = i; r < j; ++r) {
                double t = W[i][j] + C[i][r] + C[r + 1][j];
                if (t < C[i][j]) {
                    C[i][j] = t;
                    R[i][j] = r;
                }
            }
        }
    }
    
    // 更新 W 数组
    for (int l = 1; l <= n; ++l) {
        for (int i = 0; i <= n - l; ++i) {
            int j = i + l;
            W[i][j] = W[i][j - 1] + p[j] + q[j + 1];
        }
    }

    cout << "Cost matrix C:" << endl;
    for (int i = 0; i <= n; ++i) {
        for (int j = 0; j <= n; ++j) {
            cout << C[i][j] << " ";
        }
        cout << endl;
    }
    
    cout << "\nRoot position matrix R:" << endl;
    for (int i = 0; i <= n; ++i) {
        for (int j = 0; j <= n; ++j) {
            cout << R[i][j] << " ";
        }
        cout << endl;
    }
}

int main() {
    vector<int> s = {1, 3, 5, 7};  // 有序序列 S
    vector<double> p = {0.15, 0.1, 0.25, 0.1};  // 查找概率 p
    vector<double> q = {0.05, 0.15, 0.1, 0.15, 0.05};  // 边界及间隙概率 q

    // 构建 OBST
    build_optimal_bst(s, p, q);

    return 0;
}

        

        

        

        

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/900331.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NavMesh只制作可移动的导航网,清除多余不可走区域

只制作可移动的导航网。它使存储文件大小减小并提高性能。它消除了迁移到随机区域的问题。添加链接描述 1.如何使用 2.创建一个包含“NavMeshCleaner”组件的对象。Andadd指向可定制区域。 按住控制键并单击添加点。如果要移动它&#xff0c;请按 输入上的control键并单击。您…

flashback database 闪回数据库

1.修改闪回区大小&#xff0c;路径&#xff0c;保留时间 SQL> show parameter db_recovery_file_dest SQL> show parameter db_flashback_retention_targetSQL> alter system set db_recovery_file_dest_size20G scopeboth;System altered.SQL> alter system set …

ffmpeg视频滤镜: 裁剪-crop

滤镜简述 crop官网链接 > FFmpeg Filters Documentation crop滤镜可以对视频进行裁剪&#xff0c;并且这个滤镜可以接受一些变量比如时间和帧数&#xff0c;这样我们实现动态裁剪&#xff0c;从而实现一些特效。 滤镜使用 参数 out_w <string> ..…

云电脑使用教程标准版

云电脑&#xff0c;也称为云桌面&#xff0c;是一种通过互联网连接远程服务器&#xff0c;使用虚拟桌面环境来执行计算任务的技术。川翔云电脑通过创建软件镜像&#xff0c;让用户能够快速启动并使用预配置的软件和资料&#xff0c;提供高效且经济的云服务。相较于公有云服务&a…

83.【C语言】数据结构之顺序表的尾部插入和删除

目录 3.操作顺序表 2."伪"插入顺序表的元素 分析尾部插入函数SLPushBack 代码示例 SeqList.h main.c free(指针)出错的几种可能的原因 3."伪"删除顺序表元素 2.分析尾部删除函数SLPopBack 代码示例 错误检查 两种解决办法 1.判断size是否为负…

【Linux系统】页表的存在位 与 OS的按需加载策略

一、引入 加载程序会将程序代码全部从磁盘中加载进内存吗&#xff1f; 为什么你的电脑的运存只有16GB&#xff0c;但你可以运行上百GB的游戏&#xff0c;如黑神话马喽&#xff1f; 这就涉及到 操作系统的按需加载策略 二、页表的存在位 页表的一个标志位&#xff1a;存在位 …

webpack 老项目升级记录:从 node-sass 限制的的 node v8 提升至支持 ^node v22

老项目简介 技术框架 vue 2.5.17webpack 4.16.5"webpack-cli": "3.1.0""node-sass": "^4.7.2" 几个阶段 第一步&#xff1a;vue2 升级到最新 第一步&#xff1a;升级 vue2 至最新版本&#xff0c;截止到目前&#xff08;2024-10-…

【vim】手动安装 Leader-F

LeaderF 是一个功能强大的 Vim 插件&#xff0c;主要用于快速导航和搜索。它可以帮助用户在 Vim 中高效地查找文件、缓冲区、标签、函数等各种元素&#xff0c;极大地提高了编辑效率。 LeaderF 的安装如果按照仓库中的教程来的话可以很方便的实现安装&#xff0c;这里介绍一下…

面试官:常见的网络攻击手段有哪些?解决方案了解吗?零基础入门到精通,收藏这一篇就够了

引言&#xff1a;由于互联网和信息技术的高速发展&#xff0c;网络安全变得尤为重要&#xff0c;如果不熟悉常见的网络攻击手段&#xff0c;就会造成数据泄漏、信息安全问题、乃至国家安全问题&#xff0c;本文就来介绍下常见的网络攻击手段和一些防范措施。 题目 面试官&…

深入理解值类型和引用类型的存储

目录 内存 存储 1&#xff09;栈区 2&#xff09;堆区 C#的编译过程 1&#xff09;源代码 2&#xff09;公共语言规范(Common Language Specification&#xff0c;CLS) 编译 3&#xff09;通用中间语言(Microsoft Intermediate Language&#xff0c;CIL或MSIL) 4&…

从陌生到信赖,3款AI写作助手教会了我何为真诚的表达

现在信息多得不得了&#xff0c;写作已经不是只有人才能干的事了。人工智能技术发展得特别快&#xff0c;AI写作助手也开始帮我们写东西了。一开始&#xff0c;我对这些AI助手挺好奇的&#xff0c;后来用着用着就越来越信任它们&#xff0c;甚至有点离不开了。我用的那三款AI写…

ONLYOFFICE 文档8.2版本已发布:PDF 协作编辑、改进界面、性能优化等更新

ONLYOFFICE 在线编辑器最新版本已经发布&#xff0c;其中包含30多个新功能和500多个错误修复。阅读本文了解所有更新。 关于 ONLYOFFICE 文档 ONLYOFFICE 是一个开源项目&#xff0c;专注于高级和安全的文档处理。坐拥全球超过 1500 万用户&#xff0c;ONLYOFFICE 是在线办公领…

Python日志记录库——loguru

知识星球&#xff1a;知识星球 | 深度连接铁杆粉丝&#xff0c;运营高品质社群&#xff0c;知识变现的工具知识星球是创作者连接铁杆粉丝&#xff0c;实现知识变现的工具。任何从事创作或艺术的人&#xff0c;例如艺术家、工匠、教师、学术研究、科普等&#xff0c;只要能获得一…

数学建模与优化算法:从基础理论到实际应用

数学建模和优化算法&#xff0c;它们不仅帮助我们理解和描述复杂系统的行为&#xff0c;还能找到系统性能最优化的解决方案。本文将从基础的数学理论出发&#xff0c;逐步深入到各种优化算法&#xff0c;并探讨它们在实际问题中的应用。 思维导图文件可获取&#xff1a;https:…

如何指定 Maven 的 JDK 版本?

maven 路径&#xff1a;/data/maven/ jdk 路径&#xff1a;/data/jdk_1.8 1、修改 mvn 可执行文件并指定 JDK 版本 vim /data/maven/bin/mvn # 在开头新增即可... # zhurs add JAVA_HOME PATH JAVA_HOME/data/jdk_1.8 ...保存退出即可&#xff01; 为什么在此处新增&#x…

C/C++(六)多态

本文将介绍C的另一个基于继承的重要且复杂的机制&#xff0c;多态。 一、多态的概念 多态&#xff0c;就是多种形态&#xff0c;通俗来说就是不同的对象去完成某个行为&#xff0c;会产生不同的状态。 多态严格意义上分为静态多态与动态多态&#xff0c;我们平常说的多态一般…

【博客节选】Unity角色异常抖动问题排查

本文截取自本人文章 &#xff1a;【Unity实战笔记】第二一 基于状态模式的角色控制——以UnityChan为例 发现出现角色抖动问题 尝试解决方法&#xff1a; 跳跃的loop time不要勾选&#xff1b; 相机aim添加垂直阻尼 还是不行&#xff0c;仔细查看是位移时震颤。 UnityCha…

两个mp3音频怎么合成一个?音频合成的多个好用方法教程

两个mp3音频怎么合成一个&#xff1f;在数字音频时代&#xff0c;随着各类音频内容的日益丰富&#xff0c;合并音频文件的需求也愈发突出。无论是为了制作连贯的音乐集&#xff0c;还是为了解决某些场合下音频播放的便利性&#xff0c;将两个或多个MP3音频合并在一起&#xff0…

【C++面试刷题】快排(quick_sort)和堆排(priority_queue)的细节问题

一、快排的快速选择算法两种思路&#xff08;面试会考&#xff09;O(N) 快排的三数取中思路&#xff1a; 重要的是将它三个数进行排序最左为最小&#xff0c;中间为次小&#xff0c;最右为最大的数。&#xff08;错误原因&#xff1a;我刚开始没有将这三个数进行排序&#xff…

Notepad++通过自定义语言实现日志按照不同级别高亮

借助Notepad的自定义语言可以实现日志的按照不同级别的高亮&#xff1b; 参考&#xff1a; https://blog.csdn.net/commshare/article/details/131208656 在此基础上做了一点修改效果如下&#xff1a; xml文件&#xff1a; <NotepadPlus><UserLang name"Ansibl…