二叉查找树(Binary Sort Tree)又称二叉排序树。 它或者是一棵空树;或者是具有下列性质的二叉树: (1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值; (2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值; (3)左、右子树也分别为二叉排序树;
由二叉查找树的性质可以知道,我们如果以二叉树的中序遍历一颗二叉查找树的话,可以得到顺序的数字。下面先给出二叉排序树的常用操作代码,然后在后面再具体讨论其中的一些细节。
c语言代码如下:
由上面的代码可以看到,二叉查找树主要由构建构建二叉查找树,往而二叉查找树中新添加节点,中序遍历,查找节点和删除节点等构成。
往二叉查找树中添加节点比较简单, 就是不停的遍历这个二叉查找树,如果当前节点的值比需要加入的节点大,就去它的左子树中找,否则去它的右子树中找。当找到它的左子树或者右子树不存在的时候,就在该节点的左子树或者右子树中插入对应的节点。
二叉查找树的构建是建立在上述添加二叉查找树基础上的,首先让第一个节点加入二叉查找树中,让它成为根节点,然后让其它的节点依次插入。
中序遍历的结果为:9 17 19 21 22 27 30 32 34 45。
对于二叉查找树来说,最不好理解的地方还是在于删除的地方。比如在上述图中,如果我要删除17这个节点,那么删除后如何来维护这颗二叉查找树的平衡?
删除分3种情况:
1.被删除节点的左子树和右子树都为空,这是最好处理的方法。
2.被删除节点的左子树或者右子树有一个为空。
3.被删除节点的左子树和右子树都不为空。
insertBinarySearch(&head,18);
我们将18的值也插入二叉查找树中,这个时候的二叉查找树就变成了如下结构:
这个时候就不能依照上面的判断了。 根据上面的说明我们知道,需要把17这个节点的值换成18,让19这个节点的左子树设置为NULL,并且也必须要将18这个节点的值给释放了。(实现过程看代码);这样以后这颗二叉树就变成了:
可以看到这个二叉树还是保持平衡的。
由二叉查找树的性质可以知道,我们如果以二叉树的中序遍历一颗二叉查找树的话,可以得到顺序的数字。下面先给出二叉排序树的常用操作代码,然后在后面再具体讨论其中的一些细节。
c语言代码如下:
# include <stdio.h>
# include <malloc.h>
# include <stdlib.h>
typedef struct binary{
int data;
struct binary *left;
struct binary *right;
}binaryTree;
void insertBinarySearch(binaryTree * head,int data);
void createBinarySearch(binaryTree * head,int *arr,int n);
void traverseMiddle(binaryTree * head);
binaryTree * findBinarySearch(binaryTree * head,int data);
void deleteBinarySearch(binaryTree * head,int data);
int main(void){
int arr[10] = {22,17,34,19,30,27,45,9,32,21};
binaryTree head;
binaryTree * num; //保存找到的指针节点
createBinarySearch(&head,arr,10);
traverseMiddle(&head);
num = findBinarySearch(&head,28);
if(num!=NULL){
printf("查找成功");
}else{
printf("查找失败");
}
printf("\n");
deleteBinarySearch(&head,22);
traverseMiddle(&head);
return 0;
}
//向二叉查找树中新加入节点
void insertBinarySearch(binaryTree * head,int data){
binaryTree *h,*p,*parent; //h是头节点,p是新申请的内存节点,parent保存的是新申请节点的父节点
p = (binaryTree *)malloc(sizeof(binaryTree)); //申请新加入二叉排序树的节点的内存分配
p->data = data;
p->left = p->right = NULL;
h = head;
//找到需要对应插入的节点的父节点
while(h){
parent = h; //parent保存它的父节点
if(h->data > data){
h = h->left;
}else{
h = h->right;
}
}
//根据data的值判断是插入在parent节点的左边还是右边
if(parent->data > data){
parent->left = p;
}else{
parent->right = p;
}
}
//创建一个完整的二叉查找树
void createBinarySearch(binaryTree * head,int *arr,int n){
int i;
head->data = arr[0]; //把第一个节点拿出来单独插入
head->left = head->right = NULL;
//把其它对应的节点插入二叉查找树中
for(i=1;i<n;i++){
insertBinarySearch(head,arr[i]);
}
}
//中序遍历查看二叉查找树
void traverseMiddle(binaryTree * head){
if(head!=NULL){
traverseMiddle(head->left);
printf("%d ",head->data);
traverseMiddle(head->right);
}
}
//在二叉查找树中进行查找
binaryTree * findBinarySearch(binaryTree * head,int data){
//循环找到对应的节点
while(head != NULL){
if(head->data>data){ //如果要查找的值比对应的节点小,就去它的左子树找
head = head->left;
}else if(head->data<data){ //如果要查找的值比对应的节点大,就去它的右子树找
head = head->right;
}else{
return head; //如果相等返回对应的节点
}
}
return NULL;
}
//二叉树的删除操作
void deleteBinarySearch(binaryTree * head,int data){
int flag = 1; //标志位,用来判断被删除节点是其父节点的左子树还是右子树 1表示左子树 ,2表示右子树
binaryTree *h,*parent; //parent用来保存被删除节点的父节点
binaryTree *rparent,*r; //这里2个指针的作用是在删除的时候左右子树都不为空的时候使用
if(head == NULL){
return ;
}
h = head;
parent = h;
while(h != NULL){
if(h->data > data){ //向左子树中去查找
flag = 1;
parent = h;
h = h->left;
}else if(h->data < data){ //向右子树中去查找
flag = 2;
parent = h;
h = h->right;
}else if(h->data == data){ //开始删除操作
if(h->left == NULL && h->right == NULL){ //如果左子树和右子树都为空
if(h == head){ //如果是根节点
free(h);
}else if(flag == 1){ //如果被删除节点是其父节点的左子树,则让父节点的left设置为NULL
parent->left = NULL;
free(h);
}else if(flag == 2){ //如果被删除节点是其父节点的右子树,则让父节点的right设置为NULL
parent->right = NULL;
free(h);
}
}else if(h->left == NULL && h->right != NULL){ //左子树为空,右子树不为空的情况
if(flag == 1){ //如果它是其父节点的左子树
parent->left = h->right; //让父节点的左子树指向它的右节点
}else{
parent->right = h->right; //让父节点的右子树指向它的右节点
}
free(h);
}else if(h->left != NULL && h->right == NULL){ //左子树不为空,右子树为空的情况
if(flag == 1){
parent->left = h->left;
}else{
parent->right = h->left;
}
free(h);
}else if(h->left != NULL && h->right != NULL){ //左右子树都不为空的情况
rparent = h; //保存被删除节点的父节点(这里的父亲节点不是h的父节点,而是实际删除的节点)
r = rparent->right; //在它的右子树中进行查找
if(r->left != NULL){ //它的左子树存在,就把左子树的最后一个节点的值换成h的值
while(r->left != NULL){
rparent = r;
r = r->left;
}
h->data = r->data;
rparent->left = NULL;
free(r);
}else{ //它的左子树不存在的话,直接删除r,并且让h的值等于r的值
h->data = r->data;
h->right = r->right;
free(r);
}
}
//最后一定要把删除的指针设置为NULL
r = NULL;
h = NULL;
}
}
}
由上面的代码可以看到,二叉查找树主要由构建构建二叉查找树,往而二叉查找树中新添加节点,中序遍历,查找节点和删除节点等构成。
往二叉查找树中添加节点比较简单, 就是不停的遍历这个二叉查找树,如果当前节点的值比需要加入的节点大,就去它的左子树中找,否则去它的右子树中找。当找到它的左子树或者右子树不存在的时候,就在该节点的左子树或者右子树中插入对应的节点。
二叉查找树的构建是建立在上述添加二叉查找树基础上的,首先让第一个节点加入二叉查找树中,让它成为根节点,然后让其它的节点依次插入。
前面说过,二叉查找树的中序遍历结果就是按照顺序组成的由大到小的数字。先来看下经过上面的构造的二叉查找树图:
中序遍历的结果为:9 17 19 21 22 27 30 32 34 45。
对于二叉查找树来说,最不好理解的地方还是在于删除的地方。比如在上述图中,如果我要删除17这个节点,那么删除后如何来维护这颗二叉查找树的平衡?
删除分3种情况:
1.被删除节点的左子树和右子树都为空,这是最好处理的方法。
2.被删除节点的左子树或者右子树有一个为空。
3.被删除节点的左子树和右子树都不为空。
对于第一种情况和第二种情况,这里不再多说了,上面代码中已经有了注释,这里我们主要来看第三种的情况。需要找出被删除节点中序遍历后的后继节点,该节点的值要比被删除节点的值大,所以只能去它的右子树中去找。而且也要保证这个节点是右子树中最小的,所以必须找到它在右子树中的最小值,所以这个最小值必然在其右子树的最左边。(为什么这样找,看下图就明白了,因为必须保持整个二叉查找树的平衡)。这里可能存在其右子树的左子树不存在的情况,所以需要分别处理。
insertBinarySearch(&head,18);
我们将18的值也插入二叉查找树中,这个时候的二叉查找树就变成了如下结构:
这个时候就不能依照上面的判断了。 根据上面的说明我们知道,需要把17这个节点的值换成18,让19这个节点的左子树设置为NULL,并且也必须要将18这个节点的值给释放了。(实现过程看代码);这样以后这颗二叉树就变成了:
可以看到这个二叉树还是保持平衡的。