数据结构 -- 二叉查找树
查找树ADT -- 二叉查找树
二叉树的一个重要的应用是它们在查找中的使用。使二叉树变为二叉查找树的性质是,对于树的每个节点X,它的左子树中所有关键字值小于X的关键值,而右子树中所有关键字值大于X的关键字值,这意味着,该树所有的元素可以用某种统一的方式排序。现在给出通常对二叉查找树进行的操作的简要描述。由于树的递归定义,通常是递归地编写这些擦欧总的例程。因为二叉查找树的平均深度为O(log N),所以我们一般不会担心栈空间用尽。
二叉查找树的声明
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);
struct TreeNode
{
ElementType Element;
SearchTree Left;
SearchTree Right;
}
MakeEmpty
这个操作主要用于初始化。
SearchTree MakeEmpty(SearchTree T)
{
if(T != NULL)
{
MakeEmpty(T->Left);
MakeEmpty(T->Right);
free(T);
}
return NULL;
}
Find
这个操作一般返回指向树T中具有关键字X的节点的指针,如果这样的节点不存在则返回NULL。首先,要对是否为空树进行测试,如果T是NULL,那么我们就可以返回NULL。否则,如果存储在T中的关键字是X,那么我们就可以返回T。否则,我们对树T的左子树或右子树进行一次递归调用,这依赖于X与存储在T中关键字的关系。
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
这个操作返回树中的最小元的位置。从根开始并且只要有左儿子就向左进行,终止点就是最小的元素
Position FindMin(SearchTree T)
{
if(T == NULL)
return NULL;
else if(T->Left == NULL)
return T;
else
return FindMin(T->Left);
}
FindMax
这个例程返回树中的最大元的位置,除分支朝向右儿子外其余过程和FindMin一样
Position FindMax(SearchTree T) { if(T != NULL) while(T->Right != NULL) T = T->Right; return T; }
Insert
为了将X插入到树T中,如果找到X,那么什么也不用做(或做一些“更新”);否则,将X插入到遍历的路径上的最后一点上。
SearchTree Insert(ElementType X, SearchTree T)
{
if(T == NULL)
{
/* Create and return a one-node tree*/
T = malloc(sizeof(struct TreeNode));
if(T == NULL)
{
printf("Out of space");
exit(1);
}
else
{
T->Element = X;
T->Left = T->Right = NULL;
}
}
else if (X < T->Element)
T->Left = Insert(X, T->Left);
else if (X > T->Element)
T->Right = Insert(X, T->Right);
/* Else X is in the tree already; we'll do nothing */
return T;
}
Delete
最困难的操作就是删除了。下面考虑几种可能的情况:
(1) 如果节点是一片树叶,那么它可以立即被删除
(2) 如果结点有一个儿子,则该节点可以在其父节点调整指针绕过该节点后被删除
(3) 如果结点有两个儿子,一般是用其右子树的最小的数据(很容易找到)代替该节点的数据并递归地删除那个节点(现在它是空的),因为右子树的最小节点不可能有左儿子,所以第二次Delete(删除)要容易。
SearchTree Delete(ElementType X, SearchTree T)
{
Position TmpCell;
if(T == NULL)
{
printf("Element not found");
exit(1);
}
else if(X < T->Element)
T->Left = Delete(X, T->Left);
else if(X > T->Element)
T->Right = Eelete(X, T->Right);
else if(T->Left && T->Right) /*Two children*/
{
/*Replace with smallest in right subtree*/
TmpCell = FindMin(T->Right);
T->Element = TmpCell->Element;
T->Right = Delete(T->Element, T->Right);
}
else /* one or zero children */
{
TmpCell = T;
if(T->Left == NULL)
T = T->Right;
else if(T->Right == NULL)
T = T->Left;
free(TmpCell);
}
}
如果删除的次数不多,则通常使用的策略是懒惰删除:当一个元素要被删除时,它仍留在树中,而是只做了个删除的记号。这种做法特别是在有重复关键字时很流行,因此此时记录出现频率数的域可以减一。如果树中的实际节点数和“被删除”的节点数相同,那么树的深度预计只上升一个小的常数。因此,存在一个与懒惰删除相关的非常小的时间损耗,再有,如果被删除的关键字是重新插入的,那么分配一个新单元的开销就避免了。
平均情形分析
一棵树的所有节点的深度的和称为内部路径长。上述的操作的平均时间都是O(logN),除了一些个别情形外。。。