数据结构——二叉查找树
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构——二叉查找树相关的知识,希望对你有一定的参考价值。
使二叉树成为二叉查找树的性质是,对于树中的每个节点X,它的左子树中所有关键字值小于X的关键字值,而它的右子树中所有关键字值大于X的关键字值。这意味着,该树所有的元素以某种统一的方式排序。
二叉查找树的声明
二叉查找树是一棵特殊的二叉树,二叉查找树中节点的结构与二叉树种节点的结构相同,关键在于可以在二叉查找树上可以执行的操作以及对其进行操作的算法。二叉查找树的声明如下:
#ifndef _Tree_H
struct TreeNode;
typedef struct TreeNode *Position;
typedef struct TreeNode *SearchTree;
SearchTree MakeEmpty(SearchTree T);
Position Find(ElementType X,SearchTree T);
Position FindMin(SearchTree T);
Position FindMax(SearchTree T);
SearchTree Insert(ElementType X,SearchTree T);
SearchTree Delete(ElementType X,SearchTree T);
ElementType Retrieve(Position p);
#endif
二叉查找树节点的结构:
struct TreeNode
{
ElementType Element;
SearchTree Left;
SearchTree Right;
};
MakeEmpty操作
MakeEmpty主要用于建立一棵空树。例程如下:
SearchTree MakeEmpty(SearchTree T)
{
if(T!=NULL) //递归终止条件
{
MakeEmpty(T->Left); MakeEmpty(T->Right);
free(T);//实际执行的操作,释放空间
}
return NULL;
}
递归算法必须有递归终止条件,此处的终止条件就是树(子树)的根节点为Null。
Find操作
查找操作是在树中查找保存要检索的关键字元素的节点的指针,返回值为指向节点的指针类型,如果没有找到则返回NULL。二叉查找树的重要特性就是元素排列是有顺序的,因此查找元素时可以根据这一特性进行有“方向”的查找(向某一个分支进行查找),而不是检索所有的元素,这样查找操作的时间复杂度最大为二叉查找树的深度,二叉查找树的平均深度为O(logN),因此二叉查找树的查找操作的时间复杂度为O(logN),相对于线性数据结构的O(N)有很大的进步。
利用二叉查找树的递归性质,利用递归算法进行查找操作是非常简单的。核心思想就是利用要查找的元素与(子树)根节点处的元素的对比(大于小于等于)来选择查找的方向(左右子树)或者找到要查找的元素。同时处理根节点为空的情况。代码如下:
Position Find(ElementType X,SearchTree T)
{
if(T==NULL)
return NULL;
if(X<T->Element)
return Find(X,T->left);
else if(X>T->Element)
return Find(X,T->right);
else
return T;
}
FindMin和FindMax操作
FindMin与FindMax操作比较简单,查找最小值只需要找到最左边的节点,最大值只需要找到最右边的节点。算法只需要在左(右)子树为空时返回上一层节点即可,同时也需要注意最开始为NULL的情况。在树结构上的操作最直观的思路是使用递归,但是也可以使用迭代的思想。这里FindMin使用递归,FindMax使用迭代,代码如下:
Position FindMin(SearchTree T)
{
if(T==NULL)
return NULL;
else
if(T->left==NULL)
return T;
else
return FindMin(T->left);
}
Positon FindMax(SearchTree T)
{
if(T!=NULL)
while(T->Right!=NULL)
T=T->Right;
return T;
}
Insert操作
插入操作例程也比较简单,类似与查找操作,关键在于找到要插入的元素应该所处的位置,找到位置后就需要创建节点,并且将新创建的节点与原来的树“连接”起来(将新创建节点的指针放到其父节点的左或者右子节点指针位置)。利用递归思想处理插入操作,关键在于找到递归终止条件,这里的递归终止条件是要插入的树为空树(根节点为NULL的情况)。具体代码如下:
SearchTree Insert(ElementType X,SearchTree T)
{
if(T==NULL) //递归终止条件
{
T=malloc(sizeof(struct Treenode));
If(T==NULL)
ERROR;
Else
{
T->Element=X;
T->left=T->right=NULL;
}
}
else
{
if(X<T->element)
T->left=Insert(X,T->left);
If(X>T->element)
T->Right=Insert(X,T->Right);
//当要插入的元素已存在的情况,此时的操作需要看树本身定义
return T;
}
}
当要插入的元素已存在时,可以在节点中一个新的域中保存元素的频率。或者key值只是更大的数据结构的一部分,此时可以使用链表或者另一棵树来保存具有相同key值得数据。具体要根据需求决定。
Delete操作
删除操作是所有的操作中最复杂的操作,原因在于删除树中的节点不仅仅是释放一块内存空间那么简单。在从树中删除一个节点后,还需要保持二叉查找树的特性(根节点的左子树中的元素要比根节点中的元素小,右子树中的元素要比根节点中的元素大)。比较简单的情况是要删除的节点只有一个子节点或者没有子节点的情况。当没有子节点时,直接删除节点(释放节点占用的空间)即可。同时需要将其父节点的某一个子节点指针置为NULL。(在递归处理树操作时,只需要考虑当前节点作为根的子树即可,其父节点的处理是在递归函数返回后处理的,即只要在处理当前节点时考虑好怎么处理对其子节点调用本函数后的返回值即可,则递归机制运行时会处理好当前根节点的父节点的处理,这是递归思想的处理思路。)当只有一个子节点时,删除当前节点,将子节点直接作为要删除的节点的父节点的子节点。复杂的是当有两个子节点时,这时左子树需要保持不变,而把要删除的节点删除掉,同时将右子树中最小的节点拿来替换掉删除掉的节点,即将右子树中最小的节点的左子树设置为已删除节点的左子树,同时将已删除节点的父节点的某一个子节点指针设置为自己。同时,还需要从右子树中删除自己(原来最小的节点)。此时因为最小节点最多只会有右子树,则可以使用删除节点时最有一个子节点的情况进行处理。实际上,由于在利用递归思想进行操作时,当前处理节点的父节点很难获取,因此实际中并没有删除有两个子节点的要删除节点,只是将数据换为其右子树中最小节点的数据,这样比较简单。具体代码如下:
SearchTree Delete(Element X,SearchTree T)
{
Position TempCell;
If(T==NULL)
Error(“Element not found”);
Else
If(X<T->Element)
T->Left=Delete(X,T->Left);
Else
If(X>T->Element)
T->Right=Delete(X,T->right);
Else //找到了要删除的元素
If(T->left&&T->right) //左右子树都不为空
{
TempCell=FindMin(T->right);
T->Element=TempCell->Element;
T->Right=Dlete(T->element,T->Right);
}
else //只有左子树或者右子树,或者没有子节点
{
TempCell=T;
if(T->left==NULL)
T=T->Right;
If(T->Right=NULL)
T=T->Left;
Free(TempCell);
}
return T;
}
以上是关于数据结构——二叉查找树的主要内容,如果未能解决你的问题,请参考以下文章