最优二叉搜索树



这是一个经典的动态规划问题(但厉害的是其中带有一个很神奇的定理),问题是这样的:已知二叉搜索树中每个节点的访问概率,问这棵树整体的搜索时间最短是多少(此时称为最优二叉搜索树)。

众所周知,在二叉搜树中,一次搜索的时间等于待访问节点的深度。所以整体的搜索时间为:

节点i的访问概率 * 节点i的深度

所以如果要整体搜索时间最短,则访问概率高的节点应该比较靠近根节点。乍一听,好像是哈夫曼编码。但是不同的是,这是二叉搜索树,所有节点的左右顺序(这里指中序遍历的顺序)不能变化。所以无法像哈夫曼编码那样一味地把概率高的节点往上移(那是一个贪心算法)。

那该怎么办呢?其实我们只要想到这样一个递推关系:一棵树如果是最优二叉搜索树,那么要么它是空树,要么它的左、右子树也是最优二叉搜索树。这样就得到了动态规划的解法:

For size = 1到n
    For 所有包含size个元素的子树
        For 该子树的所有节点i
            找出其中一个i,使当它为根节点时,左、右子树的最短搜索时间之和最小。那么该子树的访问时间就是:
            左、右子树的最短搜索时间之和 + 所有节点的访问概率之和(因为所有节点都下降了一层)。


可见,这个算法的时间复杂度是O(n^3)。但是有一个神奇的定理,可以把算法的时间效复杂度降到O(n^2),如下:

设一个子树的节点为i ~ j(当然,这里说的i ~ j都是从小到大排好序的),则当它是最优二叉搜索树时的根节点root(i, j)满足:
root(i, j - 1) <= root(i, j) <= root(i + 1, j)。


这样一来,上面那个算法的第3个For就可以不用循环子树中的所有节点了,只要循环另两个子树的根节点之间的范围就可以了。而这个范围根据实践表明是很小的。所以整体的时间复杂度就相当于两层For循环而已。

===========================================

//最优二叉搜索树的动态规划算法代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

typedef struct matrix
{
int row;
int col;
} matrix;

typedef struct minCost
{
int cost;
int mid;
} minCost;

minCost** func(matrix* mt, ssize_t count)
{
int i, j, step, min, temp, mid;
minCost **rows;
rows = (minCost **)malloc(count*(sizeof(minCost*)));
for(i=0;i<count;i++)
rows[i] = (minCost *)malloc((count-i)*sizeof(minCost));

for(i=0;i<count;i++)
{
rows[i][0].cost=0;
rows[i][0].mid=-1;
}

for(step=1;step<count;step++)
for(j=0;j<count-step;j++)
{
min=mt[j].row*mt[j].col*mt[j+step].col
+rows[j][0].cost+rows[j+1][step-1].cost;
mid=j;
for(i=1;i<step;i++)
{
temp=rows[j][i].cost+rows[j+i+1][step-i-1].cost
+mt[j].row*mt[j+i].col*mt[j+step].col;
if(min>temp)
{
min=temp;
mid=j+i;
}
}
rows[j][step].cost=min;
rows[j][step].mid=mid;
}
printf("%d, %d\n", rows[0][count-1].cost, rows[0][count-1].mid);
return rows;
}

void rel(minCost **mc, ssize_t count)
{
int i;
for(i=0;i<count;i++)
free(mc[i]);
free(mc); 
}

int main(int argc, char *argv[])
{
minCost **temp;
matrix ma[]={{30,35},{35,15},{15,5},{5,10},{10,20},{20,25}};
temp=func(ma, sizeof(ma)/sizeof(ma[0]));
rel(temp, sizeof(ma)/sizeof(ma[0]));
return 0;
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值