CS61B -BST(二叉搜索树)
CS61B - BST
什么是BST
BST全名Binary search tree(二叉搜索树)
BST的基本性质
- BST可以是一棵空树
- 如果它的左树不为空,那么左树中的所有节点的值都小于根节点的值
- 若右树不为空,那么右树中的所有节点都大于根节点
- 其左右子树也均为二叉树(有一种递归的味)
举个例子看看什么是BST吧,假如我们按顺序向一个空二叉树中插入如下元素(暂时以整型数据作为二叉树的元素,这样便于观察规律)
5,7,3,4,6,8,9,15,10,2
那么我们的BST结构应该是这样形成的:
我们如何使用Java来构建一个BST
我们要创建一个名为BSTTree的类,该类需要包含一个BSTNode的类用于设定树的结点,其中需要包含键值(key)、父节点(parent)、左孩子(left)、右孩子(right),并且还需要一个Constructor来初始化节点的各类信息。
public class BSTTree<T extends Comparable<T>> {private BSTNode<T> mRoot;public BSTNode<T extends Comparable<T>> {T key;BSTNode<T> parent; //parent nodeBSTNode<T> left; //left nodeBSTNode<T> right; //right node//constructorpublic BSTNode(T key, BSTNode<T> parent, BSTNode<T> left, BSTNode<T> right) {this,key = key;this.parent = parent;this.left = left;this.right = right;}
}
BST的基本操作
BST的遍历
BST的遍历同普通树的遍历一样,分为中序、前序、后序遍历三种情况
- 中序遍历
左 -> 中 -> 右 - 前序遍历
中 -> 左 -> 右 - 后序遍历
右 -> 中 -> 左
举个例子看看吧(接着拿上面我们已经创建好的树进行遍历):
中序遍历顺序(左中右):2-3-4-5-6-7-8-9-10-15
前序遍历顺序(中左右):5-3-2-4-7-6-8-9-15-10
后序遍历顺序(右左中):10-15-9-8-6-7-4-2-3-5
了解了BST的三种遍历方式,我们来看看如何在Java中实现:
- 中序遍历(in-order traversal):
inOrderHelper
//inorder traversal(left -> middle -> right)
private void inOrderHelper(BSTNode<T> tree) {if(tree != null) {inOrderHelper(tree.left); //leftSystem.out.print(tree.key + " ");//middleinOrderHelper(tree.right); //right}
}
inOrder
public void preOrder() {preOrderHelper(mRoot);System.out.println();
}
- 前序遍历(pre-order traversal):
preOrderHelper
private void preOrderHelper(BSTNode<T> tree) {if(tree != null) {System.out.print(tree.key + " ");//middlepreOrderHelper(tree.left);//leftpreOrderHelper(tree.right);//right}
}
preOrder
public void preOrder() {preOrderHelper(mRoot);System.out.println();}
- 后序遍历(post-order traversal):
PostOrderHelper
private void postOrderHelper(BSTNode<T> tree) {if(tree != null) {postOrderHelper(tree.right);//rightpostOrderHelper(tree.left);//leftSystem.out.print(tree.key + " ");//middle}
}
postOrder
public void postOrder() {postOrderHelper(mRoot);System.out.println();
}
查找
查找某个key所在的位置,并且返回所查找的key值对应的节点,首先,我们需要一个searchHelper。searchHelper(BSTNode<T> x, T key)
我们查找的顺序是这样的,首先我们对比key与给定的节点x进行比较,如果key < x.key
, 我们向x的左子树中继续寻找,如果key>x.key
, 我们向x的右子树继续寻找,用这种原则进行迭代,若最终x=null,证明整个树中不存在这个key值,打印一句话返回null即可:
private BSTNode<T> searchHelper(BSTNode<T> x, T key) {if(x == null) {System.out.println("the tree is not contains " + key);return x;}int cmp = key.compareTo(x.key);if(cmp > 0) {return searchHelper(x.right, key);} else if(cmp < 0) {return searchHelper(x.left, key);} else {return x;}
}
接下来为我们的树建立查找方法,在search中直接调用helper方法并且将节点参数设定为树的根节点。
public BSTNode<T> search(T key) {return searchHelper(mRoot, key);
}
先驱、后继
什么是先驱和后继呢?
先驱节点是对一棵树进行中序遍历,遍历后当前节点的前一个节点。
后继节点是对一棵树进行中序遍历,遍历后当前节点的后一个节点
而对于二叉树而言,如果我们要寻找它的根节点的先驱节点,如果其存在左子树,那么先驱节点就是其左子树种key最大的节点,若左子树不存在,则没有先驱节点;同理,若存在右子树,那么其后继节点则为右子树中key最小的节点,若右子树不存在,则没有后继节点。
举个例子吧,接着用我们上面构建的那棵树:
对其进行中序遍历(左-中-右):2-3-4-5-6-7-8-9-10-15,所以8的先驱节点为7,后继节点为9。至于我们为什么需要先驱和后继我们接下来会提到,但是我们先想想如何找到我们树中的先驱节点和后续节点并且返回吧。
先驱节点
- 我们设计一个helper method用于返回一个较大的key值对应的节点,就叫maximumHelper吧,思路很简单,找右孩子就好了,因为右孩子一定会比父节点大:
private BSTNode<T> maximumHelper(BSTNode<T> tree) {if(tree == null) {return null;}while(tree.right != null) {tree = tree.right;}return tree;
}
- 接下来我们来找先驱节点,我们的思路是这样的:
(1)如果该节点有左子树,那么返回左子树中key最大的节点
(2)如果该节点没有左子树,那么我们再进一步分情况讨论:
case1: 如果该节点自身为一个右节点,那么如果它有父亲节点,其父亲节点就是它的先驱节点(可以画个图自己想一想,别忘了是中序遍历)。
case2:如果该节点是一个左节点,我们需要寻找它的父亲节点,直到一个父亲节点有右侧节点,那么这个父亲节点就是该节点的前驱节点。
那开始我的表演(顺带一提,先驱节点的英文叫predecessor呦)
public BSTNode<T> predecessor(BSTNode<T> x) {if(x.left != null) {return maximumHelper(x.left);}//if x don't have left node://case1: x is left node, we should find it's parent node until we find the node which have a right node, this parent node is what we want.//case2: x is right node, we should find it's parent node, it is want we want.BSTNode<T> y = x.parent; //createa node y equals to x's parent nodewhile((y != null) && (x == y.left)) { //if x is a left nodex = y;y = y.parent;}return y;
}
后继节点
- 我们设计一个minimumHelp用于查找较小的方法,如果一棵树上有左子树,那么找到最左侧的节点就OK了。
private BSTNode<T> minimumHelp(BSTNode<T> tree) {if(tree == null) {return null;}while(tree.left != null) { //找左侧tree = tree.left;}return tree;
}
- 设计successor方法,返回后继节点
也是一样的,我们分情况讨论:
(1)若该节点有右子树,则在其右子树中使用minimumHelp
(2)如果该节点没有右子树,则分类讨论
case1:如果自身是右节点,则其父亲节点则为该节点的后继节点
case2:若节点自身为左节点,则需要找该节点的父亲节点直到找到一个节点存在右孩子,那么该节点为其后继节点,java的实现与实现前驱节点过程极为相似:
public BSTNode<T> successor(BSTNode<T> x) {if(x.right != null) {return minimumHelp(x.right);}BSTNdoe<T> y = x.parent;while((y != null) && (x == y.right)) {x = y;y = y.parent;}return y;
插入
向二叉树中插入节点,这里分为两步:
- 在原有的树中确定要添加到的节点位置
- 在要添加的节点上确定将其设定为左节点还是右节点
如果原本没有根节点,那么直接将要插入的节点设定为这棵树的根节点。
我们的实现方式是这样的,先创建一个insertHelper
方法将我上述提到的两个步骤实现,之后用insert
方法调用并将参数设定为要插入的树的根节点:
insertHelper:
private void insertHelper(BSTNode<T> bst, BSTNode<T> z) {int cmp;BSTNode<T> y = null;BSTNode<T> x = bst.mRoot;//find z should be inserted in which nodewhile(x != null) {y = x; //use y record x positioncmp = z.key.compareTo(x.key);if(cmp < 0) {x = x.left;} else if (cmp ==0) {System.out.println("The item you insert has already existed");return ;} else {x = x.right;}}//find where should we insert our z node(left or right)z.parent = y;if(y == null) {mRoot = z; //the tree is empty, z is the root node} else {cmp = z.compareTo(y.key);if(cmp < 0) {y.left = z;} else {y.right = z;}}
}
insert方法,设定要添加的节点,将其父亲节点以及左右孩子节点均设为null,通过函数传递的参数设定key,之后调用insertHelper进行插入:
public void insert(T key) {BSTNode<T> insertedNode = new BSTNode<>(key, null, null, null);if(insertedNode != null) {insertHelper(this, insertedNode);}
}
移除
将某个键值对应的节点移除,这里也需要按照情况进行分析:
- 要移除的节点为孩子节点,直接拜拜
- 要移除的节点只有左or右孩子,那么将其孩子的父亲节点直接设定为要移除节点的父亲节点,然后说声拜拜
- 若要被移除的节点同时具有左右孩子节点,我们就需要细致的说一说了:
首先,我们要重新改变一下当前的树状结构,对于我们需要移除的那个节点,我们将包括该节点以及一下的部分视为一个树,那么也就是我们要移除这棵树的根节点,我们应该如何重组这棵树呢?
(1)我们要找到一个新的根,而根的要求也很简单,要求左子树中任意一个节点的值都比根节点值小,右子树中任意一个节点的值都比根节点大,那么我们很容易想到刚刚写完的先驱、后继这两个节点。对于BST而言,若查找根节点的先驱以及后继节点,就应该分别对应左子树中最大的节点和右子树中最小的节点,我们来看一个例子:
如果我们要移除这棵树中7这个节点,我们应该怎么办呢?
我们首先对以7作为根节点的这棵子树进行中序遍历:
我们可以看到该树的先驱为6、后继为8。所以我们可以选择其中的一个重新构造该树,假如我们选择的是后继节点8,那么我们的新树为:
这里我们注意到一点,对于BST来说,根节点的前驱节点和后继节点如果存在,那么一定会是只有一个孩子或者没有孩子,因为如果有两个孩子,那一定有比其更小以及更大的节点出现,那它自身就不会是先驱或者后继节点了。
(2)移除该节点后原先的节点依旧按照之前的顺序即可
下面我们用java来实现移除功能,同样也是先需要一个removeHelper
方法用于实现上述功能,而在remove
方法中对其进行调用:
private BSTNode<T> removeHelper(BSTNode<T> bst, BSTNode<T> z) {BSTNode<T> x = null;BSTNode<T> y = null;if((z.left == null) || (z.right == null)) {y = z;} else {y = successor(z);}if(y.left != null) {x = y.left;} else {x = y.right;}if(x != null) {x.parent = y.parent;}if(y.parent == null) {bst.mRoot = x; //if y's parent equals to null, it's mean that x is the root node} else if(y == y.parent.left) {y.parent.left = x;} else {y.parent.right = x;}if(y != x) {z.key = y.key;}return y;
}
remove方法中我们首先需要查找到这棵树中是否存在我们需要移除的节点,如果存在,那么调用removeHelper方法,若其返回值不为null,则将该节点设定为null(直接移除该节点)
public void remove(T key) {BSTNode<T> z, node;if((z = searchHelper(mRoot, key)) != null) {if((node = removeHelper(this, z)) != null) {node = null;}}
}
清除
clear,简简单单,直接清除树种所有节点吗?不,我们只需要将树的根节点设为null即可。
public void clear() {mRoot = null;size = 0;
}
小结
没写的很完全,代码比较粗糙,只是有个大意,接下来如果有机会会进一步改进BST的代码。
CS61B -BST(二叉搜索树)相关推荐
- 数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)
原文:http://blog.csdn.net/sup_heaven/article/details/39313731 数据结构中常见的树(BST二叉搜索树.AVL平衡二叉树.RBT红黑树.B-树.B ...
- BST二叉搜索树插入节点建树并找出不平衡节点,networkx,Python
BST二叉搜索树插入节点建树并找出失衡节点,networkx,Python import randomfrom matplotlib import pyplot as plt import netwo ...
- BST 二叉搜索树 (动态建树与静态建树)
//判断两棵树是否是同一棵树:先序遍历和中序遍历对应相同或者中序遍历和后序遍历对应相同 //代码思路:两个数组分别存放建好的树的先序遍历以及正序遍历结果,然后对比是否相等 //题目意思 这题是 HDU ...
- 在BST(二叉搜索树)中查找介于给定范围之内的值
一.题目要求 编写一个递归函数printRange(),传入一个BST.一个较小的值和一个较大的值,按照顺序打印出介于两个值之间的所有结点.函数printRange()应尽可能少地访问BST的结点.( ...
- (一)BST树(二叉搜索树)
(一)BST树(二叉搜索树) 1.BST二叉搜索树 1.1BST树的定义 1 BST树也是一个二叉树,故满足递归定义; 2 其次每个节点只存在一个值; 3 需满足左子树值<=根值<=右子树 ...
- 五.树,二叉树,二叉搜索树(BST)和自平衡二叉搜索树(AVL)
1.树 树是一种数据结构 比如:目录结构 树是一种可以递归定义的数据结构 树是由n个节点组成的集合: 如果 n=0, 那这是一颗空树 如果 n>0, 那存在1个节点作为树的根节点,其他节点可以分 ...
- 二叉搜索树BST红黑树
二叉搜索树基础知识 提起红黑树咱们肯定要先说说这个二叉搜索树(BST) 二叉搜索树又叫二叉查找树,二叉排序树:它具有以下特点: 如果它的左子树不为空,则左子树上结点的值都小于根结点. 如果它的右子树不 ...
- 【数据结构】树与树的表示、二叉树存储结构及其遍历、二叉搜索树、平衡二叉树、堆、哈夫曼树与哈夫曼编码、集合及其运算
1.树与树的表示 什么是树? 客观世界中许多事物存在层次关系 人类社会家谱 社会组织结构 图书信息管理 分层次组织在管理上具有更高的效率! 数据管理的基本操作之一:查找(根据某个给定关键字K,从集合R ...
- C++数据结构和算法2 栈 双端/队列 冒泡选择插入归并快排 二三分查找 二叉树 二叉搜索树 贪婪 分治 动态规划
C++数据结构和算法2 栈 双端/队列 冒泡选择插入归并快排 二三分查找 二叉树 二叉搜索树 贪婪 分治 动态规划 博文末尾支持二维码赞赏哦 _ github 章3 Stack栈 和 队列Queue= ...
最新文章
- 计算机在人力资源管理中的应用论文,计算机人事管理论文
- 每日一皮:死循环的深刻理解...
- python整数类型进制表示_python2学习经验(一) 变量数据类型
- CAS Server(二):基于SpringBoot搭建客户端
- ValueError: slice index xxxx of dimension 0 out of bounds,详细分析。
- eclipse对maven项目进行打war包
- Spring Cloud微服务实战(五)-应用通信
- 分数DRL:在OptaPlanner中更快,更轻松
- python中遇到循环import即circular import的问题原理剖析及解决方案
- android与ndk交互,NDK-JNI与Java的交互 hello-world
- 力扣题目——705. 设计哈希集合
- linux下打开Mongodb命令行窗口,Linux系统下MongoDB的安装与基本操作
- 位置式数字PI控制器C语言
- nginx动静分离和资源隔离的网站搭建
- 地图制图领域使用计算机优点在于哪些方面,电子地图制图的运用与发展
- Ubuntu 12.04 Eclipse 3.7 紧凑布局样式美化
- Transformer-XL论文笔记
- 将波旁威士忌的整洁Mixins提升到新的水平
- SpringBoot:yaml配置及语法、yml数据读取、多环境开发控制
- php起始符大全,以下哪种标签不是 PHP 起始 / 结束符