BST主要是用来实现查找表的功能,通过Key来找到相应的Value,类似于日常使用的字典。不仅可以高效CRUD,数据维护,还可以用来找min,max,floor,ceil,rank,select等等
^v^作者此处就不去做Key-Value的演示了,仅仅用Value来替代……
首先,需要定义一个class作为实体
public class BSTNode {
private int num;//数据
BSTNode leftNode;//左子树
BSTNode rightNode;//右子树
BSTNode parent;/*表示父节点,根节点的就为null表示,
主要是为了删除节点的时候能够顺利进行*/
public BSTNode() {
super();
}
public BSTNode(int num) {
super();
this.num = num;
this.leftNode = null;
this.rightNode = null;
this.parent = null;
}
get和set
@Override
public String toString() {
。。。/*注意要把打印parent的字段去掉,不然会陷入循环模式,
从父到子,再从子到父...这种无限读取模式*/
}
}
接下来就是重点,CRUD(虽然说笔者此处没有改,不过读者可以自己实现,其实就是覆盖操作,也看不出什么效果)
这是定义一些全局变量:
public static BSTNode result;
// 查找某个值找不到的时候,用来存放最后一个节点,方便插入使用
static Queue<BSTNode> queue = new LinkedList<BSTNode>();
遍历
深度优先如下:
/**
* 采用中序的方式,遍历整个tree,递归注意结束的条件
* @param root待遍历的二叉树的根节点
*/
public static void Traverse(BSTNode root) {
if (root == null) {
return;
}
Traverse(root.getLeftNode());
System.out.print(root.getNum() + " ");
Traverse(root.getRightNode());
}
广度优先如下:
/**
* 采用广度优先的方式来遍历整棵树
* @param root要遍历的tree
*/
public static void TraverseSpan(BSTNode root) {
if (root == null) {
return;
}
queue.offer(root);
//因为add()和remove()方法在失败的时候会抛出异常(不推荐)
while (!queue.isEmpty()) {
BSTNode poll = queue.poll();
//返回第一个元素,并在队列中删除
if (poll.getLeftNode() != null) {//加入该节点的左节点
queue.offer(poll.getLeftNode());
}
if (poll.getRightNode() != null) {//加入该节点的右节点
queue.offer(poll.getRightNode());
}
System.out.print(poll.getNum() + " ");
}
}
查找
查找max
/**
* 获得传入BST的最大值节点
*
* @param root
* @return
*/
public static BSTNode GetMax(BSTNode root) {
if (root == null)
return null;
while (root.getRightNode() != null) {
root = root.getRightNode();
}
return root;
}
查找min
/**
* 获得传入BST的最小值节点
*
* @param root
* @return
*/
public static BSTNode GetMin(BSTNode root) {
if (root == null)
return null;
while (root.getLeftNode() != null) {
root = root.getLeftNode();
}
return root;
}
查找任意数
/**
* 查看平衡二叉树里面是否有要查找的目标值target
*
* @param root待查找的平衡二叉树的根节点
* @param target要查找的目标值
* @param parent当前传入Find函数的平衡二叉树的根节点的父节点,
* 一开始没有,就传入为null,为平衡二叉树的其他操作做铺垫
* @return 找到与否
*/
public static boolean Find(BSTNode root, int target, BSTNode parent) {
if (root == null) {
// 遍历到了叶子节点,找不到,返回false,同时作为递归的结束条件
result = parent;
return false;
}
if (root.getNum() == target) { // Find your target
result = root;
return true;
} else if (root.getNum() > target) {
// your target is too small
return Find(root.getLeftNode(), target, root);
} else { // your target is too big
return Find(root.getRightNode(), target, root);
}
}
插入
/**
* 向BST插入一个节点
*
* @param root要插入节点的BST的根节点
* @param value待插入的值
* @return插入是否成功,如果该数已经存在就插入失败
*/
public static boolean Insert(BSTNode root, int value) {
if (Find(root, value, null)) {
// 找到要插入的值,不能出现重复的值,所以返回false
return false;
}
// 找不到的情况,此时通过上面找不到时得到的result知道要插入的点的父节点
BSTNode nodeTemp = new BSTNode();
nodeTemp.setNum(value);
nodeTemp.setParent(result);
if (result.getNum() > value) {
// value比node的值小,放在node的左子树
result.setLeftNode(nodeTemp);
} else {
result.setRightNode(nodeTemp);
}
return true;
}
删除
/**
* 删除任意一个节点
*
* @param root待操作的BST
* @param value欲删除的数值
* @return删除成功返回true,否则返回false
*/
public static boolean Delete(BSTNode root, int value) {
if (root == null) {// 判断传入的tree是否为空
return false;
}
if (root.getNum() == value) {
// 如果目标数值是根节点,进入删除的子函数
return DeleteNode(root);
} else if (root.getNum() > value) {
// 目标数值比较小就进入左子树进行再判断
return Delete(root.getLeftNode(), value);
} else {// 目标数值比较大就进入右子树进行再判断
return Delete(root.getRightNode(), value);
}
}
/**
* 要删除的节点,进行具体的操作
*
* @param root待操作的BST
* @return删除成功返回true,否则返回false
*/
public static boolean DeleteNode(BSTNode root) {
BSTNode parent = root.getParent();
// 通过在实体对象里面添加一个属性来简化操作
if (root.getLeftNode() == null) {// 没有左子树的情况
if (parent.getLeftNode() == root) {
parent.setLeftNode(root.getRightNode());
} else {
parent.setRightNode(root.getRightNode());
}
return true;
}
if (root.getRightNode() == null) {// 没有右子树的情况
if (parent.getLeftNode() == root) {
parent.setLeftNode(root.getLeftNode());
} else {
parent.setRightNode(root.getLeftNode());
}
return true;
}
BSTNode getMax = GetMax(root.getRightNode());
// 左右子树都有的情况
if (parent.getLeftNode() == root) {
parent.setLeftNode(getMax);
} else {
parent.setRightNode(getMax);
}
getMax.setLeftNode(root.getLeftNode());
// 不要漏了root的左子树还有再接上去
DeleteNode(getMax);
return true;
}
测试如下:
由于一开始还没写insert,所以要自己new对象,然后再自己配置他们的关系
public static void main(String[] args) {
//new 一些对象出来
BSTNode n1 = new BSTNode(59);
BSTNode n2 = new BSTNode(40);
BSTNode n3 = new BSTNode(69);
BSTNode n4 = new BSTNode(20);
BSTNode n5 = new BSTNode(49);
BSTNode n6 = new BSTNode(66);
BSTNode n7 = new BSTNode(45);
BSTNode n8 = new BSTNode(52);
BSTNode n9 = new BSTNode(41);
//手动配置对象之间的关系
n9.setParent(n7);
n8.setParent(n5);
n7.setParent(n5);
n7.setLeftNode(n9);
n6.setParent(n3);
n5.setParent(n2);
n5.setLeftNode(n7);
n5.setRightNode(n8);
n4.setParent(n2);
n3.setParent(n1);
n3.setLeftNode(n6);
n2.setParent(n1);
n2.setLeftNode(n4);
n2.setRightNode(n5);
n1.setLeftNode(n2);
n1.setRightNode(n3);
/*也可以自动注入
* BSTNode n1 = new BSTNode(40);
*
* int[] temp = new int[]{59,69,66,49,20,45,41,52}; for(int i : temp){
* Insert(n1, i); }
*/
Traverse(n1);//深度优先
System.out.println();
System.out.println("*******广度优先也可以:********");
TraverseSpan(n1);
/*
* System.out.println();
*
* int getMax = GetMax(n1).getNum(); System.out.println("max="+getMax);
*
* int getMin = GetMin(n1).getNum(); System.out.println("min="+getMin);
*
* boolean Find = Find(n1, 59, null); System.out.println(Find);
* System.out.println(result);
*
*
* boolean insert = Insert(n1, 65); if(insert){
* System.out.println("****插入如下*********"); Traverse(n1); }
*
* System.out.println();
*
* boolean delete = Delete(n1, 49); if(delete){
* System.out.println("*****删除后如下:**********"); Traverse(n1); }
*/
}
引申:
1、中序遍历可以起到从小到大排序的作用,后序遍历可以用于析构函数
2、实现rank()【看某个元素的排名】的思路是,在BSTNode里面再加上一个属性,表示该节点加上其子节点的个数,通过给属性就能很容易得获取到排名,重点在于insert()和remove()函数里面如何维护该属性
3、解决插入元素如果是重复的key的情况,其实可以再增加一个属性,用来记录有多少重复的值,进而使得重复的key也可以存在
4、当待插入的数据是近乎有序的时候,该BST会退化为链表,此时可以通过运用平衡二叉树,2-3,AVL来优化
总结:
1、编写BST的过程中,一开始笔者没有使用一个parent的属性,于是在删除一个节点的地方卡住了,正是加入这个parent,其实对原来已经编写的代码没什么影响,不过,一下子就解决了删除节点的问题,所以,如果以后有什么问题可以借助这种思路,试着去增加一个属性,或者函数调用的时候,加多一个变量,可能该问题就很容易解决了。
2、递归的解决思路,什么时候走什么步骤,有什么范围,试着体验一下。