您的位置:首页 > 其它

学习《算法导论》 二叉查找树 总结

2015-09-17 23:40 316 查看

学习《算法导论》 二叉查找树 总结

二叉查找树

二叉查找树可以用链表结构来表示, 其中每个结点除了包括卫星数据外,还包括域left, right, parent; 它们分别指向结点的左儿子,右儿子和父结点.

二叉查找树的链表结构定义如下:

typedef int ElemType;
// 二叉树的链表结构
typdef struct TreeNode
{
ElemType data;
struct TreeNode* left;  // 指向左儿子
struct TreeNode* right; // 指向右儿子
struct TreeNode* parent; // 指向父结点
}TreeNode, *PointTree, *TreeNode;


结点元素数据要满足二叉查找树性质:

1. 设x为二叉查找树的一个结点,若y是x的左子树中的一个结点,则:y.data <= x.data ; 若y是x的右子树中的一个结点,则:x.data <= y.data

注意:这里说的是x的左子树中的一个结点,并不是说x的左结点,右结点类似.

二叉查找树的遍历

根据上面的二叉查找树性质,可以按顺序输出树中的所有结点. 这些算法有:

中序遍历算法:根结点的输出介于左子树和右子树之间

先序遍历算法:根结点的输出介于左子树和右子树之前

后序遍历算法.:根结点的输出介于左子树和右子树之后

中序遍历算法

VOS_VOID INORDED-TREE-WALK(PointTree RootNode)
{
if (RootNode != NULL)
{
INORDED-TREE-WALK(RootNode->left);
printf(RootNode->data);
INORDED-TREE-WALK(RootNode->right);
}

return;
}


先序遍历算法

VOS_VOID PREORDER-TREE-WALK(PointTree TreeRoot)
{
if (TreeRoot != NULL)
{
printf(TreeRoot->data);
PREORDER-TREE-WALK(TreeRoot->left);
PREORDER-TREE-WALK(TreeRoot->right);
}
}


后序遍历算法

VOS_VOID POSTORDER-TREE-WALK(PointTree TreeRoot)
{
if (TreeRoot != NULL)
{
POSTORDER-TREE-WALK(TreeRoot->left);
POSTORDER-TREE-WALK(TreeRoot->right);
printf(TreeRoot->data);
}
}


二叉查找树的查询

二叉查找树能支持以下操作:SEARCH, MINIMUM, MAXIMUM, SUCCESSOR和PREDECESSOR等. 并说明对高度为h的树, 它们都可以在O(h)时间内完成.

SEARCH

// 给定一关键字,查找树中是否存在; 递归算法
PointTree TREE-SEARCH(PointTree Tree, ElemType KeyElem)
{
if (Tree == NULL)
{
return NULL;
}
if (Tree->data == KeyElem)
{
return Tree;
}
else if (KeyElem < Tree->data)
{
// 递归
return TREE-SEARCH(Tree->left, KeyElem);
}
else
{
return TREE-SEARCH(Tree->right, KeyElem);
}
}


// 非递归算法
PointTree ITERATIVE-TREE-SEARCH(PointTree Tree, ELemType KeyElem)
{
PointTree CurrentTreeNode = Tree;
if (NULL == Tree)
return NULL;
while(CurrentTreeNode->data != KeyElem)
{
if (KeyElem < CurrentTreeNode->data)
CurrentTreeNode = CurrentTreeNode->left;
else
CurrentTreeNode = CurrentTreeNode->right;
}
return Tree;
}


MINIMUM

PointTree TREE-MINIMUM(PointTree TreeRoot)
{
PointTree CurrentNode = TreeRoot;

while(CurrentNode->left != NULL)
{
CurrentNode = CurrentNode->left;
}

return CurrentNode;
}


MAXIMUM

