java树结构的应用_Java数据结构与算法 day10 树结构实际应用(三)

第10章 树结构的实际应用

本章源码:https://github.com/name365/Java-Data-structure

二叉排序树

二叉排序树(BST)的介绍

先看一个需求:

给你一个数列 (7, 3, 10, 12, 5, 1, 9),要求能够高效的完成对数据的查询和添加。

解决方案分析:

使用数组

数组未排序, 优点:直接在数组尾添加,速度快。 缺点:查找速度慢.

数组排序,优点:可以使用二分查找,查找速度快,缺点:为了保证数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢。

8c852e0e90e59d1c0cb5a396dc255fdf.png

使用链式存储-链表

不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动。

使用二叉排序树

二叉排序树介绍:

二叉排序树:BST: (Binary Sort(Search) Tree), 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。

特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点

比如针对前面的数据 (7, 3, 10, 12, 5, 1, 9) ,对应的二叉排序树为:

56f80134dbd618614c5442b225466773.png

二叉排序树(BST)创建和遍历

一个数组创建成对应的二叉排序树,并使用中序遍历二叉排序树,比如: 数组为 Array(7, 3, 10, 12, 5, 1, 9) , 创建成对应的二叉排序树为 :

f0b0a07907701909ed4faf1f630af9c4.png

public class BinarySortTreeTest {

public static void main(String[] args) {

int arr[] = {7,3,10,12,5,1,9};

BinaryTree tree = new BinaryTree();

//循环的添加结点到二叉排序树

for(int i = 0 ;i< arr.length;i++){

tree.add(new Node(arr[i]));

}

//中序遍历二叉排序树

System.out.println("中序遍历此树:");

tree.infixOrder(); //1,3,5,7,9,10,12

}

}

//创建Node结点

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 add(Node node){

if(node == null){

return;

}

//判断传入的结点的值,和当前子树的根结点的值的关系

if(node.value < this.value){

if(this.left == null){//如果当前结点左子结点为null

this.left = node;

}else{

//递归的向左子树添加

this.left.add(node);

}

}else{//添加的节点的值大于当前结点的值

if(this.right == null){

this.right = node;

}else{

//递归的向右子树添加

this.right.add(node);

}

}

}

//中序遍历

public void infixOrder(){

if(this.left != null){

this.left.infixOrder();

}

System.out.println(this);

if(this.right != null){

this.right.infixOrder();

}

}

}

//创建二叉排序树

class BinaryTree{

private Node root;

//添加结点的方法

public void add(Node node){

if(root == null){

root = node;//如果root为空则直接让root指向node

}else{

root.add(node);

}

}

//遍历方法

public void infixOrder(){

if(root != null){

root.infixOrder();

}else{

System.out.println("二叉排序树为空!!!");

}

}

}

二叉排序树删除结点思路图解

二叉排序树的删除情况比较复杂,有下面三种情况需要考虑

1)删除叶子节点 (比如:2, 5, 9, 12)

2)删除只有一颗子树的节点 (比如:1)

3)删除有两颗子树的节点. (比如:7, 3,10 )

873d59ba8da959cb01083ee88ac5d39e.png

二叉排序树删除叶子结点

图解 二叉排序树 删除结点的 三种情况

第一种情况: 删除叶子节点 (比如:2, 5, 9, 12)

思路

(1) 需求先去找到要删除的结点 targetNode

(2) 找到targetNode 的 父结点 parent

(3) 确定 targetNode 是 parent的左子结点 还是右子结点

(4) 根据前面的情况来对应删除

左子结点 parent.left = null

右子结点 parent.right = null;

代码实现如下:

public class BinarySortTreeTest {

public static void main(String[] args) {

int arr[] = {7,3,10,12,5,1,9};

BinaryTree tree = new BinaryTree();

//循环的添加结点到二叉排序树

for(int i = 0 ;i< arr.length;i++){

tree.add(new Node(arr[i]));

}

//中序遍历二叉排序树

System.out.println("中序遍历此树:");

tree.infixOrder(); //1,3,5,7,9,10,12

//测试一下删除叶子节点

tree.delNode(2);

tree.delNode(5);

tree.delNode(9);

System.out.println("删除后的节点:");

tree.infixOrder();

}

}

//创建Node结点

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 add(Node node){

if(node == null){

return;

}

//判断传入的结点的值,和当前子树的根结点的值的关系

if(node.value < this.value){

if(this.left == null){//如果当前结点左子结点为null

this.left = node;

}else{

//递归的向左子树添加

this.left.add(node);

}

}else{//添加的节点的值大于当前结点的值

if(this.right == null){

this.right = node;

}else{

//递归的向右子树添加

this.right.add(node);

}

}

}

//中序遍历

public void infixOrder(){

if(this.left != null){

this.left.infixOrder();

}

System.out.println(this);

if(this.right != null){

this.right.infixOrder();

}

}

//查找要删除的节点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午8:43:01

* @param value 希望删除的结点的值

* @return 如果找到该值返回,未找到返回null

*/

public Node search(int value){

if(value == this.value){//说明找到了

return this;

}else if(value < this.value){//查找的值小于当前结点的值,向左子树查找

if(this.left == null){//左子结点为空

return null;

}

return this.left.search(value);

}else{//查找的值不小于当前结点的值,向右子树查找

if(this.right == null){

return null;

}

return this.right.search(value);

}

}

//查找要删除结点的父结点

/**

*

* @param value 希望删除的结点的值

* @return 返回的是要删除的结点的父结点,如果没有就返回null

*/

public Node searchP(int value){

//如果当前结点是要删除的结点的父结点,如果没有就返回null

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.searchP(value);//向左子树查找

}else if(value >= this.value && this.right != null){

return this.right.searchP(value);//向右子树递归查找

}else {

return null;//未找到父结点

}

}

}

}

//创建二叉排序树

