二叉查找树算法

Python012

二叉查找树算法,第1张

#include <iostream>using namespace stdtypedef struct _node{ int data struct _node *pLeftPtr struct _node *pRightPtr}BTreeNodeclass BinarySearchTree{public: BinarySearchTree() ~BinarySearchTree() bool Create(int* pIntArray,int arrLength) bool Insert(int data) // 查找节点,searchLength为搜索长度,返回值为true表示指定的数据存在,否则不存在 bool Find(int data, int* searchLength) // 中序输出二叉树 void MidOutput() // 释放内存 void Destroy() void Delete(int data)protected: bool Insert(BTreeNode* pRoot, int data) bool Find(BTreeNode* pRoot,int data, int *searchLength) void Delete(BTreeNode* &pRoot, int data) void MidOutput(BTreeNode* pRoot) void Destroy(BTreeNode* pRoot)private: BTreeNode* m_pRoot //二叉搜索树根节点}BinarySearchTree::BinarySearchTree(){ m_pRoot = NULL}BinarySearchTree::~BinarySearchTree(){ Destroy()}bool BinarySearchTree::Create(int* pIntArray,int arrLength){ for(int i=0i<arrLengthi++) { if(!Insert(*(pIntArray+i))) return false } return true}bool BinarySearchTree::Insert(BTreeNode* pRoot, int data){ BTreeNode* pNewNode = new BTreeNode if(pNewNode == NULL) return false pNewNode->data = data pNewNode->pLeftPtr = NULL pNewNode->pRightPtr = NULL BTreeNode* pCurrentNode = pRoot BTreeNode* pParentNode = pCurrentNode// 保存父节点的指针 int flag = 0// 标记插入节点的位置 if(pCurrentNode == NULL) { m_pRoot = pNewNode }else{ while(pCurrentNode) { if(data <pCurrentNode->data) { pParentNode = pCurrentNode pCurrentNode = pCurrentNode->pLeftPtr flag = 0 }else if(data >pCurrentNode->data){ pParentNode = pCurrentNode pCurrentNode = pCurrentNode->pRightPtr flag = 1 } } if(flag == 0) { pParentNode->pLeftPtr = pNewNode }else{ pParentNode->pRightPtr = pNewNode } } return true }bool BinarySearchTree::Insert(int data){ return Insert(m_pRoot,data)}bool BinarySearchTree::Find(BTreeNode* pRoot,int data, int *searchLength){ if(pRoot == NULL) { *searchLength = 0 return false } BTreeNode* pCurrentNode = pRoot static int length = 0 while(pCurrentNode != NULL) { if(data == pCurrentNode->data) { *searchLength = length cout<<"数字"<<data<<"存在二叉树中,查找长度为: "<<endl<<length<<endl return true }else if(data <pCurrentNode->data) { length ++ pCurrentNode = pCurrentNode->pLeftPtr }else{ length ++ pCurrentNode = pCurrentNode->pRightPtr } } *searchLength = length cout<<"数字"<<data<<"不在二叉树中,查找长度为: "<<endl<<length<<endl length = 0// 每次查找结束,重新赋值为0 return false}bool BinarySearchTree::Find(int data, int* searchLength){ return Find(m_pRoot,data,searchLength)}void BinarySearchTree::Destroy(BTreeNode* pRoot){ BTreeNode* pCurrentNode = pRoot if(pCurrentNode == NULL) return Destroy(pCurrentNode->pLeftPtr) Destroy(pCurrentNode->pRightPtr) delete pCurrentNode m_pRoot = NULL}void BinarySearchTree::Destroy(){ Destroy(m_pRoot)}void BinarySearchTree::MidOutput(BTreeNode* pRoot){ if(pRoot) { MidOutput(pRoot->pLeftPtr) cout<<pRoot->data <<" " MidOutput(pRoot->pRightPtr) }}void BinarySearchTree::MidOutput(){ MidOutput(m_pRoot)}void BinarySearchTree::Delete(int data){ Delete(m_pRoot,data)}void BinarySearchTree::Delete(BTreeNode* &pRoot, int data){ if(!pRoot) return else if(data == pRoot->data){ if(pRoot->pLeftPtr == NULL &&pRoot->pRightPtr == NULL) // 叶子节点 { delete pRoot pRoot = NULL }else if(pRoot->pLeftPtr != NULL &&pRoot->pRightPtr == NULL){ // 只有左子树 BTreeNode* pNode = pRoot->pLeftPtr delete pRoot pRoot = pNode }else if(pRoot->pLeftPtr == NULL &&pRoot->pRightPtr != NULL){ // 只有右子树 BTreeNode* pNode = pRoot->pRightPtr delete pRoot pRoot = pNode }else{ // 有左右子树 // 找到左子树的最大节点 BTreeNode* pCurrentNode = pRoot->pLeftPtr BTreeNode* pParentNode = NULL while(pCurrentNode->pRightPtr != NULL) { pParentNode = pCurrentNode pCurrentNode = pCurrentNode->pRightPtr } pRoot->data = pCurrentNode->data if(pParentNode) { pParentNode->pRightPtr = pCurrentNode->pLeftPtr }else{ pRoot->pLeftPtr= pCurrentNode->pLeftPtr } delete pCurrentNode } }else if(data <pRoot->data) Delete(pRoot->pLeftPtr, data) else Delete(pRoot->pRightPtr, data)}void main(){ int data[8] cout<<"请输入整形数据, 数据用空格隔开, 回车键结束输入"<<endl for(int i=0i<8i++) cin>>data[i] // int data[8] = {13,15,6,20,14,5,7,18} BinarySearchTree tree tree.Create(data,sizeof(data)/sizeof(data[0])) cout<<"插入数据后的二叉树为: "<<endl tree.MidOutput() cout<<endl int len_1=0 int len_2=0 tree.Find(14,&len_1) tree.Find(21,&len_2) tree.Delete(20) cout<<"删除数字20后的二叉树结果: "<<endl tree.MidOutput() cout<<endl tree.Delete(15) cout<<"删除数字15后的二叉树结果: "<<endl tree.MidOutput() cout<<endl}

