《算法导论》动态规划—最优二分搜索树

案例

?假如我们现在在设计一个英文翻译程序,要把英文翻译成汉语,显然我们需要知道每个单词对应的汉语意思。我们可以建立一颗二分搜索树来实现英语到汉语的关联。为了更快速地翻译,我们可以使用AVL树或者红黑树使每次查询的时间复杂度Θ(lgn),实际上对于字典翻译程序来说这么做存在一个问题,比如“the”这个单词经常用,却很有可能存在于十分远离树根的位置,而“machicolation”这种不常用的单词很可能存在于十分靠近树根的位置,这就导致查询频率高的单词需要的查询时间更长,而查询频率很低的单词查询时间却很短。这明显不符合我们的期望,我们希望高频率的单词用更少的时间查询到,低频率的单词则用相对更长的时间查询到,也就是高频单词靠近树根,而低频单词远离树根。这时,我们就需要一颗最优二分搜索树来解决这个问题。

最优二分搜索树

何为最优二分搜索树

给定一个组长度为n关键字的有序序列K=< k1, k2, k3 ···· kn >

以及对应关键字的出现概率P=< p1, p2, p3 ···· pn >

由于我们要检索的关键字可能不在树中,所以我们需要n+1个伪关键字

给定一组长度为n+1的伪关键字D=< d0, d1, d2 ···· dn >

其中d0表示小于任何一个关键字的值,dn表示大于任何一个关键字的值,当 i != 0 && i != n 时,di

表示所有大于ki且小于ki+1的不存在于序列K中的值的集合(不考虑值相等的情况)(比如d5就表示所有大于k5且小于k6的不存在于序列K中的值的集合)

若满足下列条件则该二分搜索树为一颗最优二分搜索树

  • 是一颗二分搜索树(废话)
  • 满足下列条件

注意

  1. 最优二分搜索树不一定是一颗平衡二分搜索树
  2. 最优二分搜索树的根节点不一定是出现频率最高的节点

例子

如何构建一颗最优二分搜索树

穷举法

?如果要一个一个地去穷举出所有的形态再从中挑选时空复杂度很大,一个有n的节点的二叉树的形态有很明显如果穷举的话是非常不划算的。

动态规划

是否可以使用

?既然穷举法不行那么动态规划是否适用呢?使用动态规划需要两个条件

  • 具有优化子结构
  • 具有重叠子问题

?这里可以直接给出最优二分搜索树问题的优化子结构:对于一颗最优二分搜索树T,其任意一颗子树T1一定是一颗最优二分搜索树。这里可以用反证法证明,T是一颗最优二分搜索树,如果存在一个子树T1不是最优二分搜索树,那么将此子树替换为包含关键字相同但是形态不相同的另外一颗子树T2,就很有可能使的整棵树T的期望搜索代价更低,这就不满足T是一颗最优二分搜索树的前提。

?根据上述的分析,可以知道最优二分搜索树问题是存在重叠子问题的,因为如果我们要构造一颗最优二分搜索树,必须优先构造其子树,构造其子树的时候要构造其子树的子树,如此下去,就存在了重叠的子问题。

?因此最优二分搜索树问题是可以使用动态规划求解的。

问题分析

?值得注意的是,如果i=j-1,这颗子树实际上不包含任何实际的关键字,但是会包含为关键字di-1(所有小于ki的不存在于序列K中的数字的集合)。这个问题我们并没有明显的规律可循,所以我们只能寻找一个关键字kr(i <= r <= j),当kr作为该子树的树根时期望搜索代价最低,只能一个一个地试。

  • 我们以COST[i][j]表示包含关键字 ki ···· kj 的子树的搜索代价
  • 我们以W[i][j]表示包含关键字 ki ···· kj 以及伪关键字 di-1 ···· dj的子树的每个关键字和伪关键字的概率和

递归式

子问题求解顺序(矩阵填充方式)

?子问题矩阵如图

?应该按照对角线方式进行填充,因为每次求解一个对角线上的子问题,都需要前一个对角线的结果

本文涉及两个矩阵,两个矩阵的填充方式相同