class BinaryTree{

private Node root;

//添加结点的方法

public void add(Node node){

if(root == null){

root = node;//如果root为空则直接让root指向node

}else{

root.add(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 searchP(int value){

if(root == null){

return null;

}else{

return root.searchP(value);

}

}

//删除节点

public void delNode(int value){

if(root == null){

return;

}else{

//1.需求先去找到要删除的结点 targetNode

Node targetNode = search(value);

//如果没有找到要删除的结点

if(targetNode ==null){

return;

}

//如果我们发现当前这颗二叉排序树只有一个结点

if(root.left == null && root.right == null) {

root = null;

return;

}

//去找到targetNode的父结点

Node parent = searchP(value);

//如果要删除的节点为叶子节点

if(targetNode.left == null && targetNode.right == null){

//判断targetNode是父节点的左子结点,还是右子节点

if(parent.left != null && parent.left.value == value){//左子节点

parent.left = null;

}else if(parent.right != null && parent.right.value == value){//右子节点

parent.right = null;

}

}

}

}

}

BST删除有一颗子树的结点

第二种情况: 删除只有一颗子树的节点 比如 1

思路

(1) 需求先去找到要删除的结点 targetNode

(2) 找到targetNode 的 父结点 parent

(3) 确定targetNode 的子结点是左子结点还是右子结点

(4) targetNode 是 parent 的左子结点还是右子结点

(5) 如果targetNode 有左子结点

5.1 如果 targetNode 是 parent 的左子结点

parent.left = targetNode.left;

5.2 如果 targetNode 是 parent 的右子结点

parent.right = targetNode.left;

(6) 如果targetNode 有右子结点

6.1 如果 targetNode 是 parent 的左子结点

parent.left = targetNode.right;

6.2 如果 targetNode 是 parent 的右子结点

parent.right = targetNode.right

代码实现如下:

public class BinarySortTreeTest {

public static void main(String[] args) {

int arr[] = {7,3,10,12,5,1,9,0};

BinaryTree tree = new BinaryTree();

//循环的添加结点到二叉排序树

for(int i = 0 ;i< arr.length;i++){

tree.add(new Node(arr[i]));

}

//中序遍历二叉排序树

System.out.println("中序遍历此树:");

tree.infixOrder(); //0,1,3,5,7,9,10,12

//测试一下删除叶子节点

tree.delNode(1);

System.out.println("删除后的节点:");

tree.infixOrder();//0,3,5,7,9,10,12

}

}

//创建Node结点

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 add(Node node){

if(node == null){

return;

}

//判断传入的结点的值,和当前子树的根结点的值的关系

if(node.value < this.value){

if(this.left == null){//如果当前结点左子结点为null

this.left = node;

}else{

//递归的向左子树添加

this.left.add(node);

}

}else{//添加的节点的值大于当前结点的值

if(this.right == null){

this.right = node;

}else{

//递归的向右子树添加

this.right.add(node);

}

}

}

//中序遍历

public void infixOrder(){

if(this.left != null){

this.left.infixOrder();

}

System.out.println(this);

if(this.right != null){

this.right.infixOrder();

}

}

//查找要删除的节点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午8:43:01

* @param value 希望删除的结点的值

* @return 如果找到该值返回,未找到返回null

*/

public Node search(int value){

if(value == this.value){//说明找到了

return this;

}else if(value < this.value){//查找的值小于当前结点的值,向左子树查找

if(this.left == null){//左子结点为空

return null;

}

return this.left.search(value);

}else{//查找的值不小于当前结点的值,向右子树查找

if(this.right == null){

return null;

}

return this.right.search(value);

}

}

//查找要删除结点的父结点

/**

*

* @param value 希望删除的结点的值

* @return 返回的是要删除的结点的父结点,如果没有就返回null

*/

public Node searchP(int value){

//如果当前结点是要删除的结点的父结点,如果没有就返回null

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.searchP(value);//向左子树查找

}else if(value >= this.value && this.right != null){

return this.right.searchP(value);//向右子树递归查找

}else {

return null;//未找到父结点

}

}

}

}

//创建二叉排序树

class BinaryTree{

private Node root;

//添加结点的方法

public void add(Node node){

if(root == null){

root = node;//如果root为空则直接让root指向node

}else{

root.add(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 searchP(int value){

if(root == null){

return null;

}else{

return root.searchP(value);

}

}

//删除节点

public void delNode(int value){

if(root == null){

return;

}else{

//1.需求先去找到要删除的结点 targetNode

Node targetNode = search(value);

//如果没有找到要删除的结点

if(targetNode ==null){

return;

}

//如果我们发现当前这颗二叉排序树只有一个结点

if(root.left == null && root.right == null) {

root = null;

return;

}

//去找到targetNode的父结点

Node parent = searchP(value);

//如果要删除的节点为叶子节点

if(targetNode.left == null && targetNode.right == null){

//判断targetNode是父节点的左子结点,还是右子节点

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){//删除有两颗子树的节点

}else{//删除只有一个字树的节点

//如果要删除的结点有左子结点

if(targetNode.left != null) {

if(parent != null) {

//如果 targetNode 是 parent 的左子结点

if(parent.left.value == value) {

parent.left = targetNode.left;

} else {//targetNode 是 parent 的右子结点

parent.right = targetNode.left;

}

} else {

root = targetNode.left;

}

}else{//如果要删除的结点有右子结点

if(parent != null){

//如果 targetNode 是 parent 的左子结点

if(parent.left.value == value){

parent.left = targetNode.right;

}else{//如果 targetNode 是 parent 的右子结点

parent.right = targetNode.right;

}

}else{

root = targetNode.right;

}

}

}

}

}

}

BST删除有二颗子树的结点

情况三: 删除有两颗子树的节点. (比如:7, 3,10 )

思路

(1) 需求先去找到要删除的结点 targetNode

(2) 找到targetNode 的 父结点 parent

(3) 从targetNode 的右子树找到最小的结点

(4) 用一个临时变量,将 最小结点的值保存 temp = 11

(5) 删除该最小结点

(6) targetNode.value = temp

代码实现如下:

public class BinarySortTreeTest {

public static void main(String[] args) {

int arr[] = {7,3,10,12,5,1,9,0};

BinaryTree tree = new BinaryTree();

//循环的添加结点到二叉排序树

for(int i = 0 ;i< arr.length;i++){

tree.add(new Node(arr[i]));

}

//中序遍历二叉排序树

System.out.println("中序遍历此树:");

tree.infixOrder(); //0,1,3,5,7,9,10,12

//测试一下删除叶子节点

tree.delNode(7);

System.out.println("删除后的节点:");

tree.infixOrder();//0,1,3,5,9,10,12

}

}

//创建Node结点

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 add(Node node){

if(node == null){

return;

}

//判断传入的结点的值,和当前子树的根结点的值的关系

if(node.value < this.value){

if(this.left == null){//如果当前结点左子结点为null

this.left = node;

}else{

//递归的向左子树添加

this.left.add(node);

}

}else{//添加的节点的值大于当前结点的值

if(this.right == null){

this.right = node;

}else{

//递归的向右子树添加

this.right.add(node);

}

}

}

//中序遍历

public void infixOrder(){

if(this.left != null){

this.left.infixOrder();

}

System.out.println(this);

if(this.right != null){

this.right.infixOrder();

}

}

//查找要删除的节点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午8:43:01

* @param value 希望删除的结点的值

* @return 如果找到该值返回,未找到返回null

*/

public Node search(int value){

if(value == this.value){//说明找到了

return this;

}else if(value < this.value){//查找的值小于当前结点的值,向左子树查找

if(this.left == null){//左子结点为空

return null;

}

return this.left.search(value);

}else{//查找的值不小于当前结点的值,向右子树查找

if(this.right == null){

return null;

}

return this.right.search(value);

}

}

//查找要删除结点的父结点

/**

*

* @param value 希望删除的结点的值

* @return 返回的是要删除的结点的父结点,如果没有就返回null

*/

public Node searchP(int value){

//如果当前结点是要删除的结点的父结点,如果没有就返回null

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.searchP(value);//向左子树查找

}else if(value >= this.value && this.right != null){

return this.right.searchP(value);//向右子树递归查找

}else {

return null;//未找到父结点

}

}

}

}