PointTree TREE-MAXIMUM(PointTree TreeRoot)
{
PointTree CurrentNode = TreeRoot;

while(CurrentNode->right != NULL)
{
CurrentNode = CurrentNode->right;
}

return CurrentNode;


后继

前驱和后继,这两个概念不好理解,在线性表,队列或栈中,前驱就是该结点前面一个结点,后继就是该结点后面一个结点;但是树不同,因为树有三种遍历方法,在前序遍历中结点的前驱和在后序遍历中结点的前驱是不一样的。后继也一样。所以在树结构中,谈到前驱和后继,是在某种遍历方法的前提下说的。

在看后继的算法之前,先看算法导论中的一道题:

题目:若二叉查找树T中某个结点x的右子树为空,且x有一个后继y,则:y就是x的最低祖先,且其左孩子也是x的祖先.

先解释下这个结论吧,结论比较绕口。我这里用集合来表示下吧:

{祖先 | 其左孩子也是x的} 这个集合中离x最近的祖先。

证明:给定结点x,其后继y存在,则:y>x. 由于y大于x,则y不可能在x的左子树中,又因为x的右子树为空。则y只能是结点x的祖先. 或x祖先的右子树中。

又因为y是其中大于x且最小的一个,则y不可能是其祖先的右子树,那么我们可以将范围缩小至y必定为x的祖先

又根据y>x,则x必定在y的左子树中,即y的左孩子也是x的祖先(x也是x的祖先)。

下面是算法

// 查找结点x的后继
PointTree TREE-SUCCESSOR(PointTree TreeNode)
{
// TreeNode的右子树非空,则TreeNode的后继就是
// 右子树中的最小结点
if (TreeNode->right != NULL)
{
return TREE-MINIMUM(TreeNode->right);
}
else
{
// 若右子树为空,则根据上面的题目,即查找TreeNode的最低祖
// 先,且其左孩子也是x的祖先,下面算法实现:
// 从TreeNode开始向上查找,直到找到某个结点的左孩子也是
// TreeNode的祖先为止
PointTree CurrentNode = TreeNode->parent;
// 条件:CurrentNode->right == TreeNode要求:CurrentNode要大于TreeNode
while((CurrentNode != NULL) && (CurrentNode->right == TreeNode))
{
TreeNode = CurrentNode;
CurrentNode = CurrentNode->parent;
}

return CurrentNode;
}


算法比较难理解,最好画个树的图来理解.

二叉查找树的插入

插入会引起二叉查找树的动态集合的变化,所以插入之后,要修改数据的结构。但要按照二叉查找树的原则来修改。话不多说,直接看算法:

// 将NewNode插入TreeRoot树中
VOS_VOID TREE-INSERT(PointTree TreeRoot, TreeNode NewNode)
{
PointTree CurrentNode = TreeRoot;
PointTree TmpNode = NULL;
// 若CurrentNode 为空,也即找到NewNode插入的位置
while(CurrentNode != NULL)
{
TmpNode = CurrentNode;
// 左子树
if (NewNode->data < CurrentNode->data)
{
CurrentNode = CurrentNode->left;
}
else // 右子树
{
CurrentNode = CurrentNode->right;
}
}

// 由上面可知,TmpNode就是NewNode的父结点
NewNode->parent = TmpNode;
// 若TmpNode为空,则说明该树是空树
if (NULL == TmpNode)
{
TreeRoot = NewNode;
}
else if (NewNode->data < TmpNode->data)
{
// NewNode放在TmpNode的左子树
TmpNode->left = NewNode;
}
else
{
// NewNode放在TmpNode的右子树
TmpNode->right = NewNode;
}

return;
}


二叉查找树的删除

删除操作有三种情况:

1. 若删除的结点z没有子女,即z为叶子结点,则修改其父结点,将它的子女改为NULL;

2. 若z只有一个子女,则通过在其子结点与父结点间建立一条链来删除z;

3. 若z有两个子女,则找到z的后继(后继也就是:z的右子树的最左边的结点,所以它是没有左子女的);

代码如下:

VOS_VOID TREE-DELETE(PointTree TreeRoot, TreeNode DeleteNode)
{
TreeNode CurrentNode = NULL;
TreeNode ChildNode = NULL;
// 第一种情况和第二种情况:z最多只有一个子结点
if (DeleteNode->left == NULL || DeleteNode->right != NULL)
{
CurrentNode = DeleteNode;
}
else
{
// 第三种情况:找到z的后继
CurrentNode = TREE-SUCCESSOR(DeleteNode);
}

// ChildNode被置为CurrentNode的非NULL子女结点
if (CurrentNode->left != NULL) // 即这里是第二种情况或第三种情况
{
ChildNode = CurrentNode->left;
else // 即这里是第一种情况或第二种情况
{
ChildNode = CurrentNode->right;
}

if(ChildNode != NULL) // 这就是第二种情况
{
ChildNode->parent = CurrentNode->parent;
}

if (CurrentNode->parent == NULL) // CurentNode为DeleteNode,但是无祖先,说明DeleteNode为根结点
{
TreeRoot = ChlidNode;
}
else if (CurrentNode == CurrentNode->parent->left)
{
CurentNode->parent->left = ChlidNode;
}
else
{
CurentNode->parent->right = ChildNode;
}

if (CurentNode != DeleteNode)
{
DeleteNode->data = CurentNode->data;
}

free(CurentNode);
return;
}


这个删除算法没看懂,有三种情况比较好理解,但是这个算法没看明白。

下次找资料好好看看

下面再介绍一种二叉查找树的删除操作算法,这个比较好理解,实际上就是分别讨论了三种情况:

/*删除一个节点
p为树根节点指针
node_value要删除的节点的值
*/
bool DeleteTreeNode(Tree * p,int node_value)
{
Tree * t = FindNode(p,node_value);
if(t == NULL)
{
return false;
}
if((t->left == NULL)&&(t->right == NULL))
{/*没有子节点*/
Tree* tmp = t;
if(tmp->parent->left == tmp)
{
tmp->parent->left = NULL;
}
else
{
tmp->parent->right = NULL;
}
delete tmp;
tmp = NULL;
}
else if((t->left == NULL)||(t->right == NULL))
{/*一个子节点*/
Tree* tmp = t;
if(tmp->parent->left == tmp)
{
tmp->parent->left = (tmp->left ==NULL)?tmp->right :tmp->left;
}
else
{
tmp->parent->right = (tmp->left ==NULL)?tmp->right :tmp->left;
}
delete tmp;
tmp = NULL;
}
else
{/*两个子节点*/
Tree* s = Successor(t);
if(s == NULL)
{
return false;
}
t->node_value = s->node_value ;
DeleteTreeNode(s);

}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: