二叉排序树
先看一个需求
给你一个数列(7,3, 10, 12,5,1,9),要求能够高效的完成对数据的查询和添加。
方案我们一般首先会想到数组的方式
数组未排序,优点:直接在数组尾添加,速度快。缺点:查找速度慢.
数组排序,优点:可以使用二分查找,查找速度快,缺点:为了保证数组有序在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢。
链式存储呢?
不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体
移动。
我们前面说到树存储可以有效解决,到底是为什么呢?
二叉排序树
介绍
二叉排序树: BST: (Binary Sort(Search) Tree),对于二叉排序树的任何一个非叶子节点,
要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点
二叉排序树的创建和遍历
package 二叉排序树;
public class BinarySortTree {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr= {7,3,10,12,5,1,9};
BinarySortTreeDemo binarySortTree = new BinarySortTreeDemo();
//循环添加节点到二叉树
for (int i = 0; i < arr.length; i++) {
binarySortTree.addNode(new Node(arr[i]));
}
// System.out.println("中序遍历二叉树");
binarySortTree.infixOrder();
}
}
//创建二叉排序树
class BinarySortTreeDemo{
private Node root;
//添加节点的方法
public void addNode(Node node){
if(root == null){
root = node;
}else{
root.addNode(node);
}
}
//中序遍历
public void infixOrder(){
if(root != null){
root.infixOrder();
}else{
System.out.println("空树");
}
}
}
class Node{
int value;
Node left;
Node right;
public Node(int value) {
super();
this.value = value;
}
@Override
public String toString() {
return "Node [value=" + value + "]";
}
//添加节点的方法
//递归的形式添加节点,需要满足二叉排序树
public void addNode(Node node){
if(node == null){
return;
}
//判断传入的节点值,跟当前子树根节点值的关系
if(node.value < this.value){
//如果当前节点的左子节点为空
if(this.left == null){
this.left = node;
}else
{
this.left.addNode(node);//递归添加
}
}else{
if(this.right == null){
this.right = node;
}else{
this.right.addNode(node);
}
}
}
//中序遍历
public void infixOrder(){
if(this.left != null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null){
this.right.infixOrder();
}
}
}
二叉排序树的删除
这里面有很多情况:
1.删除叶子结点
2.删除只有一颗子树的节点
3.删除有两颗子树的节点
思路
情况一:删除叶子节点
1.需要先找到待删除的节点targetNode
2.找到待删除节点的父节点parent(考虑是否有父节点)
3.判断targetNode是parent的左子节点还是右子节点
4.根据前面,对应删除
情况二:删除只有一颗子树的节点
1.需要先找到待删除的节点targetNode
2.找到待删除节点的父节点parent(考虑是否有父节点)
3.确定targetNode的子节点是左子节点还是右子节点
4.确定targetNode是parent的左子节点还是右子节点
5.如果targetNode有左子节点
1)targetNode是parent的左子节点
parent.left = targetNode = left;
2)targetNode是parent的右子节点
parent.left = targetNode .right;
6.如果targetNode有右子节点同理
情况三:删除有两颗子树的节点
1.需要先找到待删除的节点targetNode
2.找到待删除节点的父节点parent(考虑是否有父节点)
3.从targetNode的右子树找到最小的节点
4.用临时变量将最小节点的值保存起来 temp
5.删除最小节点
6.targetNode.value = temp
删除节点代码
package 二叉排序树;
public class BinarySortTree {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr= {7,3,10,12,5,1,9,2};
BinarySortTreeDemo binarySortTree = new BinarySortTreeDemo();
//循环添加节点到二叉树
for (int i = 0; i < arr.length; i++) {
binarySortTree.addNode(new Node(arr[i]));
}
// System.out.println("中序遍历二叉树");
binarySortTree.infixOrder();
//测试删除叶子节点
// binarySortTree.delNode(2);
// System.out.println("删除2节点后");
// binarySortTree.infixOrder();
binarySortTree.delNode(10);
binarySortTree.infixOrder();
}
}
//创建二叉排序树
class BinarySortTreeDemo{
private Node root;
//添加节点的方法
public void addNode(Node node){
if(root == null){
root = node;
}else{
root.addNode(node);
}
}
//中序遍历
public void infixOrder(){
if(root != null){
root.infixOrder();
}else{
System.out.println("空树");
}
}
//查找要删除的节点
public Node search(int value){
if(root == null){
return null;
}else{
return root.search(value);
}
}
//查找待删除节点的父节点
public Node searchParent(int value){
if(root == null){
return null;
}else{
return root.searchParent(value);
}
}
//编写方法
/**
* 返回最小节点值,并且删除以node为根节点的二叉排序树的最小节点
* @param node 当做一颗二叉排序树的根节点
* @return 返回的以node为根节点的二叉排序树的最小节点的值
*/
public int delRightTreeMin(Node node){
Node target = node;
//循环查找左子节点,就会找到最小值
while(target.left != null){
target = target.left;
}
//这是target就指向了最小节点
//删除最小节点
delNode(target.value);
return target.value;
}
//删除叶子结点的方法
public void delNode(int value){
if(root == null){
return;
}else{
//1.需要先去找到待删除节点
Node targetNode = search(value);
//如果没有找到
if(targetNode == null){
return;
}
//如果当前这课二叉排序树只有一个节点
if(root.left == null&& root.right == null){
root = null;
return;
}
//去查找targetNode的父节点
Node parent = searchParent(value);
//如果待删除的节点是叶子结点
if(targetNode.left == null && targetNode.right == null){
//如果targetNode是parent的左子节点
if(parent.left != null && parent.left.value == targetNode.value){
parent.left = null;
}else if(parent.right != null && parent.right.value == targetNode.value){
parent.right = null;
}
}else if(targetNode.left!=null && targetNode.right != null){
int minValue = delRightTreeMin(targetNode.right);
targetNode.value = minValue;
}else{
//删除只有一个子树的节点
//如果删除的节点有左子节点
if(targetNode.left != null){
if(parent.left.value == targetNode.value){
parent.left = targetNode.left;
}else{
parent.right = targetNode.left;
}
}else{
//要删除的节点有右子节点
if(parent.left.value == targetNode.value){
parent.left = targetNode.right;
}else{
parent.right = targetNode.right;
}
}
}
}
}
}
class Node{
int value;
Node left;
Node right;
public Node(int value) {
super();
this.value = value;
}
@Override
public String toString() {
return "Node [value=" + value + "]";
}
/**
* 查找待删除的节点
* @param value 待删除节点的值
* @return
*/
public Node search(int value){
if(value == this.value){
return this;
}else if(value < this.value){//应该向左子树递归查找
if(this.left != null){
return this.left.search(value);
}else{
return null;
}
}else{
if(this.right == null){
return null;
}else{
return this.right.search(value);
}
}
}
/**
* 查找待删除节点的父节点
* @param value 待删除节点的值
* @return 返回待删除节点的父节点
*/
public Node searchParent(int value){
if((this.left !=null && this.left.value == value) || (this.right != null && this.right.value == value)){
//当前节点就是待删除节点的父节点
return this;
}else{
//如果查找的值,小于当前节点的值,且当前节点的左子节点不为空
if(value < this.value && this.left != null){
return this.left.searchParent(value);
}else if(value >= this.value && this.right != null){
return this.right.searchParent(value);
}else{
return null;//没有找到父节点
}
}
}
//添加节点的方法
//递归的形式添加节点,需要满足二叉排序树
public void addNode(Node node){
if(node == null){
return;
}
//判断传入的节点值,跟当前子树根节点值的关系
if(node.value < this.value){
//如果当前节点的左子节点为空
if(this.left == null){
this.left = node;
}else
{
this.left.addNode(node);//递归添加
}
}else{
if(this.right == null){
this.right = node;
}else{
this.right.addNode(node);
}
}
}
//中序遍历
public void infixOrder(){
if(this.left != null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null){
this.right.infixOrder();
}
}
}
注意事项
删除多个节点的时候一定要注意顺序,有的顺序可以正常删除,但是有的顺序会报空指针错误,原因就是删除的顺序有问题,导致我们删除方法中的判断出了问题,就是根节点这个地方,他没有父节点,但是我们判断了,所以在这出错