二叉查找树。假设所有的关键字是互异的。
性质:对于树的每个节点X,它的左子数中所有关键字值小于X的关键字值,而它的右子树中所有关键字值大于X的关键字值。
算法:查找、插入和删除是主要的操作,都可以通过递归操作完成。其中删除操作要分情况讨论,
如果删除的节点有一个儿子,则该节点可以在其父节点调整指针绕过该节点后被删除。如果有两个儿子,一般的删除策略是用其右子树的最小的数据代替该节点的数据并递归地删除那个节点(现在它是空的)。因为右子树中的最小的节点不可能有左儿子,所以第二次Delete要容易得多。
平均深度:二叉查找树的平均深度是O(log N)。下面主要列出源代码配套相应的注释:
searchtree.h:
#include <stdlib.h>
typedef int ElementType;
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);
struct TreeNode
{
ElementType Element;
SearchTree Left;
SearchTree Right;
};
SearchTree MakeEmpty(SearchTree T)
{
if(T != NULL)
{
MakeEmpty(T->Left);
MakeEmpty(T->Right);
free(T);
}
return NULL;
}
Position Find(ElementType X, SearchTree T)
{
/* 不存在该节点则返回NULL */
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;
}
Position FindMin(SearchTree T)
{
if(T ==NULL)
return NULL;
if(T->Left == NULL)
return T;
else
return FindMin(T->Left);
}
Position FindMax(SearchTree T)
{
if(T == NULL)
return NULL;
while(T->Right != NULL)
T = T->Right;
return T;
}
SearchTree Insert(ElementType X, SearchTree T)
{
/* 插入新节点 */
if(T == NULL)
{
T = (Position)malloc(sizeof(struct TreeNode));
if(T != NULL)
{
T->Element = X;
T->Left = NULL;
T->Right =NULL;
}
return T;
}
/* 递归查找节点应该插入的位置 */
if(X < T->Element)
T->Left = Insert(X, T->Left);
else if(X > T->Element)
T->Right = Insert(X, T->Right);
/* 如果存在该节点则什么都不做 */
return T;
}
SearchTree Delete(ElementType X, SearchTree T)
{
Position Tmp;
/* 该节点不存在,什么都不做 */
if(T != NULL)
return T;
/* 节点在左分支,递归查询 */
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)
{
/* 替换为右分支下最小的元素 */
Tmp = FindMin(T->Right);
T->Element = Tmp->Element;
/* 递归删除右分支下最小的元素 */
T->Right = Delete(Tmp->Element, T->Right);
}
/* 找到该节点,有一个儿子或没有儿子*/
else
{
Tmp = T;
if(T->Left != NULL)
T = T->Left;
else if(T->Right != NULL)
T = T->Right;
free(Tmp);
}
return T;
}
ElementType Retrieve(Position P)
{
return P->Element;
}
testsearchtree.h:
#include <stdio.h>
#include <stdlib.h>
#include <searchtree.h>
int main( )
{
SearchTree T;
Position P;
int i;
int j = 0;
T = MakeEmpty( NULL );
for( i = 0; i < 50; i++ )
T = Insert( i, T );
for( i = 0; i < 50; i++ )
if( ( P = Find( i, T ) ) == NULL || Retrieve( P ) != i )
printf( "Error at %d\n", i );
for( i = 0; i < 50; i += 2 )
T = Delete( i, T );
for( i = 1; i < 50; i += 2 )
if( ( P = Find( i, T ) ) == NULL || Retrieve( P ) != i )
printf( "Error at %d\n", i );
for( i = 0; i < 50; i += 2 )
if( ( P = Find( i, T ) ) != NULL )
printf( "Error at %d\n", i );
printf( "Min is %d, Max is %d\n", Retrieve( FindMin( T ) ),
Retrieve( FindMax( T ) ) );
system( "pause" );
return 0;
}
复杂度分析:除了MakeEmpty外,所有的操作都是O(d),其中d是所访问的关键字的节点的深度。假设所有的树的出现的机会均等,则
树的所有节点的平均深度为O(log N)。
如果向一棵预先排序的树输入数据,那么代价巨大,因为树将只由那么没有左儿子的节点组成。一种解决方法是找平衡条件:任何节点的深度不能过深。
最老的一种平衡查找树,即AVL树。另外,较新的方法是放弃平衡条件,允许树有任何的深度,但是在每次操作之后要使用一个调整规则进行调整,使得后面的操作效率更高,这是自调整类结构,如伸展树。读者可自行了解。