1

动态规划练习题汇总

问题描述:
最优二叉查找树:
给定n个互异的关键字组成的序列K=<k1,k2,...,kn>,且关键字有序(k1<k2<...<kn),我们想从这些关键字中构造一棵二叉查找树。对每个关键字ki,一次搜索搜索到的概率为pi。可能有一些搜索的值不在K内,因此还有n+1个“虚拟键”d0,d1,...,dn,他们代表不在K内的值。具体:d0代表所有小于k1的值,dn代表所有大于kn的值。而对于i = 1,2,...,n-1,虚拟键di代表所有位于ki和ki+1之间的值。对于每个虚拟键,一次搜索对应于di的概率为qi。要使得查找一个节点的期望代价(代价可以定义为:比如从根节点到目标节点的路径上节点数目)最小,就需要建立一棵最优二叉查找树。
举例说明:
n=5的关键字集合以及如下的搜索概率,构造二叉搜索树。
clipboard.png

clipboard.png

期望搜索代价的计算公式:
clipboard.png
下图中图a显示了给定上面的概率分布pi、qi,生成的两个二叉查找树的例子。图b就是在这种情况下一棵最优二叉查找树。

clipboard.png

输入:
p序列:表示对每个关键字ki,一次搜索搜索到的概率为pi;
q序列:表示对每个虚拟键di,一次搜索搜索到的概率为qi。

输出:
关键字和虚拟键的中序遍历结果,如 q0,p1,q1,p2,q2(q1表示第1个虚拟键,p1表示第一个关键字)

1 思路
对于一个序列,q0,p1,q1...pn,qn,以一个关键字作为根节点,左边的序列构成左子树,右边的序列构成右子树,则该树的总代价=根节点的代价+左子树的代价+右子树的代价,最细粒度的子树为叶节点qk(0<=k<=n),该序列的根节点可以选择任意一个关键字,从以关键字为根节点的树中选出代价最小的树即成为该序列的最优树。

2 拆分子问题
在序列q0,p1,q1...pn,qn中,求得子序列i,j的最小代价

3 计算
C[i,j]=min{ C[i,r]+Cr+C[r,j], 其中i<=r<=j}

4 代码

const pArr = [0.15, 0.1, 0.05, 0.1, 0.2];
const qArr = [0.05, 0.1, 0.05, 0.05, 0.05, 0.1];
class CalTree {
  constructor(pArr, qArr) {
    this.arr = this.combineArray(pArr, qArr);
    this.middleWalk = [];
  }
  combineArray(pArr, qArr) {
    return [].concat(pArr, qArr).map((item, index) => {
      const ind = parseInt(index / 2);
      const inc = ind + 1;
      return index % 2 === 0 ? { value: qArr[ind], name: "qArr" + ind } :
        { value: pArr[ind], name: "pArr" + inc};
    });
  }
  getTreeRecursive() {
    const result = this.getArr(this.arr, 1);
    this.walkTree(result.child);
    console.log("最优二叉树的中序遍历结果是:",this.middleWalk.join(","));
  }
  walkTree(tree) {
    this.middleWalk.push(tree.root);
    if (typeof tree.left === "string") {
      this.middleWalk.push(tree.left);
    } else {
      this.walkTree(tree.left);
    }
    if (typeof tree.right === "string") {
      this.middleWalk.push(tree.right);
    } else {
      this.walkTree(tree.right);
    }
  }
  getArr(arr, depth) {
    let mini = { value: 999999999999, child: null };
    for (let i = 1; i < arr.length;){
      const leftArr = arr.slice(0, i);
      const rightArr = arr.slice(i+1);
      let leftCost, leftTree;
      if (leftArr.length === 1) {
        leftCost = leftArr[0].value * (depth + 1);
        leftTree = leftArr[0].name;
      } else {
        const obj = this.getArr(leftArr, depth + 1);
        leftCost = obj.value;
        leftTree = obj.child;
      }
      let rightCost, rightTree;
      if (rightArr.length === 1) {
        rightCost = rightArr[0].value * (depth + 1);
        rightTree = rightArr[0].name;
      } else {
        const obj = this.getArr(rightArr, depth + 1);
        rightCost = obj.value;
        rightTree = obj.child;
      }
      const treeCost = arr[i].value * depth + leftCost + rightCost;
      if (treeCost < mini.value) {
        mini = {
          value: treeCost,
          child: {
            root: arr[i].name,
            left: leftTree,
            right: rightTree
          }
        };
      }
      i += 2;
    }
    return mini;
  }

}
new CalTree(pArr, qArr).getTreeRecursive();

5 时间复杂度
对序列q0,p1,q1...pn,qn来说,关键字的选择有n种,每种树都需要计算最小的C[i,j],其中1<=i,j<=n,故计算C[i,j]的时间复杂度为O(n2),所以总时间复杂度为O(n3)


小利子
118 声望6 粉丝