一、二叉排序树的定义
二叉排序树,又称“二叉查找树”。(BST,Binary Search Tree)二叉排序树可用于元素的有序组织、搜索。
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树。
①若它的左子树非空,则左子树上所有结点的值均小于根节点的值;
②若它的右子树非空,则右子树上所有结点的值均大于根结点的值;
③它的左右子树也分别是二叉排序树。
左子树结点值 < 根结点值 < 右子树结点值
二叉排序树是递归定义的。由定义可以得出二叉排序树的一个重要性质:中序遍历一棵二叉树时可以得到一个结点值递增的有序序列。
二、查找操作
实现思想及时间复杂度:
思想:
若树非空,目标值与根结点的值进行比较:
若相等,则查找成功;
若小于根结点,则在左子树上查找,否则在右子树上查找。
查找成功,返回结点指针;查找失败返回NULL。
时间复杂度:非递归实现:最坏时间复杂度O(1)
递归实现:最坏时间复杂度O(h):h为树高
二叉排序树查找代码实现:
非递归实现的代码:
//二叉排序树查找
//二叉排序树结点
typedef struct BSTNode{
int key;
struct BSTNode *lchild, *rchild;
}BSTNode, *BSTree;
//非递归实现:在二叉排序树中查找值为key的结点
BSTNode *BST_Search(BSTree T, int key){
while(T != NULL && T->key != key){
if(T->key < key){
T = T->rchild;
}else{
T = T->lchild;
}
}
return T;
}
递归实现的代码:
//递归实现:在二叉排序树中查找值为key的结点
BSTNode *BST_Search(BSTree T, int key){
if(T == NULL){
return NULL;//空树,查找失败
}
if(T->key == key){
return T;//查找成功
}else if(T->key > key){
return BST_Search(T->lchild, key);
}else{
return BST_Search(T->rchild, key);
}
}
三、插入操作
实现思路及时间复杂度:
思路:
若原二叉排序树为空,则直接插入结点;
否则,若关键字k小于根结点,则插入其左子树;
若关键字k大于根结点,则插入右子树。
二叉树中不允许两个结点的值相等,所以当所插结点的值已存在,那么就插入失败。
时间复杂度:新插入的结点一定是叶子结点,因此最坏时间复杂度是O(h).
二叉排序树插入代码实现:
非递归实现:
//二叉排序树插入操作
//非递归实现:利用循环
int BST_Insert(BSTree &T, int k){
if(T == NULL){//原树为空,则新插结点为根结点
T = (BSTree)malloc(sizeof(BSTNode));
T->key = k;
T->lchild = T->rchild = NULL;
return 1;
}
BSTNode *parent = NULL;//当前cur的父结点
BSTNode *cur = T;
while(cur){//找到目前cur应该在的位置
if(cur->key < k){
parent = cur;
cur = cur->rchild;
}else if(cur->key > k){
parent = cur;
cur = cur->lchild;
}else{
return false;
}
}
//到这里cur为空,就是k要插入的位置
cur = (BSTree)malloc(sizeof(BSTNode));
cur->key = k;
cur->lchile = cur->rchild = NULL;
//链接
if(k < parent->key){
parent->lchild = cur;
}
if(k > parent->key){
parent->rchild = cur;
}
return 1;
}
递归实现:
//二叉排序树插入操作:递归实现
//二叉排序树结点
typedef struct BSTNode{
int key;
struct BSTNode *lchild, *rchild;
}BSTNode, *BSTree;
//在二叉排序树插入关键字为k的新结点(递归实现)
int BST_Insert(BSTree &T, int k){
if(T == NULL){//原树为空,新插结点为根结点
T = (BSTree)malloc(sizeof(BSTNode));
T->key = k;
T->lchild = T->rchild = NULL;
return 1;
}else if(T->key == k){//树中存在相同关键字的结点,插入失败
return 0;
}else if(k < T->key){
return BST_Insert(T->lchild, k);//插左子树
}else{//插右子树
return BST_Insert(T->rchild, k);
}
}
四、删除操作
实现思路及时间复杂度:
先搜索找到目标结点:
①若被删除的结点z是叶子结点,则直接删除,不会破坏二叉排序树的性质。
②若被删除结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,代替z的位置。
③若结点z有左右两棵子树,则令z的直接后继(或直接前驱)代替z,然后从二叉排序树中删除这个直接后继(或直接前驱),这样就转换成了第①或第②种情况。
对③的解释:用直接后继来替代,即:从当前要删除的结点z的右子树中找到值最小的结点(右子树按照中序遍历第一个被访问的结点),代替z的位置。
用直接前驱来代替,即:从当前要删除的结点z的左子树中找到值最大的结点(左子树按中序遍历最后一个被访问的结点),代替z的位置。
五、查找效率分析
平均查找长度的数量级就是查找操作的时间复杂度。
-查找成功:
查找长度:在查找运算中,需要对比关键字的次数称为查找长度,反映了查找操作时间复杂度。
若树高h,找到最下层的一个结点需要对比h次。
最好情况:n个结点的二叉树最小高度为⌊log2n⌋+1。平均查找长度 = O(log2n)
最坏情况:每个结点只有一个分支,树高h = 结点数n。平均查找长度 = O(n)
-查找失败:
查找失败的结点只能出现在,最下层叶子结点的下方。因此查找失败的平均查找长度ASL与树高h密切相关。