文章目录
二叉排序树:
接下来是二叉排序树的结构.
我们以一个无序数组{4,10,12,5,1,3,0}为例,按照如下步骤得到一个二叉排序树
那么按照下图我来讲解一下:
首先遇到4,将4作为二叉树根节点,遇到10,由于10>4因此将其作为其双亲结点的右孩子.12>4,因此先向右递归找到10,发现12>10,因此12作为10的右孩子.
5>4,因此向右递归找到10,10>5,因此5作为10这个结点的左孩子.重复操作即可得到二叉排序树.
由上可知二叉排序树是在查找时进行插入或删除,因此它是一种动态查找表.
代码实现:
由于二叉排序树基于二叉树,因此其结点设置于二叉树无二
结构
class BSNode {
int data;
static int[]arr=new int[100]; //这个数组可以不用 只不过是中序遍历后可以得到一个有序数组
static int index=0; //数组下标
BSNode left;
BSNode right;
static {
Arrays.fill(arr,Integer.MIN_VALUE);
}
public static int[] getArr() {
return arr;
}
public BSNode(int data) {
this.data = data;
}
@Override
public String toString() {
return "BSNode{" +
"data=" + data +
'}';
}
}
之后就是插入操作,由上面的概念可以得到每次需要插入一个结点的时候就需要递归的遍历当前结点与待插入结点的大小.
添加操作
//添加节点
public void add(BSNode node) {
if (node == null) {
return;
}
if (this.data > node.data) {//this.data代表的是当前节点的值
if (this.left == null) {//判断左孩子是不是空
this.left = node; //是则直接插入进入
} else {
this.left.add(node);//否则递归
}
} else { //同上
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
}
}
查找操作
根据二叉查找树的特性,我们可以轻易的实现查找某个数据的代码
/**
* 该函数用于查找需要删除的结点
*
* @param data 需要删除结点的data域
* @return 如果找到则返回要删除的结点 否则返回null
*/
public BSNode search(int data) {
if (this.data == data) {//当前结点就是要查找的结点
return this;
} else if (data < this.data) {//左递归
if (this.left == null) {
return null;
}
return this.left.search(data);
} else {//右递归
if (this.right == null) {
return null;
}
return this.right.search(data);
}
}
删除操作
要想实现删除操作首先需要我们先找到要删除的结点,用上面的search即可,但是删除了要删除的结点后,若要删除的结点还有左孩子或右孩子,那么会导致数据的丢失,因此我们还需要找到删除结点的双亲结点,并将双亲结点的左孩子域或右孩子域指向被删除结点的左孩子域或右孩子域.
完整代码直接贴出,因为主要实现代码就在于删除结点
package algorithm.DataStruct.Tree;
import java.util.Arrays;
/**
* @author: Serendipity
* Date: 2022/2/9 21:59
* Description:二叉排序树'
* 非叶子节点的左孩子小于非叶子节点
* 右孩子大于非叶子节点
*/
public class BinarySortTree {
public static void main(String[] args) {
Tree tree = new Tree(new BSNode(7));
tree.add(new BSNode(3));
tree.add(new BSNode(10));
tree.add(new BSNode(12));
tree.add(new BSNode(5));
tree.add(new BSNode(1));
tree.add(new BSNode(9));
tree.add(new BSNode(2));
tree.delete(2);
tree.delete(10);
tree.midTraverse();
System.out.println(Arrays.toString(tree.getRoot().arr));
}
}
class Tree {
private BSNode root;
public BSNode getRoot() {
return root;
}
public Tree(BSNode root) {
this.root = root;
}
public void add(BSNode node) {
if (root == null) {
this.root = node;
} else {
root.add(node);
}
}
public void midTraverse() {
if (root == null) {
System.out.println("根节点为空");
return;
} else {
root.midTraverse();
}
}
public BSNode search(int data) {
if (this.root == null) {
return null;
} else {
return this.root.search(data);
}
}
public BSNode searchParent(int data) {
if (this.root == null) {
return null;
} else {
return this.root.searchParent(data);
}
}
//删除节点
public int getRightTreeMinValue(BSNode node) {
BSNode minNode = node;
while (minNode.left != null) {
minNode = minNode.left;
}
delete(minNode.data);
return minNode.data;
}
/**
* 第一种情况:删除叶子节点
* 1:需要先找到要删除的节点 2:然后找到该节点的双亲结点 3:确定要删除的结点是
* 双亲结点的左孩子还是右孩子 4:parent.left/right=null(删除)
* 第二种情况:删除只有一颗子树的结点
* 1:需要先找到要删除的节点 2:然后找到该节点的双亲结点 3:确定要删除的结点是
* 双亲结点的左孩子还是右孩子 4:确定要删除的结点有的是左孩子还是右孩子
* parent.left/right=node.left/right
* 第三种情况:删除有两个子树的结点
* 1:需要先找到要删除的节点 2:然后找到该节点的双亲结点
* 3:从要删除的的结点的右子树找到值最小的结点
* 4:用一个临时变量保存这个最小的结点的值 5:删除最小值结点 6:要删除的结点.value=temp
*/
public void delete(int data) {
if (this.root == null) {//如果根节点为空直接返回
return;
} else {
BSNode delNode = search(data);//否则先查找到要删除的结点
if (delNode == null) {//删除结点不存在直接return
System.out.println("没有找到要删除的结点");
return;
}
//删除结点不为空但是root没有左右孩子 那么说明要删除的结点就是root
if (this.root.left == null && this.root.right == null) {
this.root = null;
return;
}
//由于只有根节点没有双亲结点 所以如果走到这一步说明要删除的不是根节点
BSNode parent = searchParent(data);//所以后面无需判断parent==null
if (delNode.left == null && delNode.right == null) {//要删除的结点是叶子节点
if (parent.left != null && parent.left.data == data) {
parent.left = null; //要删除的是左孩子
} else if (parent.right != null && parent.right.data == data) {
parent.right = null; //要删除右孩子
}//因为满足且一定满足或 所以就先进行且运算
} else if (delNode.left != null && delNode.right != null) {//要删除的结点有两个子树
int min = getRightTreeMinValue(delNode.right);
delNode.data = min;
} else {//要删除的结点只有一个子树
if (delNode.left != null) {//要删除结点有的是左孩子
if (parent != null) {//由于如果只有两个结点的情况时要删除根节点的时候那么parent==null,因此会报错
if (parent.left == delNode) {//要删除结点是父节点的左孩子
parent.left = delNode.left;
} else {
parent.right = delNode.left;
}
} else {
root = delNode.left;
}
} else {//要删除结点有的是右孩子
if (parent != null) {
if (parent.left == delNode) {
parent.left = delNode.right;
} else {
parent.right = delNode.right;
}
} else {
root = delNode.right;
}
}
}
}
}
}
class BSNode {
int data;
static int[] arr = new int[100]; //这个数组可以不用 只不过是中序遍历后可以得到一个有序数组
static int index = 0; //数组下标
BSNode left;
BSNode right;
static {
Arrays.fill(arr, Integer.MIN_VALUE);
}
public static int[] getArr() {
return arr;
}
public BSNode(int data) {
this.data = data;
}
@Override
public String toString() {
return "BSNode{" +
"data=" + data +
'}';
}
//添加节点
public void add(BSNode node) {
if (node == null) {
return;
}
if (this.data > node.data) {//this.data代表的是当前节点的值
if (this.left == null) {//判断左孩子是不是空
this.left = node; //是则直接插入进入
} else {
this.left.add(node);//否则递归
}
} else { //同上
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
}
}
//中续遍历
public void midTraverse() {
if (this.left != null) {
this.left.midTraverse();
}
System.out.println(this);
arr[index++] = this.data;
if (this.right != null) {
this.right.midTraverse();
}
}
/**
* 用于找到要删除结点的双亲结点
*
* @param data 要删除结点的值
* @return null则代表没有双亲结点 否则返回双亲结点
*/
public BSNode searchParent(int data) {
//当前结点的右孩子或左孩子就是要查找的结点则直接返回this当前结点
if ((this.left != null && this.left.data == data) ||
(this.right != null && this.right.data == data)) {
return this;
} else {//左递归
if (data < this.data && this.left != null) {
return this.left.searchParent(data);
} else if (data >= this.data && this.right != null) {//右递归
return this.right.searchParent(data);
} else {//还是没有找到返回null
return null;
}
}
}
/**
* 该函数用于查找需要删除的结点
*
* @param data 需要删除结点的data域
* @return 如果找到则返回要删除的结点 否则返回null
*/
public BSNode search(int data) {
if (this.data == data) {//当前结点就是要查找的结点
return this;
} else if (data < this.data) {//左递归
if (this.left == null) {
return null;
}
return this.left.search(data);
} else {//右递归
if (this.right == null) {
return null;
}
return this.right.search(data);
}
}
}
总结:
例如出现严重的单斜树,如下.
基于上面可能出现的特殊情况,有了对二叉排序树的升级操作,平衡二叉树(AVL-Tree).
平衡二叉树(AVL树):
需要注意的是:平衡二叉树的前提是它首先是一个二叉排序树.
因此图二由于59>58却在其左子树位置因此其不是平衡二叉树
图三由于58这个结点的左子树高度为2,右子树高度为0,二者差值大于1因此不是平衡二叉树.
距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡子树.如下图,当插入结点37时,距离他最近的平衡因子绝对值超过1的结点是58,所以从58开始以下的子树为最小不平衡子树.
实现原理:
代码实现:
package algorithm.DataStruct.Tree;
/**
* @author: Serendipity
* Date: 2022/2/10 16:30
* Description:平衡二叉树是对二叉排序树的升级(AVL树)
* 是为了防止由于树二叉树的结点都向同一方向延伸而导致的变成类似于单链表的
* 单斜树,这虽然不影响插入速度但是查找速度由于依旧需要比较左子树或者右子树是否为空
* 而导致了速度甚至慢于单链表 因此引入了平衡因子 要求左右子树的高度差不大于绝对值1(-1<=x<=1)
*/
public class BalancedBinaryTree {
public static void main(String[] args) {
// int arr[] = {4, 3, 6, 5, 7, 8};
// int arr[] = {10,12,8,9,7,6};
int arr[] = {10,11,7,6,8,9};
AVLTree tree = new AVLTree();
for (int i = 0; i < arr.length; i++) {
tree.add(new BBTNode(arr[i]));
}
tree.delete(10);
tree.midTraverse();
System.out.println(tree.getRoot().leftTreeHeight());
System.out.println(tree.getRoot().rightTreeHeight());
}
}
class AVLTree {
private BBTNode root;
public AVLTree() {
}
public BBTNode getRoot() {
return root;
}
public AVLTree(BBTNode root) {
this.root = root;
}
public void add(BBTNode node) {
if (root == null) {
this.root = node;
} else {
root.add(node);
}
}
public void midTraverse() {
if (root == null) {
System.out.println("根节点为空");
return;
} else {
root.midTraverse();
}
}
public BBTNode search(int data) {
if (this.root == null) {
return null;
} else {
return this.root.search(data);
}
}
public BBTNode searchParent(int data) {
if (this.root == null) {
return null;
} else {
return this.root.searchParent(data);
}
}
public int getRightTreeMinValue(BBTNode node) {
BBTNode minNode = node;
while (minNode.left != null) {
minNode = minNode.left;
}
delete(minNode.data);
return minNode.data;
}
//删除节点
/**
* 第一种情况:删除叶子节点
* 1:需要先找到要删除的节点 2:然后找到该节点的双亲结点 3:确定要删除的结点是
* 双亲结点的左孩子还是右孩子 4:parent.left/right=null(删除)
* 第二种情况:删除只有一颗子树的结点
* 1:需要先找到要删除的节点 2:然后找到该节点的双亲结点 3:确定要删除的结点是
* 双亲结点的左孩子还是右孩子 4:确定要删除的结点有的是左孩子还是右孩子
* parent.left/right=node.left/right
* 第三种情况:删除有两个子树的结点
* 1:需要先找到要删除的节点 2:然后找到该节点的双亲结点
* 3:从要删除的的结点的右子树找到值最小的结点
* 4:用一个临时变量保存这个最小的结点的值 5:删除最小值结点 6:要删除的结点.value=temp
*/
public void delete(int data) {
if (this.root == null) {
return;
} else {
BBTNode delNode = search(data);
if (delNode == null) {
System.out.println("没有找到要删除的结点");
return;
}
if (this.root.left == null && this.root.right == null) {
this.root = null;
return;
}//由于只有根节点没有双亲结点 所以如果走到这一步说明要删除的不是根节点
BBTNode parent = searchParent(data);//所以后面无需判断parent==null
if (delNode.left == null && delNode.right == null) {//要删除的结点是叶子节点
if (parent.left != null && parent.left.data == data) {
parent.left = null;
} else if (parent.right != null && parent.right.data == data) {
parent.right = null;
}//因为满足且一定满足或 所以就先进行且运算
} else if (delNode.left != null && delNode.right != null) {//要删除的结点有两个子树
int min = getRightTreeMinValue(delNode.right);
delNode.data = min;
} else {//要删除的结点只有一个子树
if (delNode.left != null) {//要删除结点有的是左孩子
if (parent != null) {
if (parent.left == delNode) {//要删除结点是父节点的左孩子
parent.left = delNode.left;
} else {
parent.right = delNode.left;
}
} else {
root = delNode.left;
}
} else {//要删除结点有的是右孩子
if (parent != null) {
if (parent.left == delNode) {
parent.left = delNode.right;
} else {
parent.right = delNode.right;
}
} else {
root = delNode.right;
}
}
}
}
}
}
class BBTNode {
int data;
BBTNode left;
BBTNode right;
public BBTNode(int data) {
this.data = data;
}
@Override
public String toString() {
return "BBTNode{" +
"data=" + data +
'}';
}
public int leftTreeHeight() {
if (left == null) {
return 0;
}
return left.getHeight();
}
public int rightTreeHeight() {
if (right == null) {
return 0;
}
return right.getHeight();
}
//返回以当前结点为根节点的树的高度
public int getHeight() {
return Math.max(left == null ? 0 : left.getHeight(), right == null ? 0 : right.getHeight()) + 1;
}
//左旋转 当右子树高度大于左子树时需要左旋转
public void leftSpin() {
//1:创建新的结点,并将根节点的值赋值给他
BBTNode leftSpinNode = new BBTNode(this.data);
//2:把新的结点的左子树设置为当前结点的左子树
leftSpinNode.left = this.left;
//3:把新结点的右子树设置成当前结点的右子树的左子树
leftSpinNode.right = this.right.left;
//4:把当前结点的值替换为右子树的值
this.data = this.right.data;
//5:把当前结点的右子树设置成右子树的右子树
this.right = this.right.right;
//6:把当前结点的左子树设置成新的结点
this.left = leftSpinNode;
}
//右旋转 当左子树的高度-右子树的高度>1时需要右旋转
public void rightSpin() {
//1:创建一个新的结点,并将根节点的值付给他
BBTNode rightSpinNode = new BBTNode(this.data);
//2:把新结点的右子树设置为当前结点的右子树
rightSpinNode.right = this.right;
//3:把新结点的左子树设置为当前结点左子树的右子树
rightSpinNode.left = this.left.right;
//4:把当前结点的值换为左子节点的值
this.data = this.left.data;
//5:把当前结点的左子树设置为左子树的左子树
this.left = this.left.left;
//6:把当前结点的右子树设置为新结点
this.right = rightSpinNode;
}
//添加节点
public void add(BBTNode node) {
if (node == null) {
return;
}
if (this.data > node.data) {//this.data代表的是当前节点的值
if (this.left == null) {//判断左孩子是不是空
this.left = node; //是则直接插入进入
} else {
this.left.add(node);//否则递归
}
} else { //同上
if (this.right == null) {
this.right = node;
} else {
this.right.add(node);
}
}
//当添加完一个结点后右子树高读-左子树高度>1则需要左旋转
if (rightTreeHeight() - leftTreeHeight() > 1) {
//如果符合左旋转条件 如果根节点的右子树的左子树的高度大于根节点右子树的右子树的高度
//那么需要先对根节点右子树进行右旋转之后再进行左旋转即可
if (right != null && right.leftTreeHeight() > right.rightTreeHeight()) {
right.rightSpin();
leftSpin();
} else {
leftSpin();
}
return; //由于如果进行了上面的操作那么就已经平衡了
//因此直接return退出即可
}
//右旋转 当左子树的高度-右子树的高度>1时需要右旋转
if (leftTreeHeight() - rightTreeHeight() > 1) {
//如果符合右旋转条件 如果她的(根节点)左子树的右子树高度大于她的(根节点)左子树的左子树的高度
//那么需要先对当前结点(根节点)的左子树进行左旋转 然后再进行右旋转
if (left != null && left.rightTreeHeight() > left.leftTreeHeight()) {
left.leftSpin();
rightSpin();
} else {
rightSpin();
}
}
}
//中续遍历
public void midTraverse() {
if (this.left != null) {
this.left.midTraverse();
}
System.out.println(this);
if (this.right != null) {
this.right.midTraverse();
}
}
/**
* 用于找到要删除结点的双亲结点
*
* @param data 要删除结点的值
* @return null则代表没有双亲结点 否则返回双亲结点
*/
public BBTNode searchParent(int data) {
if ((this.left != null && this.left.data == data) ||
(this.right != null && this.right.data == data)) {
return this;
} else {
if (data < this.data && this.left != null) {
return this.left.searchParent(data);
} else if (data >= this.data && this.right != null) {
return this.right.searchParent(data);
} else {
return null;
}
}
}
/**
* 该函数用于查找需要删除的结点
*
* @param data 需要删除结点的data域
* @return 如果找到则返回要删除的结点 否则返回null
*/
public BBTNode search(int data) {
if (this.data == data) {
return this;
} else if (data < this.data) {
if (this.left == null) {
return null;
}
return this.left.search(data);
} else {
if (this.right == null) {
return null;
}
return this.right.search(data);
}
}
}