1 二叉排序树概念
二叉查找树 又叫 二叉排序树,二叉搜索树。Binary Search Tree(BST)
- 对于二叉查找树中的每一个节点如果存在左节点,左节点的值一定小于该节点的值
- 对于二叉查找树中的每一个节点如果存在右节点,右节点的值一定大于该节点的值
- 也就是说对于二叉查找树中的任何一个非叶子节点,左节点值小于当前节点值,右节点值大于当前节点值
- 二叉查找树的任何一个非叶子节点的左子树中的任何一个节点的值都要小于当前节点值,右子树中的任何一个节点的值都要大于当前节点值。
- 如果对二叉查找树进行中序遍历,可以得到一个从小到大的序列 ,所以也叫作二叉排序树
下图所示为一棵二叉查找树
优点
- 二叉排序树是一种比较有用的折衷方案。
- 数组的搜索比较方便,可以直接用下标,但删除或者插入某些元素就比较麻烦。
- 链表与之相反,删除和插入元素很快,但查找很慢。
- 二叉排序树就既有链表的好处,也有数组的好处。
- 在处理大批量的动态的数据是比较有用。
2基本思路
树的创建和添加:
root为空则root设置为新添加的节点,不为空 就比较当前节点和需添加进来的节点值,若新的节点值小于当前节点则接着与左节点比较,如果当前节点的左节点为空,则将新节点设置为当前节点的左节点,左节点不为空,接着与左节点比较。。。。若新的节点值大于当前节点则接着与右节点比较,如果当前节点的右节点为空,则将新节点设置为当前节点的右节点,右节点不为空,接着比较下去。
树的删除: 分为四种情况
- 树只由一个根节点构成 删除时要判断父节点为空&&左节点为空&&右节点为空
- 要删除的节点没有左和右节点 删除只需要将父节点的指向该节点的引用设置为null
- 要删除的节点只有一个节点 将要删除的节点的子节点设置为父节点相应的子节点
- 需要删除的节点有两个子节点 将要删除节点的右子树中的最小值节点替换要删除的节点,如果最小值节点有右节点的 话,并将最小值节点的右节点设置为其父节点相应子节点 或者 将左子树中的最大值节 点替换要删除的节点,如果最大值节点有左节点,并将最大值节点的左节点设置为其父节 点相应的子节点。
注:第四种情况最小(大)节点替换被删除节点时 不仅value要被替换,count也要被替换 ,否则count值将会被保留
2代码实现
节点类
package com.sorttree;
/**
* @Author WYMY
*
*/
public class Node {
int value;
Node left;
Node right;
//如果节点重复count+1 没有重复count=0
int count=0;
public Node(int value) {
this.value = value;
}
//节点添加 递归
public void add(Node node) {
if (null == node) {
return;
}
//新添加的节点值小于当前节点值
if (node.value < this.value) {
if (this.left == null) {
this.left = node;
} else {
this.left.add(node);
}
//新添加的节点值小于当前节点值
} else if(node.value>this.value){
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
}else {
//值相同的话,数量标记变量+1
this.count++;
}
}
//根据值查询节点
public Node search(int value) {
if (this.value == value) {
return this;
} else if (value < this.value) {
if (left == null) {
return null;
}
return this.left.search(value);
} else {
if (right == null) {
return null;
}
return this.right.search(value);
}
}
//查询父节点方法1
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;
}
}
}
//查询父节点方法2 递归
public Node searchP(int value) {
if (this.value > value) {
Node newNode = this.left;
if (newNode == null) {
return null;
}
if (newNode.value == value) {
return this;
} else {
return newNode.searchP(value);
}
} else {
Node newNode = this.right;
if (newNode == null) {
return null;
}
if (newNode.value == value) {
return this;
} else {
return newNode.searchP(value);
}
}
}
}
此处的count变量是用来记录重复数据的数量 ,遇到重复添加的数据不会往树中增加新的节点,而是使已存在节点的count+1
树的创建(也就是节点的不断添加),树节点的删除 ,中序遍历
package com.sorttree;
/**
* @Author WYMY
*/
public class BinarySortree {
//根节点
Node root;
/**
* 添加节点
* @param node
*/
void add(Node node) {
if (root == null) {
root = node;
} else {
root.add(node);
}
}
/**
* 中序遍历
*/
public void midSort() {
midSort(root);
}
public void midSort(Node node) {
if (node == null) {
return;
}
midSort(node.left);
//根据count输出重复节点
for (int i = 0; i <= node.count; i++) {
System.out.println(node.value);
}
midSort(node.right);
}
/**
* 根据值查询节点
* @param value
* @return
*/
public Node search(int value) {
if (null == root) {
return null;
}
return root.search(value);
}
/**
* 删除节点
* @param i
*/
public void delete(int i) {
if (root == null) {
return;
}
Node target = search(i);
if (target == null) {
return;
}
Node parent = searchParent(i);
//如果根节点没有子节点(只有根节点的树)
if (parent == null && target.right == null && target.left == null) {
root = null;
return;
}
if (target.right == null && target.left == null) {
if (target.value == parent.left.value) {
parent.left = null;
} else {
parent.right = null;
}
} else if (target.right != null && target.left != null) {
//删除节点有两个子节点
Node node = deleteMin(target.right);
//替换目标节点内容
target.value = node.value;
target.count = node.count;
//需要删除的节点只有一个子节点
} else {
//目标节点左节点不为空
if (target.left != null) {
if (target.value == parent.left.value) {
parent.left = target.left;
} else {
parent.right = target.left;
}
} else {//目标节点右节点不为空
if (target.value == parent.left.value) {
parent.left = target.right;
} else {
parent.right = target.right;
}
}
}
// root.delete(i);
}
/**
* 删除树中最小节点
*
* @param node
* @return
*/
private Node deleteMin(Node node) {
Node target = node;
//找到最小节点
while (target.left != null) {
target = target.left;
}
//如果目标节点有右节点
delete(target.value);
return target;
}
/**
* 查询父节点
* @param value
* @return
*/
public Node searchParent(int value) {
if (root == null) {
return null;
}
return root.searchParent(value);
}
}
中序遍历:在中序遍历的时候需要注意的是要根据count的值来循环输出Node的值
测试类
package com.sorttree;
/**
* @Author WYMY
* @Date 2019/4/28 22:11
*/
public class Test {
public static void main(String[] args) {
int[]arr={7,7,7,9,10,12,5,1,9};
BinarySortree binarySortree=new BinarySortree();
for(int i:arr){
binarySortree.add(new Node(i));
}
binarySortree.midSort();
// Node node=binarySortree.search(7);
// System.out.println(node.value);
// binarySortree.delete(12);
//删除value=7的节点
binarySortree.delete(7);
System.out.println("----------");
binarySortree.midSort();
// Node node1 = binarySortree.searchParent(10);
// System.out.println(node1.value);
}
}
//output
1
5
7
7
7
9
9
10
12
----------
1
5
9
9
10
12
3 平衡二叉查找树
二叉查找树很有可能某个节点的两个子树深度相差甚大,这就会早上造成效率低下,所以需要对二叉树进行平衡处理。
即对于任意节点左右子树的深度相差不超过一。
- 如图所示右边的树是最坏情况的树,虽然也是一棵二叉查找树,但是和单项链表是没有区别了,查询访问效率很低
- 左边的二叉树是一颗平衡二叉查找树,任意一个节点的左右子树深度相差不超过一,这样能大大降低深度,减少访问次数
- 要想访问93对应节点,左边只需要比较3次,右边则是要比较6次,相差一倍
示例A
示例B
如上图情况相较于上个情况比较复杂,一次右旋转无法实现平衡,所以要在右旋转之前对左子树进行处理(一次左旋转)如下
- 要使二叉查找树变得平衡 只需要在二叉查找树再添加节点时进行改动,比较当前节点左右子树深度进行左旋或者右旋
- 这里找了张右旋的图 ,这个是比较简单的情况,只进行一次旋转,复杂情况要进行2次旋转
- 二叉树开始时,对于根节点来说,左子树深度是3,右子树是1,所以不平衡,则对其进行右旋
- 如图所示 当前节点(66)的 左子树深度-右子树深度大于1 进行右旋 ,如果当前节点的左子树(60)存在右子树(75),就把右子树(75)设置为当前节点(66)的左子树,然后把(60)的右子树设置为当前节点(66)。一次旋转就完成了
左旋转后的左子树 和调整后的树
然后再对当前节点进行一次右旋转可以最后得到平衡后的树
代码调整
只需要对添加节点的方法进行补充修改 添加完一个节点后就对左右子树深度差进行判断,不符合差值小于等于1,就进行平衡调整。
public void add(Node node) {
if (null == node) {
return;
}
if (node.value < this.value) {
if (this.left == null) {
this.left = node;
} else {
this.left.add(node);
}
} else if(node.value>this.value){
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
}else {
this.count++;
}
//右旋转
if(this.leftHigh()-this.rightHigh()>=2){
if(this.left!=null&&this.left.leftHigh()-this.left.rightHigh()<0){
//双旋转
leftRotate(this.left);
rightRotate(this);
}else {
//单旋转
rightRotate(this);
}
//左旋转
}else if(this.rightHigh()-this.leftHigh()>=2){
if(this.right!=null&&this.right.leftHigh()-this.right.rightHigh()>0){
//双旋转
rightRotate(this.right);
leftRotate(this);
}else {
//单旋转
leftRotate(this);
}
}
}
//左旋转
private void leftRotate(Node node) {
//使新节点等于当前节点
Node newNode=new Node(node.value);
newNode.count=node.count;
//将新节点的左节点设置为当前节点的左节点
newNode.left=node.left;
//将新节点的右节点设置为当前节点的右节点的左节点
newNode.right=node.right.left;
//使当前节点的右节点取代当前节点
this.value=this.right.value;
this.count=this.right.count;
//把新节点的右子树设置为原来右节点的右子树
this.right=this.right.right;
//把当前节点的左子树设置为新节点
this.left=newNode;
}
//右旋转
private void rightRotate(Node node) {
//使新节点等于当前节点
Node newNode=new Node(node.value);
newNode.count=node.count;
//将新节点的右节点设置为当前节点的右节点
newNode.right=node.right;
//将新节点的左节点设置为当前节点的左节点的右节点
newNode.left=node.left.right;
//使当前节点的左节点取代当前节点
this.value=this.left.value;
this.count=this.left.count;
//把新节点的左子树设置为原来左节点的左子树
this.left=this.left.left;
//把当前节点的右子树设置为新节点
this.right=newNode;
}
//返回左子树深度
public int leftHigh(){
if(this.left==null){
return 0;
}
return this.left.high();
}
//返回右子树深度
public int rightHigh(){
if(this.right==null){
return 0;
}
return this.right.high();
}
//返回树的深度
public int high(){
return Math.max(this.left==null?0:this.left.high(),this.right==null?0:this.right.high())+1;
}
红黑树是一种平衡的二叉查找树。