//创建二叉排序树

class BinaryTree{

private Node root;

//添加结点的方法

public void add(Node node){

if(root == null){

root = node;//如果root为空则直接让root指向node

}else{

root.add(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 searchP(int value){

if(root == null){

return null;

}else{

return root.searchP(value);

}

}

//删除节点

public void delNode(int value){

if(root == null){

return;

}else{

//1.需求先去找到要删除的结点 targetNode

Node targetNode = search(value);

//如果没有找到要删除的结点

if(targetNode ==null){

return;

}

//如果我们发现当前这颗二叉排序树只有一个结点

if(root.left == null && root.right == null) {

root = null;

return;

}

//去找到targetNode的父结点

Node parent = searchP(value);

//如果要删除的节点为叶子节点

if(targetNode.left == null && targetNode.right == null){

//判断targetNode是父节点的左子结点,还是右子节点

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 minVa = delRightT(targetNode.right);

targetNode.value = minVa;

}else{//删除只有一个字树的节点

//如果要删除的结点有左子结点

if(targetNode.left != null) {

if(parent != null) {

//如果 targetNode 是 parent 的左子结点

if(parent.left.value == value) {

parent.left = targetNode.left;

} else {//targetNode 是 parent 的右子结点

parent.right = targetNode.left;

}

} else {

root = targetNode.left;

}

}else{//如果要删除的结点有右子结点

if(parent != null){

//如果 targetNode 是 parent 的左子结点

if(parent.left.value == value){

parent.left = targetNode.right;

}else{//如果 targetNode 是 parent 的右子结点

parent.right = targetNode.right;

}

}else{

root = targetNode.right;

}

}

}

}

}

//编写方法

//1.返回的 以node 为根结点的二叉排序树的最小结点的值

//2.删除node 为根结点的二叉排序树的最小结点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午10:44:31

* @param node 传入的结点(为二叉排序树的根结点)

* @return 返回的 以node 为根结点的二叉排序树的最小结点的值

*/

public int delRightT(Node node){

Node tar = node;

//循环的查找左子节点,就会找到最小值

while(tar.left != null){

tar = tar.left;

}

//这时 target就指向了最小结点

//删除最小结点

delNode(tar.value);

return tar.value;

}

}

从左子树找到最大的结点,然后删除节点

思路

看过上面的或者已经有相关数据结构的道友就会了解,实现起来异常简单。

1.最小值就是二叉树最左边的叶子节点;

2.而最大值就是二叉树最左边的叶子节点。

public class BinarySortTreeTest {

public static void main(String[] args) {

int arr[] = {7,3,10,12,5,1,9,2};

BinaryTree tree = new BinaryTree();

//循环的添加结点到二叉排序树

for(int i = 0 ;i< arr.length;i++){

tree.add(new Node(arr[i]));

}

//中序遍历二叉排序树

System.out.println("中序遍历此树:");

tree.infixOrder(); //1,2,3,5,7,9,10,12

//测试一下删除叶子节点

tree.delNode(10);

System.out.println("删除后的节点:");

tree.infixOrder();//1,2,3,5,7,9,10,12

}

}

//创建Node结点

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 add(Node node){

if(node == null){

return;

}

//判断传入的结点的值,和当前子树的根结点的值的关系

if(node.value < this.value){

if(this.left == null){//如果当前结点左子结点为null

this.left = node;

}else{

//递归的向左子树添加

this.left.add(node);

}

}else{//添加的节点的值大于当前结点的值

if(this.right == null){

this.right = node;

}else{

//递归的向右子树添加

this.right.add(node);

}

}

}

//中序遍历

public void infixOrder(){

if(this.left != null){

this.left.infixOrder();

}

System.out.println(this);

if(this.right != null){

this.right.infixOrder();

}

}

//查找要删除的节点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午8:43:01

* @param value 希望删除的结点的值

* @return 如果找到该值返回,未找到返回null

*/

public Node search(int value){

if(value == this.value){//说明找到了

return this;

}else if(value < this.value){//查找的值小于当前结点的值,向左子树查找

if(this.left == null){//左子结点为空

return null;

}

return this.left.search(value);

}else{//查找的值不小于当前结点的值,向右子树查找

if(this.right == null){

return null;

}

return this.right.search(value);

}

}

//查找要删除结点的父结点

/**

*

* @param value 希望删除的结点的值

* @return 返回的是要删除的结点的父结点,如果没有就返回null

*/

public Node searchP(int value){

//如果当前结点是要删除的结点的父结点,如果没有就返回null

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.searchP(value);//向左子树查找

}else if(value >= this.value && this.right != null){

return this.right.searchP(value);//向右子树递归查找

}else {

return null;//未找到父结点

}

}

}

}

