学习《算法导论》 二叉查找树 总结
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); } }
相关文章推荐
- 二叉树的深度
- PUNCH算法
- 二叉排序树--源码
- 在arcmap属性表 输入文本内容
- 苹果开发 笔记(75)UISegmentedControl
- 何必自卑得一塌糊涂?你其实优秀得令人发指!
- linux安装mysql以及远程不能连接mysql的解决办法
- 32-bit程序读取64bit 注册表
- 条款33:避免遮掩继承而来的名称(Avoiding hiding inherited names)
- 使用IOS7原生API进行二维码条形码的扫描
- ***HDU 4429 - Split the Rectangle(LCA'暴力求解)
- 项目实战No9 不等高cell高度 相册图片
- html5应用缓存
- C++:数组排列组合的问题。
- 从源码角度看Handler原理
- 新手解决winform问题的一点小经验
- Scramble String
- Codeforces Round #319 (Div. 2) B. Modulo Sum
- SQL Server: SELECT * 的真相: 索引覆盖(index coverage)
- H264码流结构分析