您的位置:首页 > 理论基础 > 数据结构算法

[数据结构]二叉搜索树概念及基本操作

2014-05-20 01:21 387 查看
二叉搜索树是一种动态数据结构,其最重要的特点是其保持有序性,这样可以保证在进行搜索时比其他的结构快很多。一棵二叉搜索树要保证如下的属性和特点:


满足二叉树定义

若其左子树不空,则左子树上所有节点的值均小于它的根节点的值;

若其右子树不空,则右子树上所有节点的值均大于它的根节点的值;

其左右子树各自均满足二叉搜索树定义

BST(尤其是平衡时)在插入和删除时非常快。其相关代码代码简单高效。
BST是构成一些更加复杂的数据结构的基础(如set容器)。但是当插入序列是已排好的数据时,其形成的BST的形状会退化成链表类型结构,此时在这个树中的搜索和插入都会变的低效率。基于此的改善包括Self-Balancing Binary Search Tree.
比较典型的包括AVL树和RBT树(红黑树)。

数据结构采用二叉结构,包含Datatype类型(此处定义为)以及左孩子和右孩子指针。

typedef int Datatype;
typedef struct NODE{
Datatype data;
NODE *left, *right;
}Node;
Node *creatNode(const Datatype &key){
Node *p = new Node;
p->data = key;
p->left = NULL;
p->right = NULL;
return p;
}


插入

BST的插入很简单,从根节点开始进行一
次二分查找。以下提
供递归和非递归方法。

Node *insert(Node *&p, Datatype &key){
//递归调用时要控制返回,此返回新插入的节点
if (!p){
p = creatNode(key);
return p;
}
if (key == p->data) return p;//包含此树则不建立新节点
else if (key < p->data) p->left = insert(p->left, key);
else p->right = insert(p->right, key);
return p;
}

void insert_2(Node *&p, Datatype &key){
//非递归调用,重点在while部分的控制
if (!p) {
p = creatNode(key); //p是引用,这样在空树的时候可以建树
return;
}
Node *temp = p; //p是引用,循环时会被破坏。所以用另一个变量替代
while (temp){
if (key == temp->data) break;//已包含此节点,无作为。
if (key < temp->data){
if (temp->left) temp = temp->left;
else {
temp->left = creatNode(key);
break;
}
}
else{
if (temp->right) temp = temp->right;
else {
temp->right = creatNode(key);
break;
}
}
}
}


搜索

搜索代码与插入代码基本一致

Node *search(Node *&p, const Datatype &key){
//递归搜索 空指针则失败。
if (!p) return p;
if (p->data == key) return p;
if (key < p->data) return search(p->left, key);
else return search(p->right, key);
}

Node *search_2(Node *p, const Datatype &key){
//非递归搜索 返回找到的指针,空指针则失败
if (!p) return p;
while (p){
if (key == p->data) return p;
if (key < p->data) p = p->left;
else p = p->right;
}
return p;
}


删除节点

BST的删除分三种情况,即按照要删除节点无孩子,只有一个孩子,或者有两个孩子分三类。

case1:对应无孩子节点,则直接删除该节点,并将其双亲节点的孩子指针置空。

case2:只有一个孩子节点,将双亲节点孩子指向此节点的孩子,删除该节点。

case3:有两个孩子节点,此时情况略微复杂一些。即我们需要做的不是删除直接对应的节点,而是要用其直接前驱 或者直接后继节点代替此节点,并删除那个相应的前驱或者后继。而且此时的那个前驱或者后继对应的就是上面的case1或者case2情况之一。

道理也好理解,因为受制于搜索二叉树特有的中序遍历有序结构,对于第三种情况,选用左子树中最大的节点(直接前驱)或者右子树中最小的节点(直接后继)本质上是没有区别的。实际编写代码时,开始采用非递归方法,但是有一个很大的问题是要知道被删除节点的双亲节点,因为在释放被删除节点后,务必还有将其双亲节点的孩子指针置空。处理case3时,同样也要得到直接前驱(或后继)的那个节点的双亲。如此一来,代码量很大,情况分析也很复杂。基于此,还是选择了《数据结构》书中的递归方法,代码简介一目了然。考虑到二叉搜索树的删除复杂度属于O(n)级别,所以递归的代价不会非常大。

bool delNode(Node *&target){
//子函数,用来删除节点
Node *temp;
if (!target->right){
temp = target;
target = target->left;
delete temp;
}
else if (!target->left){
temp = target;
target = target->right;
delete temp;
}
else{
//以上两个判断合起来对应case2
//这个else对应处理case3
//case1 实际上被内化在以上两个判断中了
Node *temp2;
temp = target;
temp2 = target->left;
while (temp2->right){ temp = temp2; temp2 = temp2->right; }
target->data = temp2->data;
if (temp == target) temp->left = temp2->left;
else temp->right = temp2->left;
delete temp2;
}
return true;
}
bool del_BST(Node *&p, const Datatype &key){
//删除函数,用来找到被删除元素并进行删除操作。
if (!p) return false;
if (p->data == key) return delNode(p);
else if (key < p->data) return del_BST(p->left,key);
else return del_BST(p->right, key);
}


以上的代码包含了对于二叉搜索树的插入,搜索和删除节点操作。

还有一个简洁的代码涉及判定给定二叉树是否是二叉搜索树。由二叉搜索树定义可知,仅仅判定每个节点是否介于左右两个孩子节点的值之间是不够的,因为在左子树中不能保证左子树的根节点是最大值。所以这个情况是我们知道每次判定节点的值区间时,所涉及的比较应该是两个门限而不是仅仅对应的两个孩子节点。

bool isBST(Node *p, Datatype min, Datatype max){
//min 表示应该大于的值,max表示应该小于的值
if (!p) return true;
if (p->data > max || p->data < min) return false;
else return(isBST(p->left, min, p->data) && isBST(p->right, p->data, max));
}


总结

至此,简单的二叉搜索树的相关操作均给出了代码实现。最后对于动态数据结构,给出其一些操作的复杂度。

在一般情况下,考虑随机数列,那么二叉搜索树应该是接近平衡,此时的搜索比较次数即从根节点到每个叶子的路径(深度),其复杂度(包括删除,搜索和插入)均为 O(logn),但是对于动态插入有序数列,那么此时作为最坏情况,BST接近一维结构,这时前面所述的复杂度均为O(n).

可见,降低复杂度的形象的办法是使得BST尽量分布均匀。而这就引出了自平衡二叉排序树,通过如左旋,右旋等操作动态控制生成的二叉树的分布尽量均匀,从而保证复杂度始终在O(logn).
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: