原创作品,转载请注明出处:点我
二叉查找树(Binary Search Tree,BST),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
中序遍历二叉排序树可得到一个关键字的有序序列,一个无序序列可以通过构造一棵二叉排序树变成一个有序序列,构造树的过程即为对无序序列进行排序的过程(其实构建BST的过程就是对数据进行快速排序的过程,也就是快排算法)。所以通常可以通过构建一棵二叉查找树来对数据进行排序,其时间复杂度为O(nlogn),对BST进行搜索,插入和删除的时间复杂度即为树的高度,O(logn).
先来看下本文实现的对BST进行的操作。
void create_tree(node **tree,int data);//生成二叉查找树 void mid_travel(node *tree);//中序遍历树 node* find_node(node *tree,int data);//查找 node* find_parent_node(node *tree,int data);//查找data节点的父节点 node* insert_node(node **tree,int data);//插入数据 node* get_min_parent_ptr(node *tree);//获得tree的最小值的父亲指针 node* get_max_patent_ptr(node *tree);//获取tree的最大值的父亲指针 bool delete_node(node **tree,int data);//删除指定的节点
其中,node的定义为:
typedef struct tree_node { int data;//数据域 struct tree_node *r_child;//左孩子节点 struct tree_node *l_child;//右孩子节点 }node ;
接着来看下BST的生成create_tree(),其中第一个参数是一个二级指针,指向BST的根节点,注意,这里一定要以二级指针的形式传入,不然后构造失败的,第二个参数是用来生成BST的数据。
1 /* 2 * 生成二叉树 3 * tree:二级指针,指向生成树 4 * data:生成树的数据 5 * 注意:这里只能传入二级指针 6 * author:rio_2607 7 */ 8 void create_tree(node **tree,int data) 9 { 10 if(NULL == *tree) 11 { 12 *tree = (node *)malloc(sizeof(node)); 13 (*tree)->data = data; 14 (*tree)->l_child = NULL; 15 (*tree)->r_child = NULL; 16 return; 17 } 18 else if(data < (*tree)->data)//如果小于根节点的数据,则插入到根节点的左子树 19 create_tree(&((*tree)->l_child),data); 20 else if(data > (*tree)->data)//如果大于根节点的数据,则插入到根节点的右子树 21 create_tree(&((*tree)->r_child),data); 22 else 23 { 24 cout << "有重复数据" << endl; 25 return; 26 } 27 }
再来看下对BST进行查找,这很简单,首先拿待查找的数据跟根节点数据进行比较,如果小于根节点数据,则递归的对根节点的左子树进行查找,如果大于根节点的数据,则对右子树递归的进行查找,如果查找大叶子节点还没有查找到,则要查找的数据不存在,代码如下:
1 /* 2 * 查找data是否存在 3 * 如果存在,返回对应的节点的指针否则返回NULL 4 * author:rio_2607 5 */ 6 node* find_node(node *tree, int data) 8 { 9 if(data == tree->data) 10 return tree; 11 else if(data < tree->data && tree->l_child != NULL) 12 //如果data小于根节点的数据,则对左子树进行查找 13 return find_node(tree->l_child,data); 14 else if(data > tree->data && tree->r_child != NULL) 15 //如果data大于根节点的数据,则对右子树进行查找 16 return find_node(tree->r_child,data); 17 return NULL; 18 }
再来看看插入节点:
1 /* 2 * 在合适的位置插入数据 3 * tree:二级指针,指向二叉搜索树 4 * data:要插入的数据 5 * 返回被插入节点的指针 6 * author:rio_2607 7 */ 8 node* insert_node(node **tree,int data) 9 { 10 node *temp = (node *)malloc(sizeof(node)); 11 temp->data = data; 12 temp->l_child = NULL; 13 temp->r_child = NULL; 14 15 if(NULL == *tree)//如果*tree为空,说明tree还没有数据,则直接插入 16 { 17 *tree = temp; 18 return *tree; 19 } 20 else if(data < (*tree)->data)//如果小于根节点的数据,则插入到他的左子树中 21 return insert_node(&((*tree)->l_child),data); 22 else if(data > (*tree)->data)//如果data大于根节点的数据,则插入到右子树中去 23 return insert_node(&((*tree)->r_child),data); 24 else 25 { 26 cout << "不能添加重复数据" << endl; 27 return NULL; 28 } 29 }
其实对于BST,生成,查找,插入都是很简单的递归操作,麻烦的是删除节点。那么应该怎么删除BST的节点呢?
由于BST的特殊性,BST有以下四个很明显的性质:
1、BST的右子树的最小值,没有左子树(如果有的话,就不会是最小值,因为它的所有的左子树上的值都小于根节点的值)
2、BST的左子树的最大值,没有右子树(如果有的话,就不会是最大值,因为它的所有的右子树上的值都大于根节点的值)
3、BST的左子树中的最大值一定位于该左子树中的最右边的节点上
4、BST中的右子树的最小值一定位于该右子树中的最左边的节点上
这四句话怎么理解呢?如下图所示:
以跟节点为例,根节点的右子树的最小值为15,15这个节点位于右子树的最左端,没有左子树,左子树的最大值为12,位于左子树的最右边,没有右子树。
好了,明白了上面的几点的话就可以进行删除节点的操作了。删除节点的操作为:
1、找到要删除的节点p
2、先看下以节点P否存在右子树,如果存在右子树,就找到右子树中的最小值,把这个最小值赋值给要删除的节点,然后把这个找到的最小值节点删除,删除时有些收尾工作
3、如果以节点P没有右子树,那么就找到节点P的左子树的最大值,把这个最大值赋值给要删除的节点,然后把这个最大值节点给删除,删除时有些收尾工作
4、如果以节点P既没有左子树也没有右子树,说明节点P是一个叶子节点,直接删除即可,删除时有些收尾工作
以上图为例,假如要删除节点13,那么先找到节点13的右子树的最小值,这里是节点15,找到节点15之后,把这个节点的数据,也就是15赋值给要删除的节点13,然后把节点15的父节点,这里是19,的左孩子指针指向节点15的右孩子节点,这里是节点17,然后删除节点15,即可。如下图所示:
因为这里既要用到右子树的最小值节点,记为m,也要用到最小值节点的父节点,记为p,所以我们直接找到最小值节点的父节点,这里是节点19,使用以下代码进行操作:
1 //假设要删除的节点为d 2 //最小值节点的父节点为p,最小值节点为m 3 //其中m = p->l_child 4 d->data = m->data 5 p->l_child = m->r_child 6 free(m)
在查找右子树的最小值的父节点的时候,会碰到一种特殊的情况,如下图:
要删除的节点1,先要找到节点1的右子树的最小值节点,就是节点2,但是在这颗右子树中,节点2没有父节点(因为这是节点1的右子树,节点1不包含在这颗右子树当中),此时,最小值节点的父节点就是最小值节点本身,这种情况下,父节点p的l_child节点为空。所以在代码中,要对这种情况进行特殊处理:直接把父节点p的data域赋值给要删除的节点,然后把要删除的节点(这里是节点1)的右孩子指针指向父节点(这里是节点2)的右孩子节点(这里是节点5),然后删除节点2。
以上图为例,假设我们要删除节点13,由于节点13没有右子树,所以我们要找到节点13的左子树(这里由节点10,节点12和节点11组成)的最大值,这里是节点12,找到之后,把最大值节点(节点12)的数据赋值给要删除的节点,这里是节点13,然后把最大直接点的父节点(这里是节点10)的右孩子指针指向最大值节点的左孩子节点(这里是节点11),然后删除最大直接点(节点12).如下图所示:
同样的,在处理这种情况的时候,跟上面一样,也会碰到一种特殊的情况,如下图所示:
要删除节点14,找到他的左子树的最大值,为节点10,但是在这个左子树(不包括节点14)中,节点10没有父节点,或者说节点10的父节点就是他本身,这种情况要单独处理,处理方法跟上面一样。
如果要删除的节点是叶子节点的话,这种情况就很简单了,但是,这一点要注意的是,在把叶子节点删除了之后,要把这个叶子节点的父节点对应的指针域(l_child或者r_child)置为NULL.具体操作看代码。
好了,现在可以上代码了。首先是get_min_parent_ptr()
1 /* 2 * 返回树中最小值节点的父节点的指针 3 * 最小值一定是位于左子树的叶子节点 4 * author:rio_2607 5 */ 6 node* get_min_parent_ptr(node *tree) 7 { 8 if(NULL == tree) 9 { 10 cout << "tree is empty" << endl; 11 return NULL; 12 } 13 //这种情况对应着父节点记为自身的情况 14 if(tree->l_child == NULL) 15 return tree; 16 17 node *tree_temp = tree; 18 node *temp = tree_temp->l_child; 19 while(temp->l_child != NULL) 20 { 21 tree_temp = temp; 22 temp = temp->l_child; 23 } 24 25 return tree_temp; 26 27 }
接下来是get_max_patent_ptr()
1 /* 2 * 返回树中最大值节点的父节点的指针 3 * 最大值一定是位于右子树的叶子节点 4 * author:rio_2607 5 */ 6 node* get_max_patent_ptr(node *tree) 7 { 8 if(NULL == tree) 9 { 10 cout << "tree is empty" << endl; 11 return NULL; 12 } 13 //这种情况对应着父节点记为自身的情况 14 if(tree->r_child == NULL) 15 return tree; 16 17 node *tree_temp = tree; 18 node *temp = tree_temp->r_child; 19 while(temp->r_child != NULL) 20 { 21 tree_temp = temp; 22 temp = temp->r_child; 23 } 24 25 return tree_temp; 26 }
接下来是get_parent_ptr()
1 /* 2 * 找到指定节点的父节点 3 * 返回父节点的指针,否则返回NULL 4 * 一般只会查找叶子节点的父节点 5 * author:rio_2607 6 */ 7 node* find_parent_node(node *tree,int data) 8 { 9 if(data == tree->data) 10 return tree; 11 if(data < tree->data && tree->l_child != NULL) 12 { 13 if(data == tree->l_child->data) 14 return tree; 15 else 16 return find_parent_node(tree->l_child,data); 17 } 18 else if(data > tree->data && tree->r_child != NULL) 19 { 20 if(data == tree->r_child->data) 21 return tree; 22 else 23 return find_parent_node(tree->r_child,data); 24 } 25 return NULL; 26 }
接下来就是具体的删除节点的代码了delete_node():
1 /* 2 * 删除data节点 3 * author:rio_2607 4 */ 5 bool delete_node(node **tree,int value) 6 { 7 node *value_ptr = find_node(*tree,value); 8 if(NULL == value_ptr) 9 { 10 cout << value << " is not in the data" << endl; 11 return false; 12 } 13 14 //如果要删除的数据是叶子节点,先找到这个叶子节点的父节点,把父节点的孩子节点指针清空,再删除叶子节点 15 if(NULL == value_ptr->l_child && NULL == value_ptr->r_child) 16 { 17 //叶子节点一定有父节点,找到父节点 18 node *parent_ptr = find_parent_node(*tree,value); 19 //判断叶子节点是父节点的左节点还有右节点 20 if(parent_ptr->l_child->data == value) 21 parent_ptr->l_child = NULL; 22 else if(parent_ptr->r_child->data == value) 23 parent_ptr->r_child = NULL; 24 free(value_ptr); 25 //注意,这里要把根节点的父节点的对应的指针域清空 26 return true; 27 } 28 //如果value_ptr的右子树存在,则找到右子树的最小值 29 if(value_ptr->r_child != NULL) 30 { 31 node *min_parent_ptr = get_min_parent_ptr(value_ptr->r_child); 32 if(min_parent_ptr != NULL && min_parent_ptr->l_child != NULL) 33 { 34 node *delete_ptr = min_parent_ptr->l_child;//右子树的最小值的节点指针 35 value_ptr->data = delete_ptr->data;//把要删除的节点的数据域的值赋值为右子树的最小值 36 //把最小值的节点的父节点的左子树指向最小值节点的右子树 37 //因为最小值节点没有左子树 38 min_parent_ptr->l_child = delete_ptr->r_child; 39 free(delete_ptr);//删除最小值节点 40 return true; 41 } 42 else if(min_parent_ptr != NULL && NULL == min_parent_ptr->l_child) 43 { 44 //如果min_parent_ptr->l_child为空,说明以value_ptr为根节点的树没有左子树 45 //则value_ptr的右子树的根节点就是最小值 46 value_ptr->data = min_parent_ptr->data; 47 value_ptr->r_child = min_parent_ptr->r_child; 48 free(min_parent_ptr); 49 return true; 50 } 51 } 52 //如果没有右子树有左子树,则找出左子树的最大值 53 else if(NULL == value_ptr->r_child && value_ptr->l_child != NULL) 54 { 55 node *max_parent_ptr = get_max_patent_ptr(value_ptr->l_child); 56 if(max_parent_ptr != NULL && max_parent_ptr->r_child != NULL) 57 { 58 node *delete_ptr = max_parent_ptr->r_child;//左子树的最大值节点指针 59 value_ptr->data = delete_ptr->data;//把要删除节点的数据域赋值为左子树的最大值 60 //把最大值节点的父节点的右子树赋值为最大值节点的左子树 61 //因为最大值节点没有右子树 62 max_parent_ptr->r_child = delete_ptr->l_child; 63 free(delete_ptr); 64 return true; 65 } 66 else if(max_parent_ptr != NULL && NULL == max_parent_ptr->r_child) 67 { 68 //如果max_parent_ptr->r_child为空,说明value_ptr没有右子树 69 //则value_ptr的左子树的根节点就是最大值 70 value_ptr->data = max_parent_ptr->data; 71 value_ptr->l_child = max_parent_ptr->l_child; 72 free(max_parent_ptr); 73 return true; 74 } 75 } 76 77 return false; 78 }