第一部分 二叉搜索树
1、二叉树的相关定义
二叉搜索树即二叉排序树,又叫二叉查找树
定义:
二叉排序树或者是一颗空树,或者是具有下列性质的二叉树:
(1)若它的左子树不空,则左子树上的所有结点的值均小于它的根结点的值。
(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
(3)它的左、右子树叶分别是二叉排序树。
值得注意的是:当用线性表作为表的组织形式时,可以有三种查找法。其中以二分查找效率最高。但由于二分查找要求表中结点按关键字有序,且不能用链表作存储结构,因此,当表的插入或删除操作频繁时,为维护表的有序性,势必要频繁移动表中结点。这种由移动结点引起的额外时间开销,就会抵消二分查找的优点。也就是说,二分查找只适用于静态查找表。若要对动态查找表进行高效率的查找,可采用下二叉搜索树
特点是:按照中序遍历该树所得到的中序序列是一个递增有序序列。
上面的二叉搜索树中序序列为2 3 4 5 7 8
2二叉搜索树的操作
树节点的定义
typedef class _BiTree{
public:
int data;
_BiTree* left;
_BiTree* right;
_BiTree(int data){
this->data=data;
}
}BiTree;
二叉搜索树查找关键字
在二叉排序树不为空树的前提下,首先用关键字和树的根结点进行比较,会有 3 种不同的结果:
如果相等,查找成功;
如果比较结果为根结点的关键字值较大,则说明该关键字可能存在其左子树中;
如果比较结果为根结点的关键字值较小,则说明该关键字可能存在其右子树中;
BiTree* SearchBST(BiTree* T,KeyType key){
//如果递归过程中 T 为空,则查找结果,返回NULL;或者查找成功,返回指向该关键字的指针
if (!T || key==T->data) {
return T;
}else if(key<T->data){
//递归遍历其左孩子
return SearchBST(T->lchild, key);
}else{
//递归遍历其右孩子
return SearchBST(T->rchild, key);
}
}
二叉搜索树中插入关键字
二叉排序树本身是动态查找表的一种表示形式,有时会在查找过程中插入或者删除表中元素,当因为查找失败而需要插入数据元素时,该数据元素的插入位置一定位于二叉排序树的叶子结点,并且一定是查找失败时访问的最后一个结点的左孩子或者右孩子。可见插入过程和查找过程类似
bool InsertBST(BiTree** T, int key) {
if (!(*T)) {
BiTree* newnode = new BiTree(key);
*T = newnode;
return true;
}
else if ((*T)->data == key) return false;
else if ((*T)->data > key) {
return InsertBST(&((*T)->left), key);
}
else if ((*T)->data < key) {
return InsertBST(&((*T)->right), key);
}
}
二叉搜索树的删除
删除可为BST问题最为复杂的一部分,需要考虑一下要删除的节点的四种情况:
该节点为叶子节点,删除即可
该节点只有左子树,没有右子树,删除后将该节点的左子树连接到该节点的父节点即可
该节点只有右子树,没有左子树,删除后将该节点的右子树连接到该节点的父节点即可
该节点既有左子树,也有右子树,这时候删除比较复杂。
首先,我们知道二叉排序树经过中序遍历后得到的是一个递增有序序列,该节点的前一个即为直接前驱,后一个为直接后继。我们要得到直接前驱的节点。
设要删除节点为p, p的双亲节点为f, p的左子树为left, 右子树为right。
方法一:left接到f上,将right接到p的直接前驱的右子树上,为何接到直接前驱的右子树上,因为直接前驱已经是左子树的最大值了。
直接前驱是p节点左子树的最右边的孩子,则直接前驱有可能有左子树,但不可能有右子树。
则方法二:用结点 p 的直接前驱(或直接后继)来代替结点 p,同时在二叉排序树中对其直接前驱(或直接后继)做删除操作。如果直接前驱有左子树,则将左子树接到直接前驱的双亲节点上。
现在就方法二,写出实现代码。
bool mydelete(BiTree** nodepp) {
BiTree* nodep = *nodepp;
BiTree* tmp, * q, * s;
if (!nodep->right && !nodep->left) {
*nodepp = NULL;
return true;
}
else if (nodep->left == NULL || nodep->right == NULL) {
*nodepp = nodep->left ? nodep->left : nodep->right;
free(nodep);
return true;
}
else {
q = nodep;
s = nodep->left;
while (s->right) {
q = s;
s = s->right;
}
nodep->data = s->data;
//判断q是否没有移动
if (q == nodep) q->left = s->left; //无需判断s->left是否为空
else q->right = s->left;
free(s);
return true;
}
}
bool DeleteBST(BiTree** T,int k) {
if (*T == NULL)
return false;
if ((*T)->data == k) {
return mydelete(T);
}
else if ((*T)->data > k) {
return DeleteBST(&((*T)->left), k);
}
else if ((*T)->data < k) {
return DeleteBST(&((*T)->right), k);
}
}
总结
二叉搜索树的查找节点的时间复杂度,和节点所在的深度有关。
为了弥补二叉排序树构造时产生如下图所示的影响算法效率的因素,需要对二叉排序树做“平衡化”处理,使其成为一棵平衡二叉树。