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

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

二叉查找树

二叉查找树可以用链表结构来表示, 其中每个结点除了包括卫星数据外,还包括域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)时间内完成.

// 给定一关键字,查找树中是否存在; 递归算法
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); 

    } 
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值