C++代码

#include <iostream>
#include <string>
#include <algorithm>
#include <map>

using namespace std;
const double INF = 99999;

void OPTIMAL_BST(double* p, double* q, int n);

int main()
{
    int n;
    cin >> n;
    double* p = new double[n + 1];
    double* q = new double[n];
    for (int i = 1; i <= n; i++)
        cin >> p[i];
    for (int i = 0; i <= n; i++)
        cin >> q[i];
    OPTIMAL_BST(p, q, n);
    delete p, q;
    system("pause");
}

void OPTIMAL_BST(double* p, double* q, int n)
{
    double** e = new double*[n + 2];
    double** w = new double*[n + 2];
    double t = 0.0;
    //int** root = new int*[n + 2];
    int j = 0;
    for (int i = 0; i <= n + 1; i++)
    {
        e[i] = new double[n + 1];
        w[i] = new double[n + 1];
        //root[i] = new int[n + 1];
        //矩阵置零
        memset(e[i], 0, sizeof(double)*(n + 1));
        memset(w[i], 0, sizeof(double)*(n + 1));
        //memset(root[i], 0, sizeof(int)*(n + 1));
    }

    //填充第一个对角线
    for (int i = 1; i <= n + 1; i++)
    {
        e[i][i - 1] = q[i - 1];
        w[i][i - 1] = q[i - 1];
    }
    for (int k = 1; k <= n; k++)
    {
        for (int i = 1; i <= n - k + 1; i++)
        {
            j = i + k - 1;
            e[i][j] = INF;
            w[i][j] = w[i][j - 1] + p[j] + q[j];

            for (int r = i; r <= j; r++)
            {
                t = e[i][r - 1] + e[r + 1][j] + w[i][j];
                if (e[i][j] - t > 0.0001)
                {
                    e[i][j] = t;
                    //root[i][j] = r;
                }
            }
        }
    }
    cout << endl << endl;
    for (int i = 1; i <= n + 1; i++)
    {
        for (int j = 0; j <= n; j++)
            cout << e[i][j] << ‘\t‘;
        cout << endl << endl << endl;
    }
    delete e, w;
    //delete root;
}

原文地址:https://www.cnblogs.com/FDProcess/p/9343275.html

时间: 2024-10-08 12:05:21

《算法导论》动态规划—最优二分搜索树的相关文章

算法导论--动态规划(矩阵链乘法)

矩阵链乘法问题 给定一个n个矩阵的序列?A1,A2,A3...An?,我们要计算他们的乘积:A1A2A3...An.因为矩阵乘法满足结合律,加括号不会影响结果.可是不同的加括号方法.算法复杂度有非常大的区别: 考虑矩阵链:?A1,A2,A3?.三个矩阵规模分别为10×100.100×5.5×50 假设按((A1A2)A3)方式,须要做10?100?5=5000次,再与A3相乘,又须要10?5?50=2500,共须要7500次运算: 假设按(A1(A2A3))方式计算.共须要100?5?50+10

算法导论--动态规划(最长公共子序列)

最长公共子序列问题(LCS) 给定两个序列X=?x1,x2,x3...xm?和Y=?y1,y2,y3...xn?,求X和Y的最长公共子序列. 例如:X=?A,B,C,B,D,A,B?,和Y=?B,D,C,A,B,A?,的最长公共子序列为?B,C,B,A?,长度为4: 对于此问题,可以采用暴力求解的方式来比对,即穷举出X的所有子序列,用每个子序列与y做一 一比较.假如X序列共有m个元素,对每个元素可以决定选或不选,则X的子序列个数共有2m个,可见与长度m呈指数阶,这种方法效率会很低. 动态规划 前

算法导论--动态规划(装配线调度)

装配线问题: 某个工厂生产一种产品,有两种装配线选择,每条装配线都有n个装配站.可以单独用,装配线1或2加工生产,也可以使用装配线i的第j个装配站后,进入另一个装配线的第j+1个装配站继续生产.现想找出通过工厂装配线的最快方法. 装配线i的第j个装配站表示为Si,j,在该站的装配时间是ai,j 如果从 Si,j装配站生产后,转移到另一个生产线继续生产所耗费的时间为ti,j 进入装配线花费时间ei,完成生产后离开装配线所耗费时间为xi 令f*表示通过生产所有路线中的最快的时间 令fi[j]表示从入

