動態規劃練習題-最優二分查找樹

動態規劃練習題彙總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的關鍵字集合以及以下的搜索機率,構造二叉搜索樹。
clipboard.pngthis

clipboard.png

指望搜索代價的計算公式:
clipboard.png
下圖中圖a顯示了給定上面的機率分佈pi、qi,生成的兩個二叉查找樹的例子。圖b就是在這種狀況下一棵最優二叉查找樹。spa

clipboard.png

輸入:
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

相關文章
相關標籤/搜索