//创建二叉排序树

class BinaryTree{

private Node root;

//添加结点的方法

public void add(Node node){

if(root == null){

root = node;//如果root为空则直接让root指向node

}else{

root.add(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 searchP(int value){

if(root == null){

return null;

}else{

return root.searchP(value);

}

}

//删除节点

public void delNode(int value){

if(root == null){

return;

}else{

//1.需求先去找到要删除的结点 targetNode

Node targetNode = search(value);

//如果没有找到要删除的结点

if(targetNode == null){

return;

}

//如果我们发现当前这颗二叉排序树只有一个结点

if(root.left == null && root.right == null) {

root = null;

return;

}

//去找到targetNode的父结点

Node parent = searchP(value);

//如果要删除的节点为叶子节点

if(targetNode.left == null && targetNode.right == null){

//判断targetNode是父节点的左子结点,还是右子节点

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 maxVa = delRightT(targetNode.right);

targetNode.value = maxVa;

}else{//删除只有一个字树的节点

//如果要删除的结点有左子结点

if(targetNode.left != null) {

if(parent != null) {

//如果 targetNode 是 parent 的左子结点

if(parent.left.value == value) {

parent.left = targetNode.left;

} else {//targetNode 是 parent 的右子结点

parent.right = targetNode.left;

}

} else {

root = targetNode.left;

}

}else{//如果要删除的结点有右子结点

if(parent != null){

//如果 targetNode 是 parent 的左子结点

if(parent.left.value == value){

parent.left = targetNode.right;

}else{//如果 targetNode 是 parent 的右子结点

parent.right = targetNode.right;

}

}else{

root = targetNode.right;

}

}

}

}

}

//编写方法

//1.返回的 以node 为根结点的二叉排序树的最小结点的值

//2.删除node 为根结点的二叉排序树的最小结点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午10:44:31

* @param node 传入的结点(为二叉排序树的根结点)

* @return 返回的 以node 为根结点的二叉排序树的最小结点的值

*/

public int delRightT(Node node){

Node tar = node;

//循环的查找左子节点,就会找到最大值

while(tar.right != null){

tar = tar.right;

}

//这时 target就指向了最大结点

//删除最大结点

delNode(tar.value);

//System.out.println("子树最大:" + tar.value);

return tar.value;

}

}

平衡二叉树(AVL树)

平衡二叉树(AVL树)介绍

看一个案例(说明二叉排序树可能的问题)

给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在。

29abfa25306a90f2a629b581f84c3ede.png

左边BST 存在的问题分析:

左子树全部为空,从形式上看,更像一个单链表.

插入速度没有影响

查询速度明显降低(因为需要依次比较), 不能发挥BST

的优势,因为每次还需要比较左子树,其查询速度比

单链表还慢

解决方案——》平衡二叉树(AVL)

基本介绍

平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树, 可以保证查询效率较高。

具有以下特点:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。

举例说明, 看看下面哪些AVL树, 为什么?

bcc36813baed2393dfdea30ddc0766c9.png

AVL树左旋转思路图解

应用案例-单旋转(左旋转)

1.要求: 给你一个数列,创建出对应的平衡二叉树.数列 {4,3,6,5,7,8}

2.思路分析(示意图)

0e4bd2d4dab725440fa5e6caed571722.png

问题:当插入8 时

rightHeight() - leftHeight() > 1 成立,此时,不再是一颗avl树了.

怎么处理才能保证为AVL树 --> 进行左旋转.

具体步骤图解:

1.创建一个新的节点 newNode (以4这个值创建),创建一个新的节点,值等于当前根节点的值.

//把新节点的左子树设置了当前节点的左子树

41be304aff0581fc0e8dee31e35c9cf3.png

2. newNode.left = left

//把新节点的右子树设置为当前节点的右子树的左子树

bb3067653896f6fa193c9239b3dcb35a.png

3. newNode.right =right.left;

//把当前节点的值换为右子节点的值

ea77986bed79da66b76cc5a9fad72ddc.png

4.value=right.value;

//把当前节点的右子树设置成右子树的右子树

a8d50f08724ed0a966d1937804a53e4b.png

5. right=right.right;

//把当前节点的左子树设置为新节点

689719f7d75eaec3d423d74f0ecc21f5.png

6. left=newLeft;

0f3f934c068da9ac3bca3d614af4aaa8.png

源自网络的动图:

388d7d517b1828a1d24c89a74970a4ce.gif

AVL树高度求解

public class AVLTreeTest {

public static void main(String[] args) {

int[] arr = {4,3,6,5,7,8};

//创建一个 AVLTree对象

AVLTree avlTree = new AVLTree();

//添加结点

for(int i=0; i < arr.length; i++) {

avlTree.add(new Node(arr[i]));

}

//中序遍历

System.out.println("中序遍历:");

avlTree.infixOrder();//3,4,5,6,7,8

System.out.println("未经过平衡处理的树:");

System.out.println("树的高度:" + avlTree.getRoot().height());//4

System.out.println("树的左子树高度:" + avlTree.getRoot().leftHeight()); // 1

System.out.println("树的右子树高度:" + avlTree.getRoot().rightHeight()); // 3

}

}

//创建Node结点

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 add(Node node){

if(node == null){

return;

}

//判断传入的结点的值,和当前子树的根结点的值的关系

if(node.value < this.value){

if(this.left == null){//如果当前结点左子结点为null

this.left = node;

}else{

//递归的向左子树添加

this.left.add(node);

}

}else{//添加的节点的值大于当前结点的值

if(this.right == null){

this.right = node;

}else{

//递归的向右子树添加

this.right.add(node);

}

}

}

//中序遍历

public void infixOrder(){

if(this.left != null){

this.left.infixOrder();

}

System.out.println(this);

if(this.right != null){

this.right.infixOrder();

}

}

//查找要删除的节点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午8:43:01

* @param value 希望删除的结点的值

* @return 如果找到该值返回,未找到返回null

*/

public Node search(int value){

if(value == this.value){//说明找到了

return this;

}else if(value < this.value){//查找的值小于当前结点的值,向左子树查找

if(this.left == null){//左子结点为空

return null;

}

return this.left.search(value);

}else{//查找的值不小于当前结点的值,向右子树查找

if(this.right == null){

return null;

}

return this.right.search(value);

}

}

//查找要删除结点的父结点

/**

*

* @param value 希望删除的结点的值

* @return 返回的是要删除的结点的父结点,如果没有就返回null

*/

public Node searchP(int value){

//如果当前结点是要删除的结点的父结点,如果没有就返回null

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.searchP(value);//向左子树查找

}else if(value >= this.value && this.right != null){

return this.right.searchP(value);//向右子树递归查找

}else {

return null;//未找到父结点

}

}

}

//返回以该结点为根结点的树的高度

public int height(){

return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;

}

//返回左子树的高度

public int leftHeight(){

if(left == null){

return 0;

}

return left.height();

}

//返回右子树的高度

public int rightHeight(){

if(right == null){

return 0;

}

return right.height();

}

}

