动态规划练习题汇总segmentfault
问题描述:
最优二叉查找树:
给定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的关键字集合以及以下的搜索几率,构造二叉搜索树。this
指望搜索代价的计算公式:
下图中图a显示了给定上面的几率分布pi、qi,生成的两个二叉查找树的例子。图b就是在这种状况下一棵最优二叉查找树。spa
输入:
p序列:表示对每一个关键字ki,一次搜索搜索到的几率为pi;
q序列:表示对每一个虚拟键di,一次搜索搜索到的几率为qi。code
输出:
关键字和虚拟键的中序遍历结果,如 q0,p1,q1,p2,q2(q1表示第1个虚拟键,p1表示第一个关键字)blog
1 思路
对于一个序列,q0,p1,q1...pn,qn,以一个关键字做为根节点,左边的序列构成左子树,右边的序列构成右子树,则该树的总代价=根节点的代价+左子树的代价+右子树的代价,最细粒度的子树为叶节点qk(0<=k<=n),该序列的根节点能够选择任意一个关键字,从以关键字为根节点的树中选出代价最小的树即成为该序列的最优树。ip
2 拆分子问题
在序列q0,p1,q1...pn,qn中,求得子序列i,j的最小代价get
3 计算
C[i,j]=min{ C[i,r]+Cr+C[r,j], 其中i<=r<=j}string
4 代码it
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)console