二叉查找树(BST,Binary Search Tree) ,又名二叉搜索树或二叉检索树,是一颗满足如下条件的树:

1、每个节点包含一个键值

2、每个节点有最多两个孩子

3、对于任意两个节点x和y,它们满足下述搜索性质:

a、如果y在x的左子树里,则key[y] <= key[x]

b、如果y在x的右子树里,则key[y] >= key[x]

最优二叉查找树(Optimal BST,Optimal Binary Search Tree)

最优二叉查找树是使查找各节点平均代价最低的二叉查找树。具体来说就是:给定键值序列 K = <k1 , k2 , . . . , kn >,k1 <k2 <· · · <kn ,其中键值ki ,被查找的概率为pi ,要求以这些键值构建一颗二叉查找树T,使得查找的期望代价最低(查找代价为检查的节点数)。

下面是对于查找期望代价的解释:

对于键值ki , 如果其在构造的二叉查找树里的深度(离开树根的分支数)为depthT(ki ),则搜索该键值的代价= depthT(ki ) +1(需要加上深度为0的树根节点)。由于每个键值被查找的概率分别为pi ,i=1,2,3…,n。所以查找期望代价为:

E[T的查找代价] = ∑i=1~n (depthT(ki ) +1) · pi

时间复杂度

1、穷举

穷举构造最优二叉查找树,其实就是这样的一个问题:

给一个拥有n个数的已排序的节点,可以将其构造成多少种不同的BST(用来找到一个最优的二叉查找树)?

设可以构造成T(n)个,那么枚举每一个元素作为根节点的情况,当第一个元素作为根节点时,其余n-1个构成右子树,无左子树,是n-1情况时的子问题, 共T(n-1)种;当第二个元素作为根节点时,左子树有1个元素,右子树有n-2个元素,根据乘法原理共有T(1)T(n-2)种情况……依此类推得 到:T(n) = T(0)T(n-1) + T(1)T(n-2) + T(2)T(n-3) + ...... + T(n-2)T(1) + T(n-1)T(0);此外,有T(0)=T(1)=1。

下面来求解T(n):

定义函数 f(x) = T(0) + T(1) · x + T(2) · x2 + ......

那么有:

f(x)2 = (T(0)2 ) + (T(0)T(1) + T(1)T(0)) · x + (T(0)T(2) + T(1)T(1) + T(2)T(0)) · x2 + ......

= T(1) + T(2) · x + T(3) · x2 + ......

= (f(x) - T(0)) / x

= (f(x) - 1) / x

这样解方程得到 f(x) = [1 - (1 - 4x)1/2 ] / 2x

右边进行泰勒展开,再与定义式比较最终得到: T(n) = (2n)! / (n!(n+1)!)

然后根据Stirling公式:n! ~ (2πn)1/2 · (n/e)n

于是有(2n)! / n!(n+1)! ~ (4n1/2 · 2n2n ) / (2n1/2 · nn · (2(n+1))1/2 · (n+1)n )

~ 4n · (n+1)-3/2 · (n/(n+1))n

~ 4n · n-3/2

因此最后得到穷举方法构造最优二叉查找树的时间复杂度: T(n) = O(4n · n-3/2 )

2、递归

实际上左右子树是互不影响的,不需要穷举所有左右子树的组合,所以不需要用乘法原理,加法原理就可以了,这样式子变为:

