二叉搜索树
以下摘自算法导论:
"顾名思义,一颗二叉搜索树是以二叉树来组织的。"
"二叉搜索树中的关键字总是以满足二叉搜索树性质来存储的:
设x是二叉树中的一个结点。如果y是左子树x中的一个结点,那么
y.key ≤ x.key。如果y是x右子树中的一个结点,那么y.key ≥
x.key."
“二叉搜索树上的基本操作所花费的时间与这棵树的高度成正比。对有
n个节点的 完全二叉树 来说,这些操作的最坏运行时间为Θ(lg n)。"
上面几句话非常简练的描述了二叉搜索树的基本性质和基本特性。但在实际中,并不能保证二叉搜索树始终是平衡的。比如,依次插入一列从小到大排列的数列,生成的二叉树很有可能是极端不平衡的—–它总是把下一个数据插入在前一个数据的右端,以致整棵树的右子树部分持续增长,破坏了二叉树的平衡。在这种情况下,有些二叉树上的操作时间会表现出最坏情况 Θ(n) ,与完全平衡状态下的 Θ(lg n) 相去甚远。
以下为伪代码的C++代码部分:
typedef Data int;
struct Node
{
Data key;
Node* left;
Node* right;
Node* parent;
};
typedef Tree Node*;
typedef SubTree Node*;
查询
//inorder traverse
void inorder_tree_walk (Node* x)
{
if (x != nullptr)
{
inorder_tree_walk(x.left);
//format string should change if Data is not an int
printf ("%i\t", x.key);
inorder_tree_walk (x.right);
}
}
Node* tree_search (Node* x, Data key)
{
if (x == nullptr || key == x.key)
return x;
if (key < x.key)
return tree_search (x.left, key);
else
return tree_search (x.right, key);
}
//iterative version of tree_search ()
Node* iterative_tree_search (Node* x, Data key)
{
while (x != nullptr && key != x.key)
{
if (key < x.key) x = x.left;
else x = x.right;
}
return x;
}
Node* tree_minimum (Node* x)
{
while (x.left != nullptr)
x = x.left;
return x;
}
Node* tree_maximum (Node* x)
{
while (x.right != nullptr)
x = x.right;
return x;
}
以上的函数比较简单,在此不作多余的赘述。
下面这个函数tree_successor ()接受结点指针为输入参数,输出该结点的后继结点指针。这里分两种情况:
- 若输入结点的右结点不为空,则该节点的后继结点为该结点的右结点的最小值结点。
- 若输入结点的右结点为空,则沿着二叉树向上,直至:1.遇到某个结点使得它为其父结点的左结点。2.遇到根节点。
//search for node x's successor
Node* tree_successor (Node* x)
{
if (x.right != nullptr)
return tree_minimum (x.right);
Node* y = x.parent;
while (y != nullptr && x == y.right)
{
x = y;
y = y.parent'
}
return y;
}
函数tree_predecessor ()与tree_successor ()相似,在此不再给出。
插入
函数tree_insert (Tree root, Node* node)在 以root为根结点的二
叉树中结点node,其中结点node各字段已被正确初始化。
- 过程:根据比较情况沿树向下,最后成为叶结点的子结点。
void tree_insert (Tree root, Node* node)
{
Node* y = static_cast<Noede*> (nullptr);
Node* x = root;
while (x != nullptr)
{
y = x;
if (node.key < x.key) x = x.left;
else x = x.right;
}
//assign new node's parent field
node.parent = y;
//meet the root node, change root node
//assume node is correctly initialized
if (y == nullptr) root = node;
else if (node.key < y.key) y.left = node;
else y.right = node;
}
删除
删除函数比较复杂,这里需要引入一个辅助函数:
- prototype: transplant (Tree root, SubTree u, SubTree v);
- result: 用一棵以v为根的子树来替换一棵以u为根的子树。结点u的双亲成为结点v的双亲,并且最后v成为u的双亲的相应的孩子。
- attention: 函数只处理了子树u的更新,对子树v的更新由该函数的调用者(caller)负责。
void transplant (Tree root, SubTree u, SubTree v)
{
//if sub-tree u is null, replace root with new tree v
if (u.parent == nullptr) root = v;
else if (u == u.parent.left) u.parent.left = v;
else u.parent.right = v;
if (v != nullptr) v.parent = u.parent;
}
以下是删除函数的实现, 在以root为根的二叉树中删除结点z,其中调用了子例程transplant(…);
删除函数比较复杂,因为删除结点后可能会造成二叉树的不平衡。所以在该函数中要处理多种情况,这里假设要删除的结点为z,替换即为调用transplant(…)函数:
- z结点无左子树—-直接将z结点替换为z的右子树。
- z结点无右子树—-直接将z结点替换为z的左子树。
z结点既有左子树又有右子树:
我们这里选择用z的后继结点来替换z。所以根据后继结点是否是z的右结点,又可以分为两种情况:1.后继结点是z的右结点—-直接将z结点替换为后继结点。(因为后继结点必定没有左结点)。2.后继结点不是z的右结点—-在不破坏所在子树的平衡条件情况下,将后继结点调整为z的右结点,即将情况2转化为情况1.
以下是delete结点时的四种情况:
void tree_delete (Tree root, Node* z)
{
if (z.left == nullptr) transplant (root, z, z.right);
else if (z.right == nullptr) transplant (root, z, z.left);
else
{
//because y is the minimum node of z.right, so no left child
Node* y = tree_minimum (z.right);
//if y is not a neighbor of z, transplant it, and make it a neighbor
if (y.parent != z)
{
transplant (root, y, y.right);
y.right = z.right;
y.right.parent = y;
}
transplant (root, z, y);;
y.left = z.left;
y.left.parent = y;
}
}
以上函数的正确性需要由二叉搜索树的性质来保证。比如,tree_maximum
()函数的正确性需要是由二叉搜索树的二叉性质来保证,即左子树的数据必须小于等于父节点的数据(如果有的话)。