树结构实际应用
二叉排序树
需求 :给一个数列 (7, 3, 10, 12, 5, 1, 9),要求能够高效的完成对数据的查询和添加
基本介绍:
二叉排序树 :BST: (Binary Sort(Search) Tree), 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点
比如针对前面的数据 (7, 3, 10, 12, 5, 1, 9) ,对应的二叉排序树为:
二叉排序树创建和遍历
一个数组创建成对应的二叉排序树,并使用 中序遍历 二叉排序树
package com.atguigu.binarysorttree;
/**
* @ClassName BinarySortTreeDemo
* @Author Jeri
* @Date 2022-02-26 17:58
* @Description 二叉排序树的创建 遍历 删除
*/
//创建节点类对象
class Node{
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
//重写 toString 方法
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//中序遍历
public void infixOrder(){
if(this.left != null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null){
this.right.infixOrder();
}
}
//添加节点
public void add(Node node){
//处理特殊情况
if(node == null){ return; }
//判断 node.value 与 this.value 的大小关系
if(this.value > node.value){
//向左寻找
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);
}
}
}
}
//创建二叉排序树 管理节点
class BinarySortTree{
private Node root;
public Node getRoot() {
return root;
}
public void setRoot(Node root) {
this.root = root;
}
//二叉树 添加节点
public void add(Node node){
if(root == null){
this.root = node;
}else{
this.root.add(node);
}
}
//二叉树 中序遍历
public void infixOrder(){
if(this.root != null){
this.root.infixOrder();
}else{
System.out.println("空树 无法遍历");
}
}
}
public class BinarySortTreeDemo {
public static void main(String[] args) {
int[] array = new int[]{7, 3, 10, 12, 5, 1, 9};
//创建二叉树对象
BinarySortTree bst = new BinarySortTree();
//添加节点对象
for(Integer temp:array){
bst.add(new Node(temp));
}
//中序遍历
System.out.println("中序遍历:----");
bst.infixOrder();
}
}
中序遍历:----
Node{value=1}
Node{value=3}
Node{value=5}
Node{value=7}
Node{value=9}
Node{value=10}
Node{value=12}
二叉排序树删除
二叉排序树的删除情况比较复杂,有下面三种情况需要考虑:
- 删除叶子节点
- 删除只有一颗子树的节点
- 删除有两颗子树的节点
思路分析:
删除叶子节点
- 需求先去找到要删除的结点 targetNode
- 找到 targetNode 的 父结点 parent
- 确定 targetNode 是 parent 的左子结点 还是右子结点
- 根据前面的情况来对应删除
删除只有一颗子树的节点
- 需求先去找到要删除的结点 targetNode
- 找到 targetNode 的 父结点 parent
- 确定 targetNode 的子结点是左子结点还是右子结点
- 确定 targetNode 的子结点是左子结点还是右子结点
- 根据前面的情况来对应删除
删除有两颗子树的节点
- 需求先去找到要删除的结点 targetNode
- 找到 targetNode 的 父结点 parent
- 从 targetNode 的右子树找到最小的结点、
- 用一个临时变量,将 最小结点的值保存 temp
- 删除该最小结点
- 将temp 的值赋给targetNode
package com.atguigu.binarysorttree;
/**
* @ClassName BinarySortTreeDemo
* @Author Jeri
* @Date 2022-02-26 17:58
* @Description 二叉排序树的创建 遍历 删除
*/
//创建节点类对象
class Node{
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
//重写 toString 方法
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//中序遍历
public void infixOrder(){
if(this.left != null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null){
this.right.infixOrder();
}
}
//添加节点
public void add(Node node){
//处理特殊情况
if(node == null){ return; }
//判断 node.value 与 this.value 的大小关系
if(this.value > node.value){
//向左寻找
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 Node search(int value){
if(this.value == value){
return this;
}else if(this.value > value){
//向左递归查找
if(this.left == null){
return null;
}
return this.left.search(value);
}else{
//向右递归
if(this.right == null){
return null;
}
return this.right.search(value);
}
}
//查找待删除节点的父节点
public Node searchParent(int value){
//判断当前节点
if((this.left != null && this.left.value == value) ||
(this.right != null && this.right.value == value)){
return this;
}else{
//当前节点左子节点不为空 且值 大于 查找值 向左查找
if(this.left != null && this.value > value){
return this.left.searchParent(value);
}else if(this.right != null && this.value < value){
//当前节点右子节点不为空 且值 小于 查找值
return this.right.searchParent(value);
}else{
//没有找到父节点
return null;
}
}
}
}
//创建二叉排序树 管理节点
class BinarySortTree{
private Node root;
public Node getRoot() {
return root;
}
public void setRoot(Node root) {
this.root = root;
}
//二叉树 添加节点
public void add(Node node){
if(root == null){
this.root = node;
}else{
this.root.add(node);
}
}
//二叉树 中序遍历
public void infixOrder(){
if(this.root != null){
this.root.infixOrder();
}else{
System.out.println("空树 无法遍历");
}
}
//二叉树 查找待删除节点
public Node search(int value){
if(root == null){
return null;
}else{
return this.root.search(value);
}
}
//二叉树 查找待删除节点的父节点
public Node searchParent(int value){
if(root == null){
return null;
}else{
return this.root.searchParent(value);
}
}
//二叉树 查找以当前节点为根节点的最小节点
public int delRightTreeMin(Node node){
Node target = node;
//二叉排序树 循环查找左节点即可
while(target.left != null){
target = target.left;
}
//退出循环时 即找到最小节点
//删除最小节点
deleteNode(target.value);
return target.value;
}
//二叉树 删除节点
public void deleteNode(int value){
if(this.root == null){
return;
}else{
//查找待删除节点
Node targetNode = search(value);
//没有查找到待删除节点
if(targetNode == null){
return;
}
//下列代码 默认 targetNode != null
if(this.root.left == null && this.root.right == null){
//此时 targetNode == root
root = null;
return;
}
//下列代码 默认 targetNode != null && targetNode != root
//查找 targetNode 的父节点
Node parent = searchParent(value);
if(targetNode.left == null && targetNode.right == null){
//待删除节点为叶子节点
//判断当前节点 与 父节点的关系
if(parent.left != null && parent.left.value == value){
//左子节点
parent.left = null;
}else if(parent.right != null && parent.right.value == 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 != null){
if(parent.left.value == value){
parent.left = targetNode.left;
}else{
parent.right = targetNode.left;
}
}else{
root = targetNode.left;
}
}else{
//待删除节点存在右子树
if(parent != null){
if(parent.left.value == value){
parent.left = targetNode.right;
}else{
parent.right = targetNode.right;
}
}else{
root = targetNode.right;
}
}
}
}
}
}
public class BinarySortTreeDemo {
public static void main(String[] args) {
int[] array = new int[]{7, 3, 10, 12, 5, 1, 9};
//创建二叉树对象
BinarySortTree bst = new BinarySortTree();
//添加节点对象
for(Integer temp:array){
bst.add(new Node(temp));
}
//中序遍历
System.out.println("中序遍历:----");
bst.infixOrder();
//测试一下删除叶子结点
bst.deleteNode(1);
bst.deleteNode(5);
bst.deleteNode(10);
bst.deleteNode(12);
bst.deleteNode(3);
bst.deleteNode(9);
bst.deleteNode(7);
System.out.println("删除节点后:------");
bst.infixOrder();
}
}
中序遍历:----
Node{value=1}
Node{value=3}
Node{value=5}
Node{value=7}
Node{value=9}
Node{value=10}
Node{value=12}
删除节点后:------
空树 无法遍历
平衡二叉(搜索)树
需求:
给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在.
BST 存在的问题分析
- 左子树全部为空,从形式上看,更像一个单链表
- 插入速度没有影响
- 查询速度明显降低(因为需要依次比较), 不能发挥 BST的优势
需要使用平衡二叉 (搜索) 树来解决
基本介绍:
平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为 AVL 树, 可以保证查询效率较高。
具有以下特点:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树
平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等
应用案例-左旋转
要求: 给你一个数列,创建出对应的平衡二叉树.数列 {4,3,6,5,7,8}
思路分析
说明:将节点6 的左子树插到 节点4的右边 同时将节点6设置为根结点
//以 this 为根节点对二叉树进行左旋转
public void leftRotate(){
//创建新的结点 属性为当前二叉树的根节点的属性
Node newNode = new Node(this.value);
//新节点的左子树 设置为 当前节点的左子树
newNode.left = this.left;
//新节点的右子树 设置为 当前节点右子树的左子树
newNode.right = this.right.left;
//当前节点的属性替换为右子节点的属性
this.value = this.right.value;
//当前节点的右子树 设置为 当前节点右子树的右子树
this.right = this.right.right;
//当前节点的左子树 设置为 新节点
this.left = newNode;
}
应用案例-右旋转
要求: 给你一个数列,创建出对应的平衡二叉树.数列 {10,12, 8, 9, 7, 6}
思路分析:
说明 : 将节点8 的右子树插到 节点10的左边 同时将节点8设置为根结点
//以 this 为根节点对二叉树进行右旋转
public void rightRotate(){
//创建新的节点 属性设置为当前二叉树根节点的属性
Node newNode = new Node(this.value);
//新节点的右子树 设置为 当前节点的右子树
newNode.right = this.right;
//新节点的左子树 设置为 当前节点的左子树的右子树
newNode.left = this.left.right;
//当前节点的属性替换为左子节点的属性
this.value = this.left.value;
//当前节点的左子树 设置为 当前节点的左子树的左子树
this.left = this.left.left;
//当前节点的右子树 设置为 新节点
this.right = newNode;
}
应用案例-双旋转
问题: 在某些情况下,单旋转不能完成平衡二叉树的转换
int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成 AVL 树
int[] arr = {2,1,6,5,7,3}; // 运行原来的代码可以看到,并没有转成 AVL 树
解决方法:
int[] arr = { 10, 11, 7, 6, 8, 9 } 插入节点9
- 以 this 为根节点的二叉树满足右旋转时
- 检查:this 的左子树的右子树高度 大于 this 的左子树的左子树的高度
- 先对 this的左子树 进行左旋转
- 然后再对 this 进行右旋转
int[] arr = {2,1,6,5,7,3} 插入节点3
- 以 this 为根节点的二叉树满足左旋转时
- 检查:this 的右子树的左子树高度 大于 this 的右子树的右子树的高度
- 先对 this的右子树 进行右旋转
- 然后再对 this 进行左旋转
代码展示:
package com.atguigu.avl;
import java.util.Arrays;
/**
* @ClassName AVLTreeDemo
* @Author Jeri
* @Date 2022-02-27 10:14
* @Description 平衡二叉树的构建
*/
//创建节点类
class Node{
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
//重写 toString()
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//中序遍历
public void infixOrder(){
if(this.left != null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null){
this.right.infixOrder();
}
}
//添加节点
public void add(Node node){
//处理特殊情况
if(node == null){
return;
}
//判断 node.value 与 this.value 大小关系
if(this.value > node.value){
//向左添加
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);
}
}
//添加完节点后 进行平衡
if(this.leftHeight() - this.rightHeight() > 1){
//(左子树的高度 - 右子树的高度) > 1 进行右旋转
if(this.left != null &&
this.left.rightHeight() > this.left.leftHeight()){
//this 的左子树的右子树高度 > this 的左子树的左子树的高度
//对 this的左子树 进行左旋转
this.left.leftRotate();
//this 进行右旋转
this.rightRotate();
}else{
//this 进行右旋转
this.rightRotate();
}
return;
}
if(this.rightHeight() - this.leftHeight() > 1){
//(右子树的高度 - 左子树的高度) > 1 进行左旋转
if(this.right != null &&
this.right.leftHeight() > this.right.rightHeight()){
//this的右子树的左子树的高度 > this的右子树的右子树的高度
//对 this 的右子树进行右旋转
this.right.rightRotate();
//对this 进行左旋转
this.leftRotate();
}else{
//对this 进行左旋转
this.leftRotate();
}
return;
}
}
//返回 以this为根节点的树的高度
public int height(){
return Math.max(this.left == null?0:this.left.height(),
this.right == null?0:this.right.height()) + 1;
}
//返回 以this为根节点的左子树的高度
public int leftHeight(){
if(this.left == null){
return 0;
}
return this.left.height();
}
//返回 以this为根节点的右子树的高度
public int rightHeight(){
if(this.right == null){
return 0;
}
return this.right.height();
}
//以 this 为根节点对二叉树进行左旋转
public void leftRotate(){
//创建新的结点 属性为当前二叉树的根节点的属性
Node newNode = new Node(this.value);
//新节点的左子树 设置为 当前节点的左子树
newNode.left = this.left;
//新节点的右子树 设置为 当前节点右子树的左子树
newNode.right = this.right.left;
//当前节点的属性替换为右子节点的属性
this.value = this.right.value;
//当前节点的右子树 设置为 当前节点右子树的右子树
this.right = this.right.right;
//当前节点的左子树 设置为 新节点
this.left = newNode;
}
//以 this 为根节点对二叉树进行右旋转
public void rightRotate(){
//创建新的节点 属性设置为当前二叉树根节点的属性
Node newNode = new Node(this.value);
//新节点的右子树 设置为 当前节点的右子树
newNode.right = this.right;
//新节点的左子树 设置为 当前节点的左子树的右子树
newNode.left = this.left.right;
//当前节点的属性替换为左子节点的属性
this.value = this.left.value;
//当前节点的左子树 设置为 当前节点的左子树的左子树
this.left = this.left.left;
//当前节点的右子树 设置为 新节点
this.right = newNode;
}
}
//创建平衡树
class AVLTree{
private Node root;
public Node getRoot() {
return root;
}
public void setRoot(Node root) {
this.root = root;
}
//平衡树 添加节点
public void add(Node node){
if(this.root == null){
this.root = node;
}else{
this.root.add(node);
}
}
//平衡树 中序遍历
public void infixOrder(){
if(this.root == null){
System.out.println("空树 无法遍历");
}else{
this.root.infixOrder();
}
}
}
public class AVLTreeDemo {
public static void main(String[] args) {
//int[] arr = {4,3,6,5,7,8};
//int[] arr = {2,1,6,5,7,3};
int[] arr = { 10, 11, 7, 6, 8, 9 };
System.out.println("原始数组:-----");
System.out.println(Arrays.toString(arr));
//创建一个 AVLTree对象
AVLTree avlTree = new AVLTree();
//添加结点
for(int i=0; i < arr.length; i++) {
avlTree.add(new Node(arr[i]));
}
//遍历
System.out.println("中序遍历");
avlTree.infixOrder();
System.out.println("在平衡处理~~");
System.out.println("当前的根结点=" + avlTree.getRoot());
System.out.println("树的高度=" + avlTree.getRoot().height());
System.out.println("树的左子树高度=" + avlTree.getRoot().leftHeight());
System.out.println("树的右子树高度=" + avlTree.getRoot().rightHeight());
}
}
原始数组:-----
[10, 11, 7, 6, 8, 9]
中序遍历
Node{value=6}
Node{value=7}
Node{value=8}
Node{value=9}
Node{value=10}
Node{value=11}
在平衡处理~~
当前的根结点=Node{value=8}
树的高度=3
树的左子树高度=2
树的右子树高度=2
原始数组:-----
[2, 1, 6, 5, 7, 3]
中序遍历
Node{value=1}
Node{value=2}
Node{value=3}
Node{value=5}
Node{value=6}
Node{value=7}
在平衡处理~~
当前的根结点=Node{value=5}
树的高度=3
树的左子树高度=2
树的右子树高度=2
参考文献
尚硅谷Java数据结构与java算法