二叉排序树
二叉排序树与BinarySTree结构
二叉排序树概念
-
在二叉树中任何结点放入左子树中所有的关键码值都小于该节点的关键值码,而右子树中所有关键值码都大于该结点的关键值码,则称此二叉树为二叉排序树,或检索树
二叉排序树的查找
-
在二叉排序树中查找值为x’的结点,只需要从根节点起,沿左或右子树向下搜索。当x小于根节点的值,则沿着左子树下降搜索;当x大于根节点的值,则沿着右子树下降搜索。继续上述搜索过程知道在二叉树排序树中检索到某一空子树,由此可以判断x不在树中
typedef int ElementType; enum bool {false, true}; typedef enum bool Bool; struct binarySTree { BinaryTreeNode* root;//树结构,前面有定义 }; typedef struct binarySTree BinarySTree; Bool search_bst(BinarySTree *root, ElementType e) { if (root == NULL) //检索到空子树,说明e不在二叉排序树中 return false; else if (root->data == e); return true; else if (e < root->data) search_bst(root->LeftChild, e); else if (e > root->data) search_bst(root->RightChild, e); }
二叉排序树的插入
-
将p所指结点插入以root为根结点指针的二叉排序树中
void ins_bst(BinarySTree* root, BinaryTreeNode* p) { if (root == NULL) //根结点为空,p所指结点直接作根结点 *q = p; else if (root->data < p->data) //大于根节点关键值码,置于右子树 ins_bst(root->RightChild, p); else if (root->data > p->data) //小于根节点关键值码,置于左子树 ins_bst(root->LeftChild, p); }
-
同样可以采用非递归的方式进行二叉排序树的构造和插入
二叉排序树的删除
-
在二叉排序树中删除一个结点比插入一个结点要困难(除非删除的是叶结点),因为要考虑到部分链的对接,即要保证删除一个结点后仍然还是二叉排序树。
-
事实上,二叉排序树的**序(从小到大遍历分别是左子树,根节点和右子树)**恰好是二叉排序树的对称遍历序列
-
分以下情况进行讨论:
- 被删除结点只有一个儿子结点,直接让其儿子代替它的位置
- 若被删除结点有两个儿子,为了保持二叉排序树“左小右大”的排序性质,则需要分情况考虑用其左或右儿子结点替换它的位置
-
第一种方式,规定如下
-
若待删除的结点没有左子树,则用右子树的根替换被删除的结点
-
若待删除的结点有左子树,则用左子树的根替换被删除的结点,被删结点的右子树作为被删结点左子树对称序最后一个结点的右子树
/*root为根结点指针,p指向被删结点,f指向被删结点的父结点*/ void dele_bst(BinaryTreeNode* root, BinaryTreeNode* p, BinaryTreeNode* f) { BinaryTreeNode* s; if (p->LeftChild == NULL) //若p无左子树 { if (f == NULL) //p是根结点 root = p->RightChild; else if (f->LeftChild = p) //p是其父结点f的左儿子 f->LeftChild = p->RightChild; //p没有左子树,则直接用p的右子树替代p的位置 else f->RightChild = p->RightChild; //p是其父结点f的右儿子结点 } else if (p->RightChild == NULL) //若p无右子树 { if (f == NULL) //p为根结点 root = p->LeftChild; else if (f->LeftChild == p) //p是其父结点的左子树 f->LeftChild = p->LeftChild; //按规定,若有左子树,直接用其左子树替代p结点位置 else f->RightChild = p->LeftChild; //p是其父结点的右儿子结点 } else //p结点既有左子树又有右子树 { s = p->LeftChild; //s指向p结点的左子树 while (s->RightChild != NULL) s = s->RightChild; /***循环遍历p结点左子树的右子树,找到被删结点左子树对称序的最 后一个结点的位置,用于将被删结点的右子树“嫁接”为此结点的右子树***/ if (f == NULL) //p为根结点 { root = p->LeftChild; //p的左儿子替代p的位置 s->RightChild = p->RightChild; /***将p的右子树“嫁接”到p右子树对称序最后一个结点, 作右子树***/ } else if (f->LeftChild == p) //被删结点p是其父结点的左孩子结点 { f->LeftChild = p->LeftChild; //p结点的左儿子结点替代被删结点的位置 s->RightChild = p->RightChild; /***将p的右子树“嫁接”到p右子树对称序最后一个结点, 作右子树***/ } else //被删结点p是其父结点的右孩子结点 { f->RightChild = p->LeftChild; //p结点的左儿子结点替代被删结点的位置 s->RightChild = p->RightChild; /***将p的右子树“嫁接”到p右子树对称序最后一个结点, 作右子树***/ } } }
-
-
第二种方式,规定如下
- 当被删结点有左右儿子时,用被删除结点左子树下对称序的最后一个结点来真正替换被删结点,或被删除节点右子树下对称序的第一个节点来真正替换被删结点
-
用被删除结点左子树下对称序的最后一个结点来真正替换被删结点
-
用被删除节点右子树下对称序的第一个节点来真正替换被删结点
-
- 当被删结点有左右儿子时,用被删除结点左子树下对称序的最后一个结点来真正替换被删结点,或被删除节点右子树下对称序的第一个节点来真正替换被删结点
等概率查找对应的最佳二叉排序树
扩充二叉树的概念
- 为了合理评价二叉排序树的查找效率,定义扩充二叉树:
-
对于一颗二叉树的结点,若出现空的子树时,就增加新的、特殊的结点——空树叶
-
在这种扩充二叉树中,原来二叉树的结点称为内部结点,新加的叶结点称为外部结点。对于这种外部结点是有意义的,例如下图中的外部结点A表示关键值码大于65而小于70的可能结点
-
外部路径长度和内部路径长度
-
定义外部路径长度E为从扩充的二叉树的根结点到每个外部结点的路径长度之和
-
定义内部路径长度I为扩充二叉树的根结点到每个内部结点的路径长度之和
-
例如上图所示的E和I分别为:
不难证明:一颗有n个结点的扩充二叉树,其外部结点有n+1个
-
则用数学归纳法可以证明:含有n个内部结点的扩充二叉树,对应的E、I之间满足:
E = I + 2n
-
证明如下:
-
-
考虑查找所有内部结点和外部结点概率相等时二叉排序树的查找效率。在查找过程中,每进行一次比较,就进入下一层。因此对于成功的查找,比较次数就是关键码所在的层数,对于不成功的查找,被查找的关键码属于一个对应的外部结点代表的所有可能关键码集合,比较次数等于此外部结点的层数减1.在等概率的情况下,在二叉排序树里,查找一个关键码的平均比较次数为:
等概率查找对应的最佳二叉排序树
-
定义E(n)最小的二叉排序树称为等概率查找对应的最佳二叉排序树(或称等权情况下的最佳二叉排序树)
-
显然E(n)最小等价于 I 最小,即内部路径长度最小的二叉排序树为**等概率查找对应的最佳二叉排序树。**然而在一棵二叉排序树中,路径长度为0的结点最多一个,路径长度为1的结点最多2个……路径长度为k的最多$2^{k}$个(k=0,1,2,……),因此,对于n个结点的二叉排序树,I 的最小值为序列:$0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,...$,前n项的和为: