目录
前言
在Java数据结构之二叉搜索树的学习中,我通过总结以及查阅资料,对以下问题有了更深一层的理解。
- 什么是二叉搜索树,二叉搜索树有什么样的性质以及特点?
- 二叉搜索树该如何进行查找,插入及删除?
- 二叉搜索树的时间复杂度该如何计算?
如果你在Java数据结构之二叉搜索树的学习中,也想对这些问题有一个更深的了解,请阅读这篇文章,或许会对你有所启发。
一、二叉搜索树的概念
1.定义
二叉搜索树又叫二叉排序树,它或是一颗空树,或是具有以下性质的二叉树
- 若它的左子树不为空,则左子树上的所有结点都小于根结点上的值
- 若它的右子树不为空,则右子树上的所有结点都大于根结点上的值
- 它的左右子树也分别是搜索树
2.特点
二叉搜索树中序遍历后会得到一个升序排列的数组,若是一棵树的中序遍历是一个升序排列的数组,也能说明这棵树是二叉搜索树,在二叉搜索树中,右子树的值永远大于左子树的值。
二、二叉搜索树的基本结构
public class TreeNode {
public long key;//要去寻找的结点
public TreeNode left;//左子树引用
public TreeNode right;//右子树引用
public TreeNode(long key){
this.key=key;
}
public String toString(){
return String.format("TreeNode(%d)",key);
}
}
二、二叉搜索树的操作
1.查找操作
根据二叉搜索树的定义,我们可知左子树的值要小于根节点的值,右子树的值要大于根节点的值,则我们
- 如果要找的数大于当前节点值,就去右子树找
- 如果要找的数小于当前节点值,就去左子树找
- 如果要找的数等于当前节点的值,则该结点就是我们要找的
- 如果整棵树遍历结束都没找到,则返回false
//查找操作
public boolean contains(long key) {
TreeNode cur = root;
while (cur != null) {//当前节点不为空
if (key == cur.key) {
return true;
} else if (key < cur.key) {
//如果要找的数小于当前节点值,就去左子树找
cur = cur.left;
} else {
//key>cur.key
//如果要找的数大于当前节点值,就去右子树找
cur = cur.right;
}
}
//如果整棵树遍历结束都找不到,则返回false
return false;
}
2.插入操作
在二叉搜索树中插入一个元素,首先要找到一个合适的插入位置,插入的新结点必须出现在原来空的地方
- 如果根节点的值与待插入节点的值相等,则返回false(因为待插入的值不能与搜索树中节点的值相等)
- 如果待插入的值比根节点的值小,则去左子树找
- 如果待插入的值比根节点的值大,则去右子树找
- 找到待插入节点的父节点后,创建新的节点进行插入
//插入操作
//如果key重复了,那么插入操作可能会失败
public boolean add(long key) {
//定义结点
TreeNode node = new TreeNode(key);
if (this.root == null) {
//如果根节点为空,则当前节点node作为根节点
this.root = node;
this.size = 1;
return true;
}
TreeNode parent = null;
TreeNode cur = this.root;
//parent始终指向cur的父节点,和链表插入类似,也需要记录指定位置的前一个结点
while (cur != null) {
if (key == cur.key) {
return false;//key重复了
} else if (key < cur.key) {
//如果要找到的结点小于当前节点,就去左子树寻找
parent = cur;
cur = cur.left;
} else {
//key>cur.key
//如果要找的数大于当前节点,就去右子树找
parent = cur;
cur = cur.right;
}
}
//cur==null
//保证parent一定是cur的双亲
//断言parent!=null
//要做的工作,把node插入到parent的孩子中
if(key<parent.key){
//左插
parent.left=node;
}else{
//右插
parent.right=node;
}
this.size++;
return true;
}
3.删除操作
二叉搜索树的删除操作需要考虑多种情况,首先要找到删除的数和父节点,考虑:
- 要删除的数是其父节点,要删除节点的节点是叶子节点,这时只需要将parent.right或者是parent.right设置为null,然后java自动回收机制会自动删除cur结点;
- 如果要删除的结点有parent的左子树或者右子树其中之一,则只需要将parent.left或者parent.right设置为cur.right或者cur.left即可;
- 要删除的结点有既有左子树又有右子树,在这种情况下,我们只需要找到待删结点的右子树中值最小的结点,将其删除并且获取其值,用这个值替换待删结点的值即可
//删除操作
public boolean remove(long key){
TreeNode parent=null;
TreeNode cur=root;
//如果根节点为空,当前节点作为根
while(cur!=null){
//要删除的节点不为空,则去判断该节点是父节点还是叶子结点
if(key==cur.key){
deleteNode(parent,cur);
this.size--;
return true;
}else if(key<cur.key){
parent=cur;
cur=cur.left;
}else{
parent=cur;
cur=cur.right;
}
}
return false;
}
private void deleteNode(TreeNode parent,TreeNode node){
//如果要删除的点左子树为空,就将它的右子树作为根
if(node.left==null){
if(node==this.root){
this.root=node.right;
}else if(parent.left==node){
parent.left= null;
}else{
parent.right=node.right;
}
//如果要删除的点右子树为空,就将它的左子树作为根
}else if(node.right==null){
if(node==this.root){
this.root=node.left;
}else if(parent.left==node){
parent.left=node.left;
}else{
parent.right=node.left;
}
}else{
//node.left!=null&&node.right!=null,左右子树均不为空
//采用替换删除的方式
//此处选择右子树中最小的一个(右子树中最左的一个)
TreeNode toDeleteParent=node;
TreeNode toDelete=node.right;
// 断言:toDelete != null
// 一路朝左走(toDelete.left == null 停下来)
while (toDelete.left != null) {
toDeleteParent = toDelete;
toDelete = toDelete.left;
}
// toDelete 是我们要删除的结点
node.key = toDelete.key;
// 删除 toDelete
// 断言:toDelete.left == null
// 断言:toDelete != this.root
// toDeleteParent.left | right = toDelete.right
// toDeleteParent == node => 断言 toDeleteParent.right == toDelete
// else => 断言 toDeleteParent.left == toDelete
if (toDeleteParent.left == toDelete) {
toDeleteParent.left = toDelete.right;
} else {
// toDeleteParent.right == toDelete
toDeleteParent.right = toDelete.right;
}
}
}
三、二叉搜索树的时间复杂度
分析二叉搜索树的时间复杂度,要知道它的最优情况和最坏情况分别是什么?
- 最优情况下,二叉搜索树为完全二叉树,其时间复杂度为二叉树的高度,O()
- 最差情况下,二叉搜索树退化为单分支树,其时间复杂度为O(n)
完整代码
/*
若它的左子树不为空,则左子树上的所有结点都小于根结点上的值
若它的右子树不为空,则右子树上的所有结点都大于根结点上的值
它的左右子树也分别时搜索树
*/
/*
关于搜索树的类
验证该对象的合法性
1.size>=0
size和root之间的关系
2.size==通过遍历root得到的结点个数
3.root维护的树是搜索树,任取结点
中序遍历是有序的,key不重复——搜索树的规则
搜索树——>中序是有序的
中序是有序的——>搜索树
*/
public class BSTree {
private TreeNode root;
private int size;
public BSTree() {
this.root = null;
this.size = 0;
}
//查找操作
public boolean contains(long key) {
TreeNode cur = root;
while (cur != null) {//当前节点不为空
if (key == cur.key) {
return true;
} else if (key < cur.key) {
//如果要找的数小于当前节点值,就去左子树找
cur = cur.left;
} else {
//key>cur.key
//如果要找的数大于当前节点值,就去右子树找
cur = cur.right;
}
}
//如果整棵树遍历结束都找不到,则返回false
return false;
}
//插入操作
//如果key重复了,那么插入操作可能会失败
public boolean add(long key) {
//定义结点
TreeNode node = new TreeNode(key);
if (this.root == null) {
//如果根节点为空,则当前节点node作为根节点
this.root = node;
this.size = 1;
return true;
}
TreeNode parent = null;
TreeNode cur = this.root;
//parent始终指向cur的父节点,和链表插入类似,也需要记录指定位置的前一个结点
while (cur != null) {
if (key == cur.key) {
return false;//key重复了
} else if (key < cur.key) {
//如果要找到的结点小于当前节点,就去左子树寻找
parent = cur;
cur = cur.left;
} else {
//key>cur.key
//如果要找的数大于当前节点,就去右子树找
parent = cur;
cur = cur.right;
}
}
//cur==null
//保证parent一定是cur的双亲
//断言parent!=null
//要做的工作,把node插入到parent的孩子中
if(key<parent.key){
//左插
parent.left=node;
}else{
//右插
parent.right=node;
}
this.size++;
return true;
}
//删除操作
public boolean remove(long key){
TreeNode parent=null;
TreeNode cur=root;
//如果根节点为空,当前节点作为根
while(cur!=null){
//要删除的节点不为空,则去判断该节点是父节点还是叶子结点
if(key==cur.key){
deleteNode(parent,cur);
this.size--;
return true;
}else if(key<cur.key){
parent=cur;
cur=cur.left;
}else{
parent=cur;
cur=cur.right;
}
}
return false;
}
private void deleteNode(TreeNode parent,TreeNode node){
//如果要删除的点左子树为空,就将它的右子树作为根
if(node.left==null){
if(node==this.root){
this.root=node.right;
}else if(parent.left==node){
parent.left= null;
}else{
parent.right=node.right;
}
//如果要删除的点右子树为空,就将它的左子树作为根
}else if(node.right==null){
if(node==this.root){
this.root=node.left;
}else if(parent.left==node){
parent.left=node.left;
}else{
parent.right=node.left;
}
}else{
//node.left!=null&&node.right!=null,左右子树均不为空
//采用替换删除的方式
//此处选择右子树中最小的一个(右子树中最左的一个)
TreeNode toDeleteParent=node;
TreeNode toDelete=node.right;
// 断言:toDelete != null
// 一路朝左走(toDelete.left == null 停下来)
while (toDelete.left != null) {
toDeleteParent = toDelete;
toDelete = toDelete.left;
}
// toDelete 是我们要删除的结点
node.key = toDelete.key;
// 删除 toDelete
// 断言:toDelete.left == null
// 断言:toDelete != this.root
// toDeleteParent.left | right = toDelete.right
// toDeleteParent == node => 断言 toDeleteParent.right == toDelete
// else => 断言 toDeleteParent.left == toDelete
if (toDeleteParent.left == toDelete) {
toDeleteParent.left = toDelete.right;
} else {
// toDeleteParent.right == toDelete
toDeleteParent.right = toDelete.right;
}
}
}
public static void main(String[] args) {
BSTree tree = new BSTree();
long[] keys = { 5, 1, 6, 8, 3, 9, 0, 2, 4, 7 };
for (long key : keys) {
tree.add(key);
}
System.out.println(tree.remove(11));
}
}
总结
以上就是今天二叉搜索树的相关内容,希望我们都能在二叉搜索树的代码练习中掌握有关知识!