二叉查找树
总结:
1、节点的定义中
1 typedef int Type; 2 3 typedef struct BSTreeNode{ 4 Type key; // 关键字(键值) 5 struct BSTreeNode *left; // 左孩子 6 struct BSTreeNode *right; // 右孩子 7 struct BSTreeNode *parent; // 父结点 8 }Node, *BSTree;
a. 左右孩子用指针定义,类似于int *left,因为结构体本身就是一种自定义类型,struct BSTreeNode看成系统的类型int不过分。
b. 用了typedef重定义类型,给struct BSTreeNode起了两个别名Node和*BSTree。
c. 其中的*BSTree起始就相当于 typedef int *m中的*m。
2、创建节点中
1 static Node* create_bstree_node(Type key, Node *parent, Node *left, Node* right) 2 { 3 Node* p; 4 5 if ((p = (Node *)malloc(sizeof(Node))) == NULL) 6 return NULL; 7 p->key = key; 8 p->left = left; 9 p->right = right; 10 p->parent = parent; 11 12 return p; 13 }
a. 用了malloc函数动态分配内存,p = (Node *)malloc(sizeof(Node)),很定式的写法,malloc出来的是指针,所以强转成指针,动态申请的空间大小,自然是sizeof(Node),Node是我们定义的结构体的别名
b. if ((p = (Node *)malloc(sizeof(Node))) == NULL) 用动态分配的返回值是否为NULL来判断是否动态创建成功,c++的动态创建用的是new关键字。
c. “没有”在C语言中表示为NULL,在JAVA中表示为null。
d. 在c和c++中,对象是指针的话,用->来找成员,例如p->key = key;,如果对象是非指针的话,用的是.(点号)来找成员。
3、在前序遍历中
1 void preorder_bstree(BSTree tree) 2 { 3 if(tree != NULL) 4 { 5 printf("%d ", tree->key); 6 preorder_bstree(tree->left); 7 preorder_bstree(tree->right); 8 } 9 }
a、非空的节点我们才有必要输出它的值和找左右孩子才有意义
4、在递归查找中
1 Node* bstree_search(BSTree x, Type key) 2 { 3 if (x==NULL || x->key==key) 4 return x; 5 if (key < x->key) 6 return bstree_search(x->left, key); 7 else 8 return bstree_search(x->right, key); 9 }
要查的树的根节点是x,我们要查的键值是key,返回的节点是我们找到的那个值为key的节点,没找到返回NULL
a. 第三行,空树就没有必要查了吧,这里也包括反复递归的过程中没有找到的而到NULL节点的情况,直接返回NULL就可以了,
b. 如果找到key的话,直接返回那个节点即可
c. 如果当前查找的key的值小于根的值,就往左子树跑,否则往右子树跑。递归调用。
5、查找最大值的代码
1 Node* bstree_maximum(BSTree tree) 2 { 3 if (tree == NULL) 4 return NULL; 5 while(tree->right != NULL) 6 tree = tree->right; 7 return tree; 8 }
要查找的是树的根节点tree,返回的是值最大的那个节点
a. 如果树为空的话,直接返回即可。
b. 如果右子树存在的情况下,也就是右子树不为空的情况就往右子树找。
c. 找最小值和找最大值几乎一模一样,一个往左一个往右。
6、查找前驱节点
节点的前驱:是该节点的左子树中的最大节点。
1 Node* bstree_predecessor(Node *x) 2 { 3 // 如果x存在左孩子,则"x的前驱结点"为 "以其左孩子为根的子树的最大结点"。 4 if (x->left != NULL) 5 return bstree_maximum(x->left); 6 7 // 如果x没有左孩子。则x有以下两种可能: 8 // (01) x是"一个右孩子",则"x的前驱结点"为 "它的父结点"。 9 // (01) x是"一个左孩子",则查找"x的最低的父结点,并且该父结点要具有右孩子",找到的这个"最低的父结点"就是"x的前驱结点"。 10 Node* y = x->parent; 11 while ((y!=NULL) && (x==y->left)) 12 { 13 x = y; 14 y = y->parent; 15 } 16 17 return y; 18 }


5这个节点有左孩子,所以前驱就是子树中的最大节点4.
7这个节点没有左孩子且7是右孩子,所以前驱就是父亲节点6.
2这个节点没有左孩子且2这个节点是左孩子,找到第一个父亲的祖先有右孩子的1就是它的前驱。同理节点8也是。
a. 第一种情况用到了上面的求树中最大值的算法
b. 节点y是x的父亲,去找父亲第一个有右孩子的祖先,就是x的前驱。因为走到节点5和1的时候发现5不是节点1的左孩子,就停止循环,故找到节点1.
c. 第十行的那个代码包括第二种和第三种情况,比如找7(第二种情况)的前驱时候,把这段代码代进去算就会发现可以用。因为不是左孩子,没进循环,直接返回父亲了。
7、查找后继节点
节点的后继:是该节点的右子树中的最小节点。
1 Node* bstree_successor(Node *x) 2 { 3 // 如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。 4 if (x->right != NULL) 5 return bstree_minimum(x->right); 6 7 // 如果x没有右孩子。则x有以下两种可能: 8 // (01) x是"一个左孩子",则"x的后继结点"为 "它的父结点"。 9 // (02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。 10 Node* y = x->parent; 11 while ((y!=NULL) && (x==y->right)) 12 { 13 x = y; 14 y = y->parent; 15 } 16 17 return y; 18 }
查找的是节点x的后继节点,返回的就是后继节点的那个点

如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。
比如节点1的后继节点就是2
如果x没有右孩子。则x有以下两种可能:
(01) x是"一个左孩子",则"x的后继结点"为 "它的父结点"。
比如说节点2,本身没有右孩子,自己又是左孩子,所以后继就是父亲,因为左子树总比根小
(02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。
比如说节点5,没有右孩子,自己又是右孩子,那就找父亲里面第一个有左孩子的就好,节点5的后继就是6.
其实5没有右孩子,说明5是这颗子树里面最大的,而这样的结构只有变成左子树才能找到比它大的,所以找到的第一个左子树的分叉6就是后继。
a. 代码第三行就是有右孩子直接右孩子那棵树返回最小的
b. 第十行就是后面两种情况,去找左子树来找比它大的做它的后继,因为根比左子树大,都是找到第一个比它大的就是。
8、插入节点
1 static Node* bstree_insert(BSTree tree, Node *z) 2 { 3 Node *y = NULL; 4 Node *x = tree; 5 6 // 查找z的插入位置 7 while (x != NULL) 8 { 9 y = x; 10 if (z->key < x->key) 11 x = x->left; 12 else 13 x = x->right; 14 } 15 16 z->parent = y; 17 if (y==NULL) 18 tree = z; 19 else if (z->key < y->key) 20 y->left = z; 21 else 22 y->right = z; 23 24 return tree; 25 } 26 27 Node* insert_bstree(BSTree tree, Type key) 28 { 29 Node *z; // 新建结点 30 31 // 如果新建结点失败,则返回。 32 if ((z=create_bstree_node(key, NULL, NULL, NULL)) == NULL) 33 return tree; 34 35 return bstree_insert(tree, z); 36 }
bstree_insert(tree, z)是内部函数,它的作用是:将结点(z)插入到二叉树(tree)中,并返回插入节点后的根节点。
insert_bstree(tree, key)是对外接口,它的作用是:在树中新增节点,key是节点的值;并返回插入节点后的根节点。
注:本文实现的二叉查找树是允许插入相同键值的节点的!
insert_bstree(tree, key)中
a. 32行的if ((z=create_bstree_node(key, NULL, NULL, NULL)) == NULL)是节点没有创建成功返回NULL
b. 这里新建节点直接Node *z;,相当于int a;这样做是只声明了并没有给它内存空间,所以在create函数里面用了malloc给它内存空间。这里和JAVA一样。
bstree_insert(tree, z)中
a. 因为是在链上操作,所以插入操作需要需要记录两个点,把插入的点插到中间就好了,防止断链。
b. 第6到14行代码,相等插在右子树上,6-14行就是找到那个为NULL的插入的位置
c. 16-24行插入操作,如果树为空直接插入z做根节点,找到是插入左边还是右边,因为节点是插入在y的子树上的,这里的y是插入位置的父亲
9、删除操作
二叉树的删除可以算是二叉树最为复杂的操作,删除的时候要考虑到很多种情况:
1.被删除的节点是叶子节点
2.被删除的节点只有左孩子节点
3.被删除的节点只有右孩子节点
4.被删除的有两个孩子节点
所以在删除的时候,这4种情况都必须考虑进去,并且这4中情况之下,还会有细的划分,下面就细说怎么删除。
bstree_delete(tree, z)是内部函数,它的作用是:删除二叉树(tree)中的节点(z),并返回删除节点后的根节点。
delete_bstree(tree, key)是对外接口,它的作用是:在树中查找键值为key的节点,找到的话就删除该节点;并返回删除节点后的根节点。
1 //tree是树的根节点,z是我们要删除的那个节点 2 static Node* bstree_delete(BSTree tree, Node *z) 3 { 4 Node *x=NULL; 5 Node *y=NULL; 6 7 //左孩子或者右孩子为空的情况,就是只有一个孩子或者两个孩子都没有的情况 8 //直接将z赋值给y 9 if ((z->left == NULL) || (z->right == NULL) ) 10 y = z; 11 else//两个孩子都存在的情况下,直接找z的后继 12 y = bstree_successor(z); 13 14 //那个后继节点的左子树不为空,将左子树给x 15 if (y->left != NULL) 16 x = y->left; 17 else//那个后继节点的左子树为空的话,那x就是y的右子树 18 x = y->right; 19 20 //x节点不为空的情况,将y的父亲赋值给x的父亲,标准的删除操作,这是为了删除后继节点y 21 //因为删除的思路就是用后继节点来替换删除的那个节点 22 if (x != NULL) 23 x->parent = y->parent; 24 25 //如果y没有父亲了,y是比较靠近树根的点 26 if (y->parent == NULL) 27 tree = x; 28 else if (y == y->parent->left) 29 y->parent->left = x; 30 else 31 y->parent->right = x; 32 33 if (y != z) 34 z->key = y->key; 35 36 if (y!=NULL) 37 free(y); 38 39 return tree; 40 } 41 42 Node* delete_bstree(BSTree tree, Type key) 43 { 44 Node *z, *node; 45 46 if ((z = bstree_search(tree, key)) != NULL) 47 tree = bstree_delete(tree, z); 48 49 return tree; 50 }
在delete_bstree(tree, key)中
a. 先调用查找函数找这个节点,如果找到了就删了这个节点,z是这个找到的节点
在bstree_delete(tree, z)中
a. 第9行的y = bstree_successor(z);是找到z的后继y,
10、打印二叉树
1 void print_bstree(BSTree tree, Type key, int direction) 2 { 3 if(tree != NULL) 4 { 5 if(direction==0) // tree是根节点 6 printf("%2d is root ", tree->key); 7 else // tree是分支节点 8 printf("%2d is %2d's %6s child ", tree->key, key, direction==1?"right" : "left"); 9 10 print_bstree(tree->left, tree->key, -1); 11 print_bstree(tree->right,tree->key, 1); 12 } 13 }
print_bstree(tree, key, direction)的作用是打印整颗二叉树(tree)。其中,tree是二叉树节点,key是二叉树的键值,而direction表示该节点的类型:
direction为 0,表示该节点是根节点;
direction为-1,表示该节点是它的父结点的左孩子;
direction为 1,表示该节点是它的父结点的右孩子。
a. 这样子用一个 中间变量direction可以指明每一个点是左还是右还是根。
11、 销毁二叉树
1 void destroy_bstree(BSTree tree) 2 { 3 if (tree==NULL) 4 return ; 5 6 if (tree->left != NULL) 7 destroy_bstree(tree->left); 8 if (tree->right != NULL) 9 destroy_bstree(tree->right); 10 11 free(tree); 12 }
a. 如果tree为空,不用销毁,在销毁了左右之后再来销毁根
b. 销毁的核心操作就是free,这个操作是释放指针所指的内存空间,按JAVA里面的说法就是释放堆空间,因为栈空间系统会自动维护的,虽然JAVA里面没有指针,但是引用本质也是指针。
概要
本章先对二叉树的相关理论知识进行介绍,然后给出C语言的详细实现。关于二叉树的学习,需要说明的是:它并不难,不仅不难,而且它非常简单。初次接触树的时候,我也觉得它似乎很难;而之所产生这种感觉主要是由于二叉树有一大堆陌生的概念、性质等内容。而当我真正的实现了二叉树再回过头来看它的相关概念和性质的时候,觉得原来它是如此的简单!因此,建议在学习二叉树的时候:先对二叉树基本的概念、性质有个基本了解,遇到难懂的知识点,可以画图来帮助理解;在有个基本的概念之后,再亲自动手实现二叉查找树(这一点至关重要!);最后再回过头来总结一下二叉树的理论知识时,你会发现——它的确很简单!在代码实践中,我以"二叉查找树,而不是单纯的二叉树"为例子进行说明,单纯的二叉树非常简单,实际使用很少。况且掌握了二叉查找树,二叉树也就自然掌握了。
本篇实现的二叉查找树是C语言版的,后面章节再分别给出C++和Java版本的实现。您可以根据自己熟悉的语言进行实践学习!
请务必深刻理解、实践并掌握"二叉查找树"!它是后面学习AVL树、伸展树、红黑树等相关树结构的基石!
目录
1. 树的介绍
2. 二叉树的介绍
3. 二叉查找树的C实现
4. 二叉查找树的C测试程序
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3576328.html
更多内容: 数据结构与算法系列 目录
(01). 二叉查找树(一)之 图文解析 和 C语言的实现
(02). 二叉查找树(二)之 C++的实现
(03). 二叉查找树(三)之 Java的实现
树的介绍
1. 树的定义
树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。
把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
(01) 每个节点有零个或多个子节点;
(02) 没有父节点的节点称为根节点;
(03) 每一个非根节点有且只有一个父节点;
(04) 除了根节点外,每个子节点可以分为多个不相交的子树。
2. 树的基本术语
若一个结点有子树,那么该结点称为子树根的"双亲",子树的根是该结点的"孩子"。有相同双亲的结点互为"兄弟"。一个结点的所有子树上的任何结点都是该结点的后裔。从根结点到某个结点的路径上的所有结点都是该结点的祖先。
结点的度:结点拥有的子树的数目。
叶子:度为零的结点。
分支结点:度不为零的结点。
树的度:树中结点的最大的度。
层次:根结点的层次为1,其余结点的层次等于该结点的双亲结点的层次加1。
树的高度:树中结点的最大层次。
无序树:如果树中结点的各子树之间的次序是不重要的,可以交换位置。
有序树:如果树中结点的各子树之间的次序是重要的, 不可以交换位置。
森林:0个或多个不相交的树组成。对森林加上一个根,森林即成为树;删去根,树即成为森林。
二叉树的介绍
1. 二叉树的定义
二叉树是每个节点最多有两个子树的树结构。它有五种基本形态:二叉树可以是空集;根可以有空的左子树或右子树;或者左、右子树皆为空。
2. 二叉树的性质
二叉树有以下几个性质:TODO(上标和下标)
性质1:二叉树第i层上的结点数目最多为 2{i-1} (i≥1)。
性质2:深度为k的二叉树至多有2{k}-1个结点(k≥1)。
性质3:包含n个结点的二叉树的高度至少为log2 (n+1)。
性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1。
2.1 性质1:二叉树第i层上的结点数目最多为 2{i-1} (i≥1)
证明:下面用"数学归纳法"进行证明。
(01) 当i=1时,第i层的节点数目为2{i-1}=2{0}=1。因为第1层上只有一个根结点,所以命题成立。
(02) 假设当i>1,第i层的节点数目为2{i-1}。这个是根据(01)推断出来的!
下面根据这个假设,推断出"第(i+1)层的节点数目为2{i}"即可。
由于二叉树的每个结点至多有两个孩子,故"第(i+1)层上的结点数目" 最多是 "第i层的结点数目的2倍"。即,第(i+1)层上的结点数目最大值=2×2{i-1}=2{i}。
故假设成立,原命题得证!
2.2 性质2:深度为k的二叉树至多有2{k}-1个结点(k≥1)
证明:在具有相同深度的二叉树中,当每一层都含有最大结点数时,其树中结点数最多。利用"性质1"可知,深度为k的二叉树的结点数至多为:
20+21+…+2k-1=2k-1
故原命题得证!
2.3 性质3:包含n个结点的二叉树的高度至少为log2 (n+1)
证明:根据"性质2"可知,高度为h的二叉树最多有2{h}–1个结点。反之,对于包含n个节点的二叉树的高度至少为log2(n+1)。
2.4 性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1
证明:因为二叉树中所有结点的度数均不大于2,所以结点总数(记为n)="0度结点数(n0)" + "1度结点数(n1)" + "2度结点数(n2)"。由此,得到等式一。
(等式一) n=n0+n1+n2
另一方面,0度结点没有孩子,1度结点有一个孩子,2度结点有两个孩子,故二叉树中孩子结点总数是:n1+2n2。此外,只有根不是任何结点的孩子。故二叉树中的结点总数又可表示为等式二。
(等式二) n=n1+2n2+1
由(等式一)和(等式二)计算得到:n0=n2+1。原命题得证!
3. 满二叉树,完全二叉树和二叉查找树
3.1 满二叉树
定义:高度为h,并且由2{h} –1个结点的二叉树,被称为满二叉树。
3.2 完全二叉树
定义:一棵二叉树中,只有最下面两层结点的度可以小于2,并且最下一层的叶结点集中在靠左的若干位置上。这样的二叉树称为完全二叉树。
特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。
3.3 二叉查找树
定义:二叉查找树(Binary Search Tree),又被称为二叉搜索树。设x为二叉查找树中的一个结点,x节点包含关键字key,节点x的key值记为key[x]。如果y是x的左子树中的一个结点,则key[y] <= key[x];如果y是x的右子树的一个结点,则key[y] >= key[x]。
在二叉查找树中:
(01) 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(02) 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(03) 任意节点的左、右子树也分别为二叉查找树。
(04) 没有键值相等的节点(no duplicate nodes)。
在实际应用中,二叉查找树的使用比较多。下面,用C语言实现二叉查找树。
二叉查找树的C实现
1. 节点定义
1.1 节点定义
typedef int Type;
typedef struct BSTreeNode{
Type key; // 关键字(键值)
struct BSTreeNode *left; // 左孩子
struct BSTreeNode *right; // 右孩子
struct BSTreeNode *parent; // 父结点
}Node, *BSTree;
二叉查找树的节点包含的基本信息:
(01) key -- 它是关键字,是用来对二叉查找树的节点进行排序的。
(02) left -- 它指向当前节点的左孩子。
(03) right -- 它指向当前节点的右孩子。
(04) parent -- 它指向当前节点的父结点。
1.2 创建节点
创建节点的代码
static Node* create_bstree_node(Type key, Node *parent, Node *left, Node* right)
{
Node* p;
if ((p = (Node *)malloc(sizeof(Node))) == NULL)
return NULL;
p->key = key;
p->left = left;
p->right = right;
p->parent = parent;
return p;
}
2 遍历
这里讲解前序遍历、中序遍历、后序遍历3种方式。
2.1 前序遍历
若二叉树非空,则执行以下操作:
(01) 访问根结点;
(02) 先序遍历左子树;
(03) 先序遍历右子树。
前序遍历代码
void preorder_bstree(BSTree tree)
{
if(tree != NULL)
{
printf("%d ", tree->key);
preorder_bstree(tree->left);
preorder_bstree(tree->right);
}
}
2.2 中序遍历
若二叉树非空,则执行以下操作:
(01) 中序遍历左子树;
(02) 访问根结点;
(03) 中序遍历右子树。
中序遍历代码
void inorder_bstree(BSTree tree)
{
if(tree != NULL)
{
inorder_bstree(tree->left);
printf("%d ", tree->key);
inorder_bstree(tree->right);
}
}
2.3 后序遍历
若二叉树非空,则执行以下操作:
(01) 后序遍历左子树;
(02) 后序遍历右子树;
(03) 访问根结点。
后序遍历代码
void postorder_bstree(BSTree tree)
{
if(tree != NULL)
{
postorder_bstree(tree->left);
postorder_bstree(tree->right);
printf("%d ", tree->key);
}
}
下面通过例子对这些遍历方式进行介绍。
对于上面的二叉树而言,
(01) 前序遍历结果: 3 1 2 5 4 6
(02) 中序遍历结果: 1 2 3 4 5 6
(03) 后序遍历结果: 2 1 4 6 5 3
3. 查找
递归版本的代码
Node* bstree_search(BSTree x, Type key)
{
if (x==NULL || x->key==key)
return x;
if (key < x->key)
return bstree_search(x->left, key);
else
return bstree_search(x->right, key);
}
非递归版本的代码
Node* iterative_bstree_search(BSTree x, Type key)
{
while ((x!=NULL) && (x->key!=key))
{
if (key < x->key)
x = x->left;
else
x = x->right;
}
return x;
}
4. 最大值和最小值
查找最大值的代码
Node* bstree_maximum(BSTree tree)
{
if (tree == NULL)
return NULL;
while(tree->right != NULL)
tree = tree->right;
return tree;
}
查找最小值的代码
Node* bstree_minimum(BSTree tree)
{
if (tree == NULL)
return NULL;
while(tree->left != NULL)
tree = tree->left;
return tree;
}
5. 前驱和后继
节点的前驱:是该节点的左子树中的最大节点。
节点的后继:是该节点的右子树中的最小节点。
查找前驱节点的代码
Node* bstree_predecessor(Node *x)
{
// 如果x存在左孩子,则"x的前驱结点"为 "以其左孩子为根的子树的最大结点"。
if (x->left != NULL)
return bstree_maximum(x->left);
// 如果x没有左孩子。则x有以下两种可能:
// (01) x是"一个右孩子",则"x的前驱结点"为 "它的父结点"。
// (01) x是"一个左孩子",则查找"x的最低的父结点,并且该父结点要具有右孩子",找到的这个"最低的父结点"就是"x的前驱结点"。
Node* y = x->parent;
while ((y!=NULL) && (x==y->left))
{
x = y;
y = y->parent;
}
return y;
}
查找后继节点的代码
Node* bstree_successor(Node *x)
{
// 如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。
if (x->right != NULL)
return bstree_minimum(x->right);
// 如果x没有右孩子。则x有以下两种可能:
// (01) x是"一个左孩子",则"x的后继结点"为 "它的父结点"。
// (02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。
Node* y = x->parent;
while ((y!=NULL) && (x==y->right))
{
x = y;
y = y->parent;
}
return y;
}
6. 插入
插入节点的代码
static Node* bstree_insert(BSTree tree, Node *z)
{
Node *y = NULL;
Node *x = tree;
// 查找z的插入位置
while (x != NULL)
{
y = x;
if (z->key < x->key)
x = x->left;
else
x = x->right;
}
z->parent = y;
if (y==NULL)
tree = z;
else if (z->key < y->key)
y->left = z;
else
y->right = z;
return tree;
}
Node* insert_bstree(BSTree tree, Type key)
{
Node *z; // 新建结点
// 如果新建结点失败,则返回。
if ((z=create_bstree_node(key, NULL, NULL, NULL)) == NULL)
return tree;
return bstree_insert(tree, z);
}
bstree_insert(tree, z)是内部函数,它的作用是:将结点(z)插入到二叉树(tree)中,并返回插入节点后的根节点。
insert_bstree(tree, key)是对外接口,它的作用是:在树中新增节点,key是节点的值;并返回插入节点后的根节点。
注:本文实现的二叉查找树是允许插入相同键值的节点的!若用户不希望插入相同键值的节点,将bstree_insert()修改为以下代码即可。
static Node* bstree_insert(BSTree tree, Node *z)
{
Node *y = NULL;
Node *x = tree;
// 查找z的插入位置
while (x != NULL)
{
y = x;
if (z->key < x->key)
x = x->left;
else if (z->key > x->key)
x = x->right;
else
{
free(z); // 释放之前分配的系统。
return tree;
}
}
z->parent = y;
if (y==NULL)
tree = z;
else if (z->key < y->key)
y->left = z;
else
y->right = z;
return tree;
}
7. 删除
删除节点的代码
static Node* bstree_delete(BSTree tree, Node *z)
{
Node *x=NULL;
Node *y=NULL;
if ((z->left == NULL) || (z->right == NULL) )
y = z;
else
y = bstree_successor(z);
if (y->left != NULL)
x = y->left;
else
x = y->right;
if (x != NULL)
x->parent = y->parent;
if (y->parent == NULL)
tree = x;
else if (y == y->parent->left)
y->parent->left = x;
else
y->parent->right = x;
if (y != z)
z->key = y->key;
if (y!=NULL)
free(y);
return tree;
}
Node* delete_bstree(BSTree tree, Type key)
{
Node *z, *node;
if ((z = bstree_search(tree, key)) != NULL)
tree = bstree_delete(tree, z);
return tree;
}
bstree_delete(tree, z)是内部函数,它的作用是:删除二叉树(tree)中的节点(z),并返回删除节点后的根节点。
delete_bstree(tree, key)是对外接口,它的作用是:在树中查找键值为key的节点,找到的话就删除该节点;并返回删除节点后的根节点。
8. 打印
打印二叉树的代码
void print_bstree(BSTree tree, Type key, int direction)
{
if(tree != NULL)
{
if(direction==0) // tree是根节点
printf("%2d is root
", tree->key);
else // tree是分支节点
printf("%2d is %2d's %6s child
", tree->key, key, direction==1?"right" : "left");
print_bstree(tree->left, tree->key, -1);
print_bstree(tree->right,tree->key, 1);
}
}
print_bstree(tree, key, direction)的作用是打印整颗二叉树(tree)。其中,tree是二叉树节点,key是二叉树的键值,而direction表示该节点的类型:
direction为 0,表示该节点是根节点;
direction为-1,表示该节点是它的父结点的左孩子;
direction为 1,表示该节点是它的父结点的右孩子。
9. 销毁二叉树
销毁二叉树的代码
void destroy_bstree(BSTree tree)
{
if (tree==NULL)
return ;
if (tree->left != NULL)
destroy_bstree(tree->left);
if (tree->right != NULL)
destroy_bstree(tree->right);
free(tree);
}
完整的实现代码
二叉查找树的头文件(bstree.h)
1 #ifndef _BINARY_SEARCH_TREE_H_
2 #define _BINARY_SEARCH_TREE_H_
3
4 typedef int Type;
5
6 typedef struct BSTreeNode{
7 Type key; // 关键字(键值)
8 struct BSTreeNode *left; // 左孩子
9 struct BSTreeNode *right; // 右孩子
10 struct BSTreeNode *parent; // 父结点
11 }Node, *BSTree;
12
13 // 前序遍历"二叉树"
14 void preorder_bstree(BSTree tree);
15 // 中序遍历"二叉树"
16 void inorder_bstree(BSTree tree);
17 // 后序遍历"二叉树"
18 void postorder_bstree(BSTree tree);
19
20 // (递归实现)查找"二叉树x"中键值为key的节点
21 Node* bstree_search(BSTree x, Type key);
22 // (非递归实现)查找"二叉树x"中键值为key的节点
23 Node* iterative_bstree_search(BSTree x, Type key);
24
25 // 查找最小结点:返回tree为根结点的二叉树的最小结点。
26 Node* bstree_minimum(BSTree tree);
27 // 查找最大结点:返回tree为根结点的二叉树的最大结点。
28 Node* bstree_maximum(BSTree tree);
29
30 // 找结点(x)的后继结点。即,查找"二叉树中数据值大于该结点"的"最小结点"。
31 Node* bstree_successor(Node *x);
32 // 找结点(x)的前驱结点。即,查找"二叉树中数据值小于该结点"的"最大结点"。
33 Node* bstree_predecessor(Node *x);
34
35 // 将结点插入到二叉树中,并返回根节点
36 Node* insert_bstree(BSTree tree, Type key);
37
38 // 删除结点(key为节点的值),并返回根节点
39 Node* delete_bstree(BSTree tree, Type key);
40
41 // 销毁二叉树
42 void destroy_bstree(BSTree tree);
43
44 // 打印二叉树
45 void print_bstree(BSTree tree, Type key, int direction);
46
47 #endif
二叉查找树的实现文件(bstree.c)
1 /**
2 * 二叉搜索树(C语言): C语言实现的二叉搜索树。
3 *
4 * @author skywang
5 * @date 2013/11/07
6 */
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include "bstree.h"
11
12
13 /*
14 * 前序遍历"二叉树"
15 */
16 void preorder_bstree(BSTree tree)
17 {
18 if(tree != NULL)
19 {
20 printf("%d ", tree->key);
21 preorder_bstree(tree->left);
22 preorder_bstree(tree->right);
23 }
24 }
25
26 /*
27 * 中序遍历"二叉树"
28 */
29 void inorder_bstree(BSTree tree)
30 {
31 if(tree != NULL)
32 {
33 inorder_bstree(tree->left);
34 printf("%d ", tree->key);
35 inorder_bstree(tree->right);
36 }
37 }
38
39 /*
40 * 后序遍历"二叉树"
41 */
42 void postorder_bstree(BSTree tree)
43 {
44 if(tree != NULL)
45 {
46 postorder_bstree(tree->left);
47 postorder_bstree(tree->right);
48 printf("%d ", tree->key);
49 }
50 }
51
52 /*
53 * (递归实现)查找"二叉树x"中键值为key的节点
54 */
55 Node* bstree_search(BSTree x, Type key)
56 {
57 if (x==NULL || x->key==key)
58 return x;
59
60 if (key < x->key)
61 return bstree_search(x->left, key);
62 else
63 return bstree_search(x->right, key);
64 }
65
66 /*
67 * (非递归实现)查找"二叉树x"中键值为key的节点
68 */
69 Node* iterative_bstree_search(BSTree x, Type key)
70 {
71 while ((x!=NULL) && (x->key!=key))
72 {
73 if (key < x->key)
74 x = x->left;
75 else
76 x = x->right;
77 }
78
79 return x;
80 }
81
82 /*
83 * 查找最小结点:返回tree为根结点的二叉树的最小结点。
84 */
85 Node* bstree_minimum(BSTree tree)
86 {
87 if (tree == NULL)
88 return NULL;
89
90 while(tree->left != NULL)
91 tree = tree->left;
92 return tree;
93 }
94
95 /*
96 * 查找最大结点:返回tree为根结点的二叉树的最大结点。
97 */
98 Node* bstree_maximum(BSTree tree)
99 {
100 if (tree == NULL)
101 return NULL;
102
103 while(tree->right != NULL)
104 tree = tree->right;
105 return tree;
106 }
107
108 /*
109 * 找结点(x)的后继结点。即,查找"二叉树中数据值大于该结点"的"最小结点"。
110 */
111 Node* bstree_successor(Node *x)
112 {
113 // 如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。
114 if (x->right != NULL)
115 return bstree_minimum(x->right);
116
117 // 如果x没有右孩子。则x有以下两种可能:
118 // (01) x是"一个左孩子",则"x的后继结点"为 "它的父结点"。
119 // (02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。
120 Node* y = x->parent;
121 while ((y!=NULL) && (x==y->right))
122 {
123 x = y;
124 y = y->parent;
125 }
126
127 return y;
128 }
129
130 /*
131 * 找结点(x)的前驱结点。即,查找"二叉树中数据值小于该结点"的"最大结点"。
132 */
133 Node* bstree_predecessor(Node *x)
134 {
135 // 如果x存在左孩子,则"x的前驱结点"为 "以其左孩子为根的子树的最大结点"。
136 if (x->left != NULL)
137 return bstree_maximum(x->left);
138
139 // 如果x没有左孩子。则x有以下两种可能:
140 // (01) x是"一个右孩子",则"x的前驱结点"为 "它的父结点"。
141 // (01) x是"一个左孩子",则查找"x的最低的父结点,并且该父结点要具有右孩子",找到的这个"最低的父结点"就是"x的前驱结点"。
142 Node* y = x->parent;
143 while ((y!=NULL) && (x==y->left))
144 {
145 x = y;
146 y = y->parent;
147 }
148
149 return y;
150 }
151
152 /*
153 * 创建并返回二叉树结点。
154 *
155 * 参数说明:
156 * key 是键值。
157 * parent 是父结点。
158 * left 是左孩子。
159 * right 是右孩子。
160 */
161 static Node* create_bstree_node(Type key, Node *parent, Node *left, Node* right)
162 {
163 Node* p;
164
165 if ((p = (Node *)malloc(sizeof(Node))) == NULL)
166 return NULL;
167 p->key = key;
168 p->left = left;
169 p->right = right;
170 p->parent = parent;
171
172 return p;
173 }
174
175 /*
176 * 将结点插入到二叉树中
177 *
178 * 参数说明:
179 * tree 二叉树的根结点
180 * z 插入的结点
181 * 返回值:
182 * 根节点
183 */
184 static Node* bstree_insert(BSTree tree, Node *z)
185 {
186 Node *y = NULL;
187 Node *x = tree;
188
189 // 查找z的插入位置
190 while (x != NULL)
191 {
192 y = x;
193 if (z->key < x->key)
194 x = x->left;
195 else
196 x = x->right;
197 }
198
199 z->parent = y;
200 if (y==NULL)
201 tree = z;
202 else if (z->key < y->key)
203 y->left = z;
204 else
205 y->right = z;
206
207 return tree;
208 }
209
210 /*
211 * 新建结点(key),并将其插入到二叉树中
212 *
213 * 参数说明:
214 * tree 二叉树的根结点
215 * key 插入结点的键值
216 * 返回值:
217 * 根节点
218 */
219 Node* insert_bstree(BSTree tree, Type key)
220 {
221 Node *z; // 新建结点
222
223 // 如果新建结点失败,则返回。
224 if ((z=create_bstree_node(key, NULL, NULL, NULL)) == NULL)
225 return tree;
226
227 return bstree_insert(tree, z);
228 }
229
230 /*
231 * 删除结点(z),并返回根节点
232 *
233 * 参数说明:
234 * tree 二叉树的根结点
235 * z 删除的结点
236 * 返回值:
237 * 根节点
238 */
239 static Node* bstree_delete(BSTree tree, Node *z)
240 {
241 Node *x=NULL;
242 Node *y=NULL;
243
244 if ((z->left == NULL) || (z->right == NULL) )
245 y = z;
246 else
247 y = bstree_successor(z);
248
249 if (y->left != NULL)
250 x = y->left;
251 else
252 x = y->right;
253
254 if (x != NULL)
255 x->parent = y->parent;
256
257 if (y->parent == NULL)
258 tree = x;
259 else if (y == y->parent->left)
260 y->parent->left = x;
261 else
262 y->parent->right = x;
263
264 if (y != z)
265 z->key = y->key;
266
267 if (y!=NULL)
268 free(y);
269
270 return tree;
271 }
272
273 /*
274 * 删除结点(key为节点的键值),并返回根节点
275 *
276 * 参数说明:
277 * tree 二叉树的根结点
278 * z 删除的结点
279 * 返回值:
280 * 根节点
281 */
282 Node* delete_bstree(BSTree tree, Type key)
283 {
284 Node *z, *node;
285
286 if ((z = bstree_search(tree, key)) != NULL)
287 tree = bstree_delete(tree, z);
288
289 return tree;
290 }
291
292 /*
293 * 销毁二叉树
294 */
295 void destroy_bstree(BSTree tree)
296 {
297 if (tree==NULL)
298 return ;
299
300 if (tree->left != NULL)
301 destroy_bstree(tree->left);
302 if (tree->right != NULL)
303 destroy_bstree(tree->right);
304
305 free(tree);
306 }
307
308 /*
309 * 打印"二叉树"
310 *
311 * tree -- 二叉树的节点
312 * key -- 节点的键值
313 * direction -- 0,表示该节点是根节点;
314 * -1,表示该节点是它的父结点的左孩子;
315 * 1,表示该节点是它的父结点的右孩子。
316 */
317 void print_bstree(BSTree tree, Type key, int direction)
318 {
319 if(tree != NULL)
320 {
321 if(direction==0) // tree是根节点
322 printf("%2d is root
", tree->key);
323 else // tree是分支节点
324 printf("%2d is %2d's %6s child
", tree->key, key, direction==1?"right" : "left");
325
326 print_bstree(tree->left, tree->key, -1);
327 print_bstree(tree->right,tree->key, 1);
328 }
329 }
二叉查找树的测试程序(btree_test.c)
1 /**
2 * C 语言: 二叉查找树
3 *
4 * @author skywang
5 * @date 2013/11/07
6 */
7
8 #include <stdio.h>
9 #include "bstree.h"
10
11 static int arr[]= {1,5,4,3,2,6};
12 #define TBL_SIZE(a) ( (sizeof(a)) / (sizeof(a[0])) )
13
14 void main()
15 {
16 int i, ilen;
17 BSTree root=NULL;
18
19 printf("== 依次添加: ");
20 ilen = TBL_SIZE(arr);
21 for(i=0; i<ilen; i++)
22 {
23 printf("%d ", arr[i]);
24 root = insert_bstree(root, arr[i]);
25 }
26
27 printf("
== 前序遍历: ");
28 preorder_bstree(root);
29
30 printf("
== 中序遍历: ");
31 inorder_bstree(root);
32
33 printf("
== 后序遍历: ");
34 postorder_bstree(root);
35 printf("
");
36
37 printf("== 最小值: %d
", bstree_minimum(root)->key);
38 printf("== 最大值: %d
", bstree_maximum(root)->key);
39 printf("== 树的详细信息:
");
40 print_bstree(root, root->key, 0);
41
42 printf("
== 删除根节点: %d", arr[3]);
43 root = delete_bstree(root, arr[3]);
44
45 printf("
== 中序遍历: ");
46 inorder_bstree(root);
47 printf("
");
48
49 // 销毁二叉树
50 destroy_bstree(root);
51 }
二叉查找树的C测试程序
上面的btree_test.c是二叉查找树的测试程序,它的运行结果如下:
== 依次添加: 1 5 4 3 2 6 == 前序遍历: 1 5 4 3 2 6 == 中序遍历: 1 2 3 4 5 6 == 后序遍历: 2 3 4 6 5 1 == 最小值: 1 == 最大值: 6 == 树的详细信息: 1 is root 5 is 1's right child 4 is 5's left child 3 is 4's left child 2 is 3's left child 6 is 5's right child == 删除根节点: 3 == 中序遍历: 1 2 4 5 6
下面对测试程序的流程进行分析!
(01) 新建"二叉查找树"root。
(02) 向二叉查找树中依次插入1,5,4,3,2,6 。如下图所示:
(03) 打印树的信息
插入1,5,4,3,2,6之后,得到的二叉查找树如下:
前序遍历结果: 1 5 4 3 2 6
中序遍历结果: 1 2 3 4 5 6
后序遍历结果: 2 3 4 6 5 1
最小值是1,而最大值是6。
(04) 删除节点3。如下图所示:
(05) 重新遍历该二叉查找树。
中序遍历结果: 1 2 4 5 6