//创建AVL树

class AVLTree{

private Node root;

public Node getRoot() {

return root;

}

//添加结点的方法

public void add(Node node){

if(root == null){

root = node;//如果root为空则直接让root指向node

}else{

root.add(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 searchP(int value){

if(root == null){

return null;

}else{

return root.searchP(value);

}

}

//删除节点

public void delNode(int value){

if(root == null){

return;

}else{

//1.需求先去找到要删除的结点 targetNode

Node targetNode = search(value);

//如果没有找到要删除的结点

if(targetNode ==null){

return;

}

//如果我们发现当前这颗二叉排序树只有一个结点

if(root.left == null && root.right == null) {

root = null;

return;

}

//去找到targetNode的父结点

Node parent = searchP(value);

//如果要删除的节点为叶子节点

if(targetNode.left == null && targetNode.right == null){

//判断targetNode是父节点的左子结点,还是右子节点

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 minVa = delRightT(targetNode.right);

targetNode.value = minVa;

}else{//删除只有一个字树的节点

//如果要删除的结点有左子结点

if(targetNode.left != null) {

if(parent != null) {

//如果 targetNode 是 parent 的左子结点

if(parent.left.value == value) {

parent.left = targetNode.left;

} else {//targetNode 是 parent 的右子结点

parent.right = targetNode.left;

}

} else {

root = targetNode.left;

}

}else{//如果要删除的结点有右子结点

if(parent != null){

//如果 targetNode 是 parent 的左子结点

if(parent.left.value == value){

parent.left = targetNode.right;

}else{//如果 targetNode 是 parent 的右子结点

parent.right = targetNode.right;

}

}else{

root = targetNode.right;

}

}

}

}

}

//编写方法

//1.返回的 以node 为根结点的二叉排序树的最小结点的值

//2.删除node 为根结点的二叉排序树的最小结点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午10:44:31

* @param node 传入的结点(为二叉排序树的根结点)

* @return 返回的 以node 为根结点的二叉排序树的最小结点的值

*/

public int delRightT(Node node){

Node tar = node;

//循环的查找左子节点,就会找到最小值

while(tar.left != null){

tar = tar.left;

}

//这时 target就指向了最小结点

//删除最小结点

delNode(tar.value);

return tar.value;

}

}

AVL树左旋转代码实现

public class AVLTreeTest {

public static void main(String[] args) {

int[] arr = { 4, 3, 6, 5, 7, 8 };

// 创建一个 AVLTree对象

AVLTree avlTree = new AVLTree();

// 添加结点

for (int i = 0; i < arr.length; i++) {

avlTree.add(new Node(arr[i]));

}

// 中序遍历

System.out.println("中序遍历:");

avlTree.infixOrder(); // 3,4,5,6,7,8

System.out.println("经过平衡处理的树:");

System.out.println("树的高度:" + avlTree.getRoot().height()); // 3

System.out.println("树的左子树高度:" + avlTree.getRoot().leftHeight()); // 2

System.out.println("树的右子树高度:" + avlTree.getRoot().rightHeight()); // 2

}

}

// 创建Node结点

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 add(Node node) {

if (node == null) {

return;

}

// 判断传入的结点的值,和当前子树的根结点的值的关系

if (node.value < this.value) {

if (this.left == null) { // 如果当前结点左子结点为null

this.left = node;

} else {

// 递归的向左子树添加

this.left.add(node);

}

} else { // 添加的节点的值大于当前结点的值

if (this.right == null) {

this.right = node;

} else {

// 递归的向右子树添加

this.right.add(node);

}

}

//当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 , 左旋转

if(rightHeight() - leftHeight() > 1) {

leftRate();//左旋转

}

}

// 中序遍历

public void infixOrder() {

if (this.left != null) {

this.left.infixOrder();

}

System.out.println(this);

if (this.right != null) {

this.right.infixOrder();

}

}

// 查找要删除的节点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午8:43:01

* @param value

* 希望删除的结点的值

* @return 如果找到该值返回,未找到返回null

*/

public Node search(int value) {

if (value == this.value) { // 说明找到了

return this;

} else if (value < this.value) { // 查找的值小于当前结点的值,向左子树查找

if (this.left == null) { // 左子结点为空

return null;

}

return this.left.search(value);

} else { // 查找的值不小于当前结点的值,向右子树查找

if (this.right == null) {

return null;

}

return this.right.search(value);

}

}

// 查找要删除结点的父结点

/**

*

* @param value

* 希望删除的结点的值

* @return 返回的是要删除的结点的父结点,如果没有就返回null

*/

public Node searchP(int value) {

// 如果当前结点是要删除的结点的父结点,如果没有就返回null

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.searchP(value); // 向左子树查找

} else if (value >= this.value && this.right != null) {

return this.right.searchP(value); // 向右子树递归查找

} else {

return null; // 未找到父结点

}

}

}

// 返回以该结点为根结点的树的高度

public int height() {

return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;

}

// 返回左子树的高度

public int leftHeight() {

if (left == null) {

return 0;

}

return left.height();

}

// 返回右子树的高度

public int rightHeight() {

if (right == null) {

return 0;

}

return right.height();

}

// 左旋转方法

public void leftRate() {

// 创建新的结点,以当前根结点的值

Node newNode = new Node(value);

// 把新的结点的左子树设置成当前结点的左子树

newNode.left = left;

// 把新的结点的右子树设置成带你过去结点的右子树的左子树

newNode.right = right.left;

// 把当前结点的值替换成右子结点的值

value = right.value;

// 把当前结点的右子树设置成当前结点右子树的右子树

right = right.right;

// 把当前结点的左子树(左子结点)设置成新的结点

left = newNode;

}

}