T(n) = T(0) + T(n-1) + T(1) + T(n-2) + T(2) + T(n-3) + ...... + T(n-2) + T(1) + T(n-1) + T(0)

= 2(T(0) + T(1) + T(2) + ...... + T(n-1))

= 3T(n-1)

所以得到T(n) = O(3n ) ,还是指数级的一个算法

3、动态规划

上面得到指数级算法的原因在于,计算了很多重复的子树情况,一些子树的查找代价被计算了很多遍;而一棵树如果是最优二叉搜索树,那么要么它是空树,要么它 的左、右子树也是最优二叉搜索树,因此只需要将子树的查找代价记录下来,采用记忆化搜索或者是自底向上的动态规划的方法,虽然需要消耗一定的空间,但可以 把时间复杂度从指数级降到多项式级,这些空间消耗也是可以接受的。

以下是采用自底向上的解法:

输入:键值序列 K = <k1 , k2 , . . . , kn >,概率序列 P = <p1 , p2 , . . . , pn >

输出:两个二维数组,Price[i][j]表示ki 到kj 构成的最优子树的查找代价,Root[i][j]表示表示ki 到kj 构成的最优子树的根节点位置(用于重构最优二叉查找树)

算法1 :

For 子树大小size = 1 to n

For 子树的起点start = 1 to (n - size + 1) //这样子树的终点即为 end = start + size - 1,长度为size

For 该子树的所有节点作为根节点root = start to end

对于每个root,根据之前计算过的Price数组得到左右最优子树的代价,可直接得到该子树的代价price为:

左右子树的最优子树代价之和 + 所有节点的访问概率之和(因为所有节点都下降了一层)

在内层循环中找到代价最小的price和root分别记录在Price[start][end]和Root[start][end]中

下面分析这个算法的时间复杂度:

由于除了计算出我们最后需要得到的Price和Root二维数组,还产生了部分冗余的子树,因此不能简单的将算法归结为O(n2 )的算法。

对于子树大小为1时,我们考察了n个子树;

对于子树大小为2时,一共产生了(n - 1)个最优子树,但是在我们的每次考察中,都将子树的所有节点作为根节点考虑过一次,因此每得到1个大小为2的子树,我们需要考察2个不同的子树来找到一 个代价最小的,因此最后我们实际上考察了2(n - 1)个子树;

对于子树大小为3时,类似的,我们考察了3(n - 2)个子树;

……

对于子树大小为n时,我们考察了n个子树。

最后,我们一共考察了T(n) = n + 2(n - 1) + 3(n - 2) + ...... + n个子树。

求解这个公式依然可以借用之前的方法,定义函数 f(x) = 1 + 2x + 3x2 + ...... = (1 - x)-2

这样一来 f(x)2 = T(1) + T(2) · x + T(3) · x2 + ......

再借用泰勒展开得到 T(n) = (n + 2)(n + 1)n/6 = O(n3 )

或者把所有项视为n2,则有 T(n) ≤ n2 + n2 + n2 + n2 + ...... = (n+1)n2 ≤ 2n3

把中间n/2项都视为n/4 · 3n/4的话,则有 T(n) ≥ n/2 · n/4 · 3n/4 = (3/32)n3

根据时间复杂度的定义有 T(n) = O(n3 )

下面介绍一个定理,可以借此把动态规划算法的时间复杂度进一步降到O(n2 ),详细的证明参见参考文献:

定理1 :Root[i][j-1] ≤ Root[i][j] ≤ Root[i+1][j] (Root数组定义见算法1)

也就是说,算法1的第3个For就可以不用循环子树中的所有节点了,只要循环另两个子树的根节点之间的范围就可以了。算法如下,红色的为修改的部分:

算法2 :

For 子树大小size = 1 to n

For 子树的起点start = 1 to (n - size + 1) //这样子树的终点即为 end = start + size - 1,长度为size

For 该子树的所有节点作为根节点root = Root[start][end-1] to Root[start+1][end]

对于每个root,根据之前计算过的Price数组得到左右最优子树的代价,可直接得到该子树的代价price为:

左右子树的最优子树代价之和 + 所有节点的访问概率之和(因为所有节点都下降了一层)

在内层循环中找到代价最小的price和root分别记录在Price[start][end]和Root[start][end]中

在分析该算法的时间复杂度时应注意,考察的子树是与考察的内层循环中root数量一一对应的,而当start递进时,前一个root的终点正好等于后一个root的起点(算法中的红色部分),也就是说对于固定的size来说,考察的root的范围加起来应当首位相接 而且至多刚好覆盖 所有节点,因此对于每个size,最多只考察2n个root(这里缩放了一下),因此总共最多考察了2n · n = 2n2 个子树;另一方面,Root数组中每一个值对应得到的一个最优二叉查找树,也即至少需要考察n2 个子树。因此根据定义得到 T(n) = O(n2 )