最优二叉搜索树的java实现

问题来源:
我们在实现英语到汉语时,我们需要查询英语文本中的每一个单词所对用的汉语。我们此时可以构建一个二叉搜索树,将n个英语单词作为关键字,对应的汉语意思作为关联数据。也就时我们所理解key–value对。既然出现了二叉树,那么搜索二叉树的速度就成了我们需要关注的问题。主要问题就是,如果出现频率较低的英语单词放在根上,出现频率很高的英语单词出现在叶子节点,这样会使得我们每次的搜索都会很低效。

问题模型:
给定的n个不同关键字的已排序的序列K=k1,k2,…,kn,满足k1 < k2 < … < kn。同时,对于每个关键字对应的的搜索概率是Pi。
还有很多搜索的值不在K中,因此还由n+1个伪关键字,分别是d0,d1,d2,…,dn。d0 < k1, dn > kn。
注意:
1.每一个关键字ki是一个内部节点,而每一个伪关键字di是一个叶结点。
2.每次搜索要么成功搜索到关键字,要么失败搜索到伪关键字。
3.最优二叉搜索树不一定是高度最矮的。
4.概率最高的关键字也不一定在二叉搜索树的根节点。

递归算法实现:
求解包含关键字ki,…,kj的最优二叉搜索树,其中i>=1,j<=n且j>=i-1。这句话非常的重要:当j=i-1的时候,子树不包含实际关键字,只包含伪关键字。

定义e[i,j]是包含关键字ki,…,kj的最优二叉搜索树的期望代价。那么,我们最终的问题就是计算求出e[1,n]。

此刻,我们假设r结点就是从i到j的根节点。那么必有如下公式:

e[i,j] = e[i,r-1] + e[r+1,j] + w(i,j)

w(i,j)表示包含关键字ki到kj的子树,所有概率之和。

由于根节点r是未知的,所以我们得到最后的公式:

// j = i-1
e[i,j] = q[i-1]
// i <= j
e[i,j] = min{e[i,r-1] + e[r+1,j] + w(i,j)}//r从i取到j

作如下定义,为具体算法代码左准备:
e[n+2,n+1]:期望代价
root[n+2,n+1]:根节点
w[n+2,n+1]:保存临时结果

java代码如下:

/**
     * @param p
     *            :关键字搜索的概率
     * @param q
     *            :伪关键字搜索的概率
     * @param n
     *            :关键字的数量
     * @return 返回最优二叉搜索树的代价和最优根结点
     */
    public static HashMap<Integer, double[][]> optional_bst(double p[], double q[], int n) {
        // 存放回返的两个数组的集合
        HashMap<Integer, double[][]> map = new HashMap<Integer, double[][]>();
        // 用这个二维数组保存e[i][j]最小代价
        double e[][] = new double[n + 2][n + 1];
        // 用这个而为数组保存w[i][j],以避免每次都重新计算
        double w[][] = new double[n + 2][n + 1];
        // root二维数组记录子树的根
        double root[][] = new double[n + 2][n + 1];

        // 初始化e和w数组
        for (int i = 1; i < n + 2; i++) {
            e[i][i - 1] = q[i - 1];
            w[i][i - 1] = q[i - 1];
        }

        for (int l = 1; l < n + 1; l++) {
            for (int i = 1; i < n - l + 2; i++) {
                int j = i + l - 1;
                e[i][j] = Integer.MAX_VALUE;
                w[i][j] = w[i][j - 1] + p[j - 1] + q[j];
                for (int r = i; r < j + 1; r++) {
                    double t = e[i][r - 1] + e[r + 1][j] + w[i][j];
                    if (t < e[i][j]) {
                        e[i][j] = t;
                        root[i][j] = (double) r;
                    }
                }
            }
        }

        map.put(1, e);
        map.put(2, root);
        return map;
    }

测试代码:

//规模为5的二叉树
        double p[] = { 0.15, 0.10, 0.05, 0.10, 0.20 };
        double q[] = { 0.05, 0.10, 0.05, 0.05, 0.05, 0.10 };

        HashMap<Integer, double[][]> map = optional_bst(p, q, 5);

        double e[][] = map.get(1);

        for (int i = 0; i < e.length; i++) {
            for (int j = 0; j < e[i].length; j++) {
                if (e[i][j] > 0) {
                    // 按四舍五入保留两位小数
                    BigDecimal b = new BigDecimal(e[i][j]);
                    System.out.println(
                            "e[" + i + "]" + "[" + j + "]=" + b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
                }
            }
        }

到此为止,最佳二叉搜索树的代价以及分割点已经全部得到,现在我们需要打印输出二叉搜索的结构,java代码如下:

/**
     * 打印最优二叉树的结构
     * 
     * @param root
     *            : 最优根结点
     * @param i
     *            :子树起点
     * @param j
     *            :子树终点
     */
    public static void print_construct_optimal_bst(double root[][], int i, int j, int r) {

        int rootChild = (int) root[i][j];

        if (rootChild == root[1][5]) {
            System.out.println("K" + rootChild + "为根");
            print_construct_optimal_bst(root, i, rootChild - 1, rootChild);// 左子树
            print_construct_optimal_bst(root, rootChild + 1, j, rootChild);// 右子树
            return;// 根只有一个,所以需要一个return
        }

        if (j < i - 1) {
            return;
        } else if (j == i - 1) {// 这里是伪关键字节点,必为叶子结点

            if (j < r) {
                System.out.println("d" + j + "为" + "K" + r + "的左孩子");
            } else {
                System.out.println("d" + j + "为" + "K" + r + "的右孩子");
            }

            return;

        } else {// 这里关键字节点,必为非叶子结点
            if (rootChild < r) {
                System.out.println("K" + rootChild + "为" + "K" + r + "的左孩子");
            } else {
                System.out.println("K" + rootChild + "为" + "K" + r + "的右孩子");
            }

        }

        print_construct_optimal_bst(root, i, rootChild - 1, rootChild);
        print_construct_optimal_bst(root, rootChild + 1, j, rootChild);

    }

对于这个算法,需要明白二叉树的结构特点。

基本完成,算法导论上面的算法具体实现。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值