二叉查找树是一种特殊性质的二叉树,该树中的任何一个节点,它的左子树(若存在)的元素值小于节点的元素值,右子树(若存在)的元素值大于节点的元素值。
实现了二叉树查找树的实现以及基本操作,包括查找、插入、删除、初始化、释放等。
源码下载地址:http://download.csdn.net/detail/mcu_tian/9548788
二叉查找树节点的结构定义
相比二叉树,二叉查找树的元素结构定义稍微有点改动,即在元素结构体中添加了frequency成员,用来记录重复的元素。
当为新的元素的时候,frequency成员为1,每当有重复的成员函数插入到树种的时候,frequency加1。具体如下:
typedef char TreeElementType;
struct TreeNode
{
TreeElementType element;
unsigned int frequency;//当有重复的元素的时候的计数
struct TreeNode *right;
struct TreeNode *left;
};
typedef struct TreeNode *nodePtr;
typedef struct TreeNode *tree;
typedef struct TreeNode node;
二叉查找树MakeEmpty操作
该操作主要用于树的初始化,即将一棵树初始化为一颗空树,递归释放树中的节点元素。
其实现如下;
/*以递归的方式传递进行树节点的释放*/
tree MakeEmptyTree(tree root)
{
if(root != NULL)
{
MakeEmptyTree(root->left);
MakeEmptyTree(root->right);
free(root);
root = NULL;
}
return root;
}
二叉查找树的插入操作SearchTreeInsert
进行插入操作的基本思路为
1:递归查找插入元素的树中位置,申请节点,插入到树中
2:根据二叉查找树的性质,当插入的元素大于节点元素的时候,向右子树确定元素插入位置,当小于某节点元素的时候,向左子树确定元素插入位置
3:当树中有与插入元素相等的节点,则将该节点结构中的frequency成员加1
其具体实现如下:
void SearchTreeInsert(TreeElementType x,tree *root)
{
if((*root)==NULL)
{
(*root) = (nodePtr) malloc(sizeof(node));
if((*root) == NULL)
{
printf("can't malloc ,insert operation failed");
}
(*root)->frequency = 1;
(*root)->element = x;
(*root)->left = NULL;
(*root)->right = NULL;
return;
}
if(x > ((*root)->element))
{
SearchTreeInsert(x,&(*root)->right);
return;
}
if(x < ((*root)->element))
{
SearchTreeInsert(x,&(*root)->left);
return;
}
if(x == ((*root)->element))
{
++((*root)->frequency);
}
}
二叉查找树的查找操作Find、FindMin、Findmax
二叉查找树的基本查找操作有三种
nodePtr Find(TreeElementType x,tree root)函数的功能为查找root树中是否有x元素,若有则返回元素节点结构的指针,若没有则返回NULL
nodePtr FindMax(tree root)函数的功能返回root查找树中的最大元的指针,若root为空,则返回NULL。
nodePtr FindMin(tree root)函数的功能返回root查找树中的最小元的指针,若root为空,则返回NULL。
以上三种函数的实现可以通过递归和迭代两种方法。
Find
在本次的实现中Find操作的实现使用的是递归实现,代码如下:
nodePtr Find(TreeElementType x,tree root)
{
if(root != NULL)
{
if(root->element < x)
{
return Find(x,root->right);
}
if(root->element > x)
{
return Find(x,root->left);
}
}
return root;
}
FindMax和FindMin
FindMax可以用递归,也可以用迭代
下面通过迭代的方式实现进行最大值的查找
递归的查找的思路如下:
nodeptr FindMax(tree root)
{ if(root == NULL)
{ return NULL;}
if(root->right != NULL)
{ return FindMax(root);}
return root;
}
FindMin递归实现同上
其迭代实现的源码如下
FindMax
nodePtr FindMax(tree root)
{
if(root == NULL)
{
return NULL;
}
while((root->right)!= NULL)
{
root = root->right;
}
return root;
}
FindMin
nodePtr FindMin(tree root)
{
if(root == NULL)
{
return NULL;
}
while((root->left) != NULL)
{
root = root->left;
}
return root;
}
二叉查找树的删除操作是二叉查找树中最难的部分
删除节点target有如下四种情况
1;删除节点target的频率大于1
2:删除节点target为叶子节点
3:删除节点target只有左孩子或者右孩子
4:删除节点target只有既有右孩子也有左孩子
删除节点target的频率大于1在这种情况下,只需要将target结构体中的frequency成员的值减1
if(targetPtr->frequency > 1)
{
--targetPtr->frequency;
return;
}
删除节点target为叶子节点
找到target节点的父节点,并删除target节点
if((targetPtr->left == NULL)&&(targetPtr->right == NULL))
{
if((*root) == targetPtr)
{
free(targetPtr);
(*root) = NULL;
return;
}
tmpPtr = *root; //找到父亲节点
while(tmpPtr != NULL)
{
if(x < tmpPtr->element)
{
if(tmpPtr->left == targetPtr) break;
else tmpPtr = tmpPtr->left;
}
if(x > tmpPtr->element)
{
if(tmpPtr->right == targetPtr) break;
else tmpPtr = tmpPtr->right;
}
}
free(targetPtr);
tmpPtr->left = NULL;
tmpPtr->right = NULL;
return;
}
删除节点target只有左孩子或者右孩子
只有一个孩子节点的时候,只需要将孩子替换掉被删除的节点即可
该方法中,具体实现为将目标节点target的孩子节点赋值到target中,然后删除孩子节点
即 target = leftchild or target = rightchild then delete righchild or leftchild
其实现代码如下
if((targetPtr->left != NULL)&&(targetPtr->right == NULL))
{
tmpPtr = targetPtr->left;
targetPtr->element = tmpPtr->element;
targetPtr->left = tmpPtr->left;
targetPtr->right = tmpPtr->right;
targetPtr->frequency = tmpPtr->frequency;
free(tmpPtr);
return;
}
if((targetPtr->left == NULL)&&(targetPtr->right != NULL))
{
tmpPtr = targetPtr->right;
targetPtr->element = tmpPtr->element;
targetPtr->left = tmpPtr->left;
targetPtr->right = tmpPtr->right;
targetPtr->frequency = tmpPtr->frequency;
free(tmpPtr);
return;
}
删除节点target只有既有右孩子也有左孩子
当删除节点有左右孩子的时候,用右子树的最小值代替删除的节点(也可以用左子树的最大值代替),本次实现中,使用的是右子树的最小值。
在找到最小值替换之后,就需要将最初的最小值的节点删除,由于右子树中的最小值只可能有右孩子,不可能有左孩子
当删除最小节点的时候有如下两种情况
1:最小值节点有右孩子节点,跟删除节点target只有左孩子或者右孩子的思路类似,只需要将右孩子替换掉该节点。
2:最小值节点为叶子节点,找到该叶子节点的父节点,删除该节点,并将父节点得右孩子指针设为NULL。
具体代码实现如下:
if((targetPtr->left != NULL)&&(targetPtr->right != NULL))
{
minPtr = FindMin(targetPtr->right); //最小值肯定没有左子树
targetPtr->element = minPtr->element;
if((minPtr->right == NULL))
{
tmpPtr = targetPtr;
if(tmpPtr->right == minPtr) //最小值为左子树的第一个节点
{
free(minPtr);
tmpPtr->left = NULL;
tmpPtr->right = NULL;
return;
}
tmpPtr = tmpPtr->right;
while(tmpPtr != NULL)
{
if(tmpPtr->left == minPtr)
{
free(minPtr);
tmpPtr->left = NULL;
return;
}
tmpPtr = tmpPtr->left;
}
}
测试:
在源码的main函数中
运行程序截图如下
除了delete和makeEmpty之外,对于所有的操作都是花费O(logN)的时间,因为我们用的常数时间在书中降低一层,对树的操作大概减少一半左右
二叉树的平均深度是O(log N),但是当反复进行删除和插入操作时候,有可能会导致树不平衡,比如左右子树之间的深度有着很大的差。
这种情况就会导致之前树的基本操作消耗,和深度的优势就会大大的折扣,这种情况显然不是乐于见到的。
对于上面,可以在进行删除操作的时候,我们随机选择用右子树的最小元素和左子树的最大元素来替代被删除的元素以缓解这种问题,但是不排除极端的问题。
还有一种方法,即使用平衡二叉树,即AVL树。
参考资料:数据结构与算法分析:C语言描述(原书第2版)