// 创建AVL树

class AVLTree {

private Node root;

public Node getRoot() {

return root;

}

// 添加结点的方法

public void add(Node node) {

if (root == null) {

root = node; // 如果root为空则直接让root指向node

} else {

root.add(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 searchP(int value) {

if (root == null) {

return null;

} else {

return root.searchP(value);

}

}

// 删除节点

public void delNode(int value) {

if (root == null) {

return;

} else {

// 1.需求先去找到要删除的结点 targetNode

Node targetNode = search(value);

// 如果没有找到要删除的结点

if (targetNode == null) {

return;

}

// 如果我们发现当前这颗二叉排序树只有一个结点

if (root.left == null && root.right == null) {

root = null;

return;

}

// 去找到targetNode的父结点

Node parent = searchP(value);

// 如果要删除的节点为叶子节点

if (targetNode.left == null && targetNode.right == null) {

// 判断targetNode是父节点的左子结点,还是右子节点

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 minVa = delRightT(targetNode.right);

targetNode.value = minVa;

} else { // 删除只有一个字树的节点

// 如果要删除的结点有左子结点

if (targetNode.left != null) {

if (parent != null) {

// 如果 targetNode 是 parent 的左子结点

if (parent.left.value == value) {

parent.left = targetNode.left;

} else { // targetNode 是 parent 的右子结点

parent.right = targetNode.left;

}

} else {

root = targetNode.left;

}

} else { // 如果要删除的结点有右子结点

if (parent != null) {

// 如果 targetNode 是 parent 的左子结点

if (parent.left.value == value) {

parent.left = targetNode.right;

} else { // 如果 targetNode 是 parent 的右子结点

parent.right = targetNode.right;

}

} else {

root = targetNode.right;

}

}

}

}

}

// 编写方法

// 1.返回的 以node 为根结点的二叉排序树的最小结点的值

// 2.删除node 为根结点的二叉排序树的最小结点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午10:44:31

* @param node

* 传入的结点(为二叉排序树的根结点)

* @return 返回的 以node 为根结点的二叉排序树的最小结点的值

*/

public int delRightT(Node node) {

Node tar = node;

// 循环的查找左子节点,就会找到最小值

while (tar.left != null) {

tar = tar.left;

}

// 这时 target就指向了最小结点

// 删除最小结点

delNode(tar.value);

return tar.value;

}

}

上面的左旋转,仅仅是左旋转,考虑并不完全,完整的旋转代码,参考下方的双旋转!!!

AVL树右旋转图解和实现

应用案例-单旋转(右旋转)

1.要求: 给你一个数列,创建出对应的平衡二叉树.数列 {10,12, 8, 9, 7, 6}

2.思路分析(示意图)

问题:当插入6 时

leftHeight() - rightHeight() > 1 成立,此时,不再是一颗avl树了.

怎么处理 --> 进行右旋转.[就是降低左子树的高度], 这里是将9 这个节点,通过右旋转,到右子树

24b04bef1f40f1f60f2d1d10e6e75093.png

1. 创建一个新的节点 newNode (以10这个值创建),创建一个新的节点,值等于当前根节点的值

//把新节点的右子树设置了当前节点的右子树

31cc918faf786cb36ab83eb7d1a776d6.png

2. newNode.right = right

//把新节点的左子树设置为当前节点的左子树的右子树

dce2be96ccedec9f3ceb54602d85dccc.png

3. newNode.left =left.right;

//把当前节点的值换为左子节点的值

54dcd7c83e7e6e4b0c3feb5232b0a4fa.png

4.value=left.value;

//把当前节点的左子树设置成左子树的左子树

d6f59f69f72cd3740ffdfa0ace1dab4f.png

5. left=left.left;

//把当前节点的右子树设置为新节点

a53172f87fd084e99a4d344d21e2a3c4.png

6. right=newLeft;

95b883b5a62c7c078fb0cab0a6a8f5eb.png

源自网络的动图:

92eade1f3afc7f80845b9c59ad5dfbb2.gif

代码实现如下:

public class AVLTreeTest {

public static void main(String[] args) {

//int[] arr = { 4, 3, 6, 5, 7, 8 };

int arr[] = { 10,12, 8, 9, 7, 6};

// 创建一个 AVLTree对象

AVLTree avlTree = new AVLTree();

// 添加结点

for (int i = 0; i < arr.length; i++) {

avlTree.add(new Node(arr[i]));

}

// 中序遍历

System.out.println("中序遍历:");

avlTree.infixOrder(); // 3,4,5,6,7,8

System.out.println("经过平衡处理的树:");

System.out.println("树的高度:" + avlTree.getRoot().height()); // 3

System.out.println("树的左子树高度:" + avlTree.getRoot().leftHeight()); // 2

System.out.println("树的右子树高度:" + avlTree.getRoot().rightHeight()); // 2

System.out.println("当前的根节点:" + avlTree.getRoot());

}

}

// 创建Node结点

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 add(Node node) {

if (node == null) {

return;

}

// 判断传入的结点的值,和当前子树的根结点的值的关系

if (node.value < this.value) {

if (this.left == null) { // 如果当前结点左子结点为null

this.left = node;

} else {

// 递归的向左子树添加

this.left.add(node);

}

} else { // 添加的节点的值大于当前结点的值

if (this.right == null) {

this.right = node;

} else {

// 递归的向右子树添加

this.right.add(node);

}

}

//当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 , 左旋转

if(rightHeight() - leftHeight() > 1) {

leftRate(); //左旋转

}

//当添加完一个结点后,如果 (左子树的高度 - 右子树的高度) > 1, 右旋转

if(leftHeight() - rightHeight() > 1) {

rightRotate(); //右旋转

}

}

// 中序遍历

public void infixOrder() {

if (this.left != null) {

this.left.infixOrder();

}

System.out.println(this);

if (this.right != null) {

this.right.infixOrder();

}

}

// 查找要删除的节点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午8:43:01

* @param value

* 希望删除的结点的值

* @return 如果找到该值返回,未找到返回null

*/

public Node search(int value) {

if (value == this.value) { // 说明找到了

return this;

} else if (value < this.value) { // 查找的值小于当前结点的值,向左子树查找

if (this.left == null) { // 左子结点为空

return null;

}

return this.left.search(value);

} else { // 查找的值不小于当前结点的值,向右子树查找

if (this.right == null) {

return null;

}

return this.right.search(value);

}

}

// 查找要删除结点的父结点

/**

*

* @param value

* 希望删除的结点的值

* @return 返回的是要删除的结点的父结点,如果没有就返回null

*/

public Node searchP(int value) {

// 如果当前结点是要删除的结点的父结点,如果没有就返回null

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.searchP(value); // 向左子树查找

} else if (value >= this.value && this.right != null) {

return this.right.searchP(value); // 向右子树递归查找

} else {

return null; // 未找到父结点

}

}

}

// 返回以该结点为根结点的树的高度

public int height() {

return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;

}

// 返回左子树的高度

public int leftHeight() {

if (left == null) {

return 0;

}

return left.height();

}

// 返回右子树的高度

public int rightHeight() {

if (right == null) {

return 0;

}

return right.height();

}

// 左旋转方法

public void leftRate() {

// 创建新的结点,以当前根结点的值

Node newNode = new Node(value);

// 把新的结点的左子树设置成当前结点的左子树

newNode.left = left;

// 把新的结点的右子树设置成带你过去结点的右子树的左子树

newNode.right = right.left;

// 把当前结点的值替换成右子结点的值

value = right.value;

// 把当前结点的右子树设置成当前结点右子树的右子树

right = right.right;

// 把当前结点的左子树(左子结点)设置成新的结点

left = newNode;

}

//右旋转

public void rightRotate(){

Node newNode = new Node(value);

newNode.right = right;

newNode.left = left.right;

value = left.value;

left = left.left;

right = newNode;

}

}

// 创建AVL树

class AVLTree {

private Node root;

public Node getRoot() {

return root;

}

// 添加结点的方法

public void add(Node node) {

if (root == null) {

root = node; // 如果root为空则直接让root指向node

} else {

root.add(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 searchP(int value) {

if (root == null) {

return null;

} else {

return root.searchP(value);

}

}

// 删除节点

public void delNode(int value) {

if (root == null) {

return;

} else {

// 1.需求先去找到要删除的结点 targetNode

Node targetNode = search(value);

// 如果没有找到要删除的结点

if (targetNode == null) {

return;

}

// 如果我们发现当前这颗二叉排序树只有一个结点

if (root.left == null && root.right == null) {

root = null;

return;

}

// 去找到targetNode的父结点

Node parent = searchP(value);

// 如果要删除的节点为叶子节点

if (targetNode.left == null && targetNode.right == null) {

// 判断targetNode是父节点的左子结点,还是右子节点

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 minVa = delRightT(targetNode.right);

targetNode.value = minVa;

} else { // 删除只有一个字树的节点

// 如果要删除的结点有左子结点

if (targetNode.left != null) {

if (parent != null) {

// 如果 targetNode 是 parent 的左子结点

if (parent.left.value == value) {

parent.left = targetNode.left;

} else { // targetNode 是 parent 的右子结点

parent.right = targetNode.left;

}

} else {

root = targetNode.left;

}

} else { // 如果要删除的结点有右子结点

if (parent != null) {

// 如果 targetNode 是 parent 的左子结点

if (parent.left.value == value) {

parent.left = targetNode.right;

} else { // 如果 targetNode 是 parent 的右子结点

parent.right = targetNode.right;

}

} else {

root = targetNode.right;

}

}

}

}

}

// 编写方法

// 1.返回的 以node 为根结点的二叉排序树的最小结点的值

// 2.删除node 为根结点的二叉排序树的最小结点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午10:44:31

* @param node

* 传入的结点(为二叉排序树的根结点)

* @return 返回的 以node 为根结点的二叉排序树的最小结点的值

*/

public int delRightT(Node node) {

Node tar = node;

// 循环的查找左子节点,就会找到最小值

while (tar.left != null) {

tar = tar.left;

}

// 这时 target就指向了最小结点

// 删除最小结点

delNode(tar.value);

return tar.value;

}

}

上面的右旋转,仅仅是右旋转,考虑并不完全,完整的旋转代码,参考下方的双旋转!!!

AVL树双旋转图解和实现

应用案例-双旋转

前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。比如数列

int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成AVL树.

int[]arr= {2,1,6,5,7,3}; // 运行原来的代码可以看到,并没有转成 AVL树

f6c105f83b4e064a50bdefc0ada36756.png

问题分析:

在满足右旋转条件时,要判断:

(1)如果 是 左子树的 右子树高度 大于左子树的左子树时:

(2)就是 对 当前根节点的左子树,先进行 左旋转,

(3)然后, 再对当前根节点进行右旋转即可

否则,直接对当前节点(根节点)进行右旋转.即可.

先对当前节点的左子树,进行左旋转

73afcb3a9f43417580a39c218e01a816.png

b4d79ea1bce4e81c1beb862106d2554c.png

8e1adba537960d82e9fecba0dfd8cae3.png

d318bf3c0b02d3a00e182ad22b2fbc81.png

再对当前节点,进行右旋转

b1d9c8b383e213d1ff671d8a50bc59c3.png

ac7d7108b8ded2a6bf765714c3a88b0b.png

e7e01d08be5e3c90f7f6e271f50c2b6a.png

9c78f04340ae21ab2b6ccc1cae65d74c.png

33d858d14101a12fc34b91939e2ebbf4.png

具体代码分析:

public class AVLTreeTest {

public static void main(String[] args) {

//int[] arr = { 4, 3, 6, 5, 7, 8 };

int arr[] = { 10,12, 8, 9, 7, 6};

// 创建一个 AVLTree对象

AVLTree avlTree = new AVLTree();

// 添加结点

for (int i = 0; i < arr.length; i++) {

avlTree.add(new Node(arr[i]));

}

// 中序遍历

System.out.println("中序遍历:");

avlTree.infixOrder(); // 3,4,5,6,7,8

System.out.println("经过平衡处理的树:");

System.out.println("树的高度:" + avlTree.getRoot().height()); // 3

System.out.println("树的左子树高度:" + avlTree.getRoot().leftHeight()); // 2

System.out.println("树的右子树高度:" + avlTree.getRoot().rightHeight()); // 2

System.out.println("当前的根节点:" + avlTree.getRoot());

}

}

// 创建Node结点

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 add(Node node) {

if (node == null) {

return;

}

// 判断传入的结点的值,和当前子树的根结点的值的关系

if (node.value < this.value) {

if (this.left == null) { // 如果当前结点左子结点为null

this.left = node;

} else {

// 递归的向左子树添加

this.left.add(node);

}

} else { // 添加的节点的值大于当前结点的值

if (this.right == null) {

this.right = node;

} else {

// 递归的向右子树添加

this.right.add(node);

}

}

//当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 , 左旋转

if(rightHeight() - leftHeight() > 1) {

//如果它的右子树的左子树的高度大于它的右子树的右子树的高度

if(right != null && right.leftHeight() > right.rightHeight()) {

//先对右子结点进行右旋转

right.rightRotate();

//然后在对当前结点进行左旋转

leftRate(); //左旋转

} else {

//直接进行左旋转即可

leftRate();

}

return; //重要!!!

}

//当添加完一个结点后,如果 (左子树的高度 - 右子树的高度) > 1, 右旋转

if(leftHeight() - rightHeight() > 1) {

//如果它的左子树的右子树高度大于它的左子树的高度

if(left != null && left.rightHeight() > left.leftHeight()){

//先对当前结点的左结点(左子树)->左旋转

left.leftRate();

//再对当前结点进行右旋转

rightRotate();

}else{

//直接进行右旋转即可

rightRotate();

}

}

}

// 中序遍历

public void infixOrder() {

if (this.left != null) {

this.left.infixOrder();

}

System.out.println(this);

if (this.right != null) {

this.right.infixOrder();

}

}

// 查找要删除的节点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午8:43:01

* @param value

* 希望删除的结点的值

* @return 如果找到该值返回,未找到返回null

*/

public Node search(int value) {

if (value == this.value) { // 说明找到了

return this;

} else if (value < this.value) { // 查找的值小于当前结点的值,向左子树查找

if (this.left == null) { // 左子结点为空

return null;

}

return this.left.search(value);

} else { // 查找的值不小于当前结点的值,向右子树查找

if (this.right == null) {

return null;

}

return this.right.search(value);

}

}

// 查找要删除结点的父结点

/**

*

* @param value

* 希望删除的结点的值

* @return 返回的是要删除的结点的父结点,如果没有就返回null

*/

public Node searchP(int value) {

// 如果当前结点是要删除的结点的父结点,如果没有就返回null

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.searchP(value); // 向左子树查找

} else if (value >= this.value && this.right != null) {

return this.right.searchP(value); // 向右子树递归查找

} else {

return null; // 未找到父结点

}

}

}

// 返回以该结点为根结点的树的高度

public int height() {

return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;

}

// 返回左子树的高度

public int leftHeight() {

if (left == null) {

return 0;

}

return left.height();

}

// 返回右子树的高度

public int rightHeight() {

if (right == null) {

return 0;

}

return right.height();

}

// 左旋转方法

public void leftRate() {

// 创建新的结点,以当前根结点的值

Node newNode = new Node(value);

// 把新的结点的左子树设置成当前结点的左子树

newNode.left = left;

// 把新的结点的右子树设置成带你过去结点的右子树的左子树

newNode.right = right.left;

// 把当前结点的值替换成右子结点的值

value = right.value;

// 把当前结点的右子树设置成当前结点右子树的右子树

right = right.right;

// 把当前结点的左子树(左子结点)设置成新的结点

left = newNode;

}

//右旋转

public void rightRotate(){

Node newNode = new Node(value);

newNode.right = right;

newNode.left = left.right;

value = left.value;

left = left.left;

right = newNode;

}

}