算法导论--动态规划(钢条切割)

钢条切割问题 现有一段长度为n英寸的钢条和一个价格表pi,求切割方案使销售利益最大rn最大 长度为n英寸的钢条共有2n?1种不同的切割方案,因为可以每个整英寸的位置都可以决定切割或者不切割. 为了得到rn最大,可以把这个问题分成子问题求解,先切一刀,再考虑余下的部分的最大收益即求 rn=max{pk+rn?k}(k=1,2,3-n-1), pk部分不进行继续切割,直接作为一个整体售出 ; rn?k部分继续切割,考虑所有的情况,分成子问题. 求出所有k值对应的收益最大者作为rn 也有可能不进行任何

算法导论—动态规划

华电北风吹 天津大学认知计算与应用重点实验室 日期:2015/8/27 首先区分动态规划和分治策略. 这两者有很相似的地方,都是通过组合子问题的解来求解原问题.不同的是,分治策略将原问题划分为互不相交的子问题,递归的求解子问题,再将它们的解组合起来,求出原问题的解.与之相反,动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题(子问题的求解是递归进行的,将其划分为更小的子问题).在这种情况下,分治法会做许多不必要的工作,他会反复求解那些公共的子子问题.而动态规划算法对每个子子问题只求

算法导论——动态规划

动态规划指的是一个问题可以拆分成多个小的最优子问题,并且这些子问题具有重叠,典型的如斐波那契数列:f(2)=f(1)+f(0),f(3)=f(2)+f(1),f(4)=f(3)+f(2),若使用简单的递归算法求f(4),则f(2)会被计算两次,当计算f(n)时需要计算f(n-1)和f(n-2)而f(n-1)又要计算记一次f(n-2),如此往复时间复杂度为n的指数级别,导致算法效率低下.若能够记录f(2)至f(n-1)的结果,可以保证每一级计算都是O(1)复杂度,整个算法的时间复杂度就能下降至O(

算法导论动态规划 15.1-3 钢条切割问题

算法导论的第一个动态规划问题--钢条切割 我们知道钢条的长度和价格为: 长度i 1 2 3 4 5 6 7 8 9 10 价格pi 1 5 8 9 10 17 17 20 24 30 书上的两种方法已经很清楚,这里主要说一下课后练习里面15-3钢条成本加上切割成本,即要切割的次数最少.15-4返回切割方案 #include<fstream> #include<iostream> using namespace std; int main() { int const N = 11;

算法导论------------------动态规划之矩阵链问题

[问题描述] 给定有n个连乘矩阵的维数,要求计算其采用最优计算次序时所用的乘法次数,即所要求计算的乘法次数最少.例如,给定三个连乘矩阵{A1,A2,A3}的维数分别是10*100,100*5和5*50,采用(A1A2)A3,乘法次数为10*100*5+10*5*50=7500次,而采用A1(A2A3),乘法次数为100*5*50+10*100*50=75000次乘法,显然,最好的次序是(A1A2)A3,乘法次数为7500次. 分析: 矩阵链乘法问题描述: 给定由n个矩阵构成的序列[A1,A2,.

算法导论-动态规划-钢条切割

动态规划通常用于解决最优化问题,在这类问题中,通过做出一组选择来达到最优解.在做出每个选择的同时,通常会生成与原问题形式相同的子问题.当多于一个选择子集都生成相同的子问题时,动态规划技术通常就会很有效,其关键技术就是对每个这样的子问题都保存其解,当其重复出现时即可避免重复求解. 钢条切割问题 Serling公司购买长钢条,将其切割为短钢条出售.切割工序本身没有成本支出.公司管理层希望知道最佳的切割方案.假定我们知道Serling公司出售一段长为i英寸的钢条的价格为pi(i=1,2,…,单位为美元