// 创建AVL树

class AVLTree {

private Node root;

public Node getRoot() {

return root;

}

// 添加结点的方法

public void add(Node node) {

if (root == null) {

root = node; // 如果root为空则直接让root指向node

} else {

root.add(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 searchP(int value) {

if (root == null) {

return null;

} else {

return root.searchP(value);

}

}

// 删除节点

public void delNode(int value) {

if (root == null) {

return;

} else {

// 1.需求先去找到要删除的结点 targetNode

Node targetNode = search(value);

// 如果没有找到要删除的结点

if (targetNode == null) {

return;

}

// 如果我们发现当前这颗二叉排序树只有一个结点

if (root.left == null && root.right == null) {

root = null;

return;

}

// 去找到targetNode的父结点

Node parent = searchP(value);

// 如果要删除的节点为叶子节点

if (targetNode.left == null && targetNode.right == null) {

// 判断targetNode是父节点的左子结点,还是右子节点

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 minVa = delRightT(targetNode.right);

targetNode.value = minVa;

} else { // 删除只有一个字树的节点

// 如果要删除的结点有左子结点

if (targetNode.left != null) {

if (parent != null) {

// 如果 targetNode 是 parent 的左子结点

if (parent.left.value == value) {

parent.left = targetNode.left;

} else { // targetNode 是 parent 的右子结点

parent.right = targetNode.left;

}

} else {

root = targetNode.left;

}

} else { // 如果要删除的结点有右子结点

if (parent != null) {

// 如果 targetNode 是 parent 的左子结点

if (parent.left.value == value) {

parent.left = targetNode.right;

} else { // 如果 targetNode 是 parent 的右子结点

parent.right = targetNode.right;

}

} else {

root = targetNode.right;

}

}

}

}

}

// 编写方法

// 1.返回的 以node 为根结点的二叉排序树的最小结点的值

// 2.删除node 为根结点的二叉排序树的最小结点

/**

*

* @Description

* @author subei

* @date 2020年6月13日上午10:44:31

* @param node

* 传入的结点(为二叉排序树的根结点)

* @return 返回的 以node 为根结点的二叉排序树的最小结点的值

*/

public int delRightT(Node node) {

Node tar = node;

// 循环的查找左子节点,就会找到最小值

while (tar.left != null) {

tar = tar.left;

}

// 这时 target就指向了最小结点

// 删除最小结点

delNode(tar.value);

return tar.value;

}

}

本章思维导图

7317b270696ae2cd1ef3d48fa7e38630.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值