树
一、使用树这种结构的原因:
●数组存储方式的分析
➢优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
➢缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低
●链式存储方式的分析
➢优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可,删除效率也很好)。
➢缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)
●树存储方式的分析
➢能提高数据存储,读取的效率,比如利用二叉排序树(Binary SortTree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
二、二叉树
➢树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
➢二叉树的子节点分为左节点和右节点。
➢如果该二叉树的所有叶子节点都在最后一层,并且结点总数=2^n-1,n为层.数,则我们称为满二叉树。
➢如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。
●前序遍历:先输出父节点,再遍历左子树和右子树
●中序遍历:先遍历左子树,再输出父节点,再遍历右子树
●后序遍历:先遍历左子树,再遍历右子树,最后输出父节点
●小结:看输出父节点的顺序,就确定是前序,中序还是后序
package com.xudong.DataStructures;
public class BinaryTreeDemo {
public static void main(String[] args) {
//创建一个二叉树
BinaryTree binaryTree = new BinaryTree();
//创建需要的节点
HeroNode1 root = new HeroNode1(1, "宋江");
HeroNode1 node2 = new HeroNode1(2, "吴用");
HeroNode1 node3 = new HeroNode1(3, "卢俊义");
HeroNode1 node4 = new HeroNode1(4, "林冲");
HeroNode1 node5 = new HeroNode1(5, "关胜");
//方式一:手动创建二叉树
root.setLeft(node2);
root.setRight(node3);
node3.setRight(node4);
node3.setLeft(node5);
binaryTree.setRoot(root);
//测试
System.out.println("前序遍历:");
binaryTree.preOrder();
System.out.println("中序遍历:");
binaryTree.infixOrder();
System.out.println("后序遍历:");
binaryTree.postOrder();
System.out.println("======================");
//前序查找
System.out.println("前序查找:");
HeroNode1 resNode = binaryTree.preOrderSearch(5);
if (resNode != null){
System.out.printf("找到了信息为no= %d name= %s 的英雄。\n",resNode.getNo(),resNode.getName());
}else {
System.out.println("没有找到该英雄!");
}
//中序查找
System.out.println("中序查找:");
resNode = binaryTree.infixOrderSearch(5);
if (resNode != null){
System.out.printf("找到了信息为no= %d name= %s 的英雄。\n",resNode.getNo(),resNode.getName());
}else {
System.out.println("没有找到该英雄!");
}
//后序查找
System.out.println("后序查找:");
resNode = binaryTree.postOrderSearch(5);
if (resNode != null){
System.out.printf("找到了信息为no= %d name= %s 的英雄。\n",resNode.getNo(),resNode.getName());
}else {
System.out.println("没有找到该英雄!");
}
System.out.println("====================");
//删除节点
System.out.println("删除前,前序遍历:");
binaryTree.preOrder();
binaryTree.delNode(5);
System.out.println("删除后,前序遍历:");
binaryTree.preOrder();
System.out.println("--------------------");
System.out.println("删除前,中序遍历:");
binaryTree.infixOrder();
binaryTree.delNode(4);
System.out.println("删除后,中序遍历:");
binaryTree.infixOrder();
System.out.println("--------------------");
System.out.println("删除前,后序遍历:");
binaryTree.postOrder();
binaryTree.delNode(3);
System.out.println("删除后,后序遍历:");
binaryTree.postOrder();
}
}
//定义BinaryTree二叉树
class BinaryTree{
private HeroNode1 root;
public void setRoot(HeroNode1 root) {
this.root = root;
}
//前序遍历
public void preOrder(){
if (this.root != null){
this.root.preOrder();
}else {
System.out.println("二叉树为空,无法遍历!");
}
}
//中序遍历
public void infixOrder(){
if (this.root != null){
this.root.infixOrder();
}else {
System.out.println("二叉树为空,无法遍历!");
}
}
//后序遍历
public void postOrder(){
if (this.root != null){
this.root.postOrder();
}else {
System.out.println("二叉树为空,无法遍历!");
}
}
//前序查找
public HeroNode1 preOrderSearch(int no){
if (root != null){
return root.preOrderSearch(no);
}else {
return null;
}
}
//中序查找
public HeroNode1 infixOrderSearch(int no){
if (root != null){
return root.infixOrderSearch(no);
}else {
return null;
}
}
//后序查找
public HeroNode1 postOrderSearch(int no){
if (root != null){
return root.postOrderSearch(no);
}else {
return null;
}
}
//删除节点
public void delNode(int no){
if (root != null){
//立即判断root是不是要删除的节点
if (root.getNo() == no){
root = null;
}else {
//递归删除
root.delNode(no);
}
}else {
System.out.println("空树!不能删除");
}
}
}
//创建heroNode节点
class HeroNode1{
private int no;
private String name;
private HeroNode1 left;
private HeroNode1 right;
public HeroNode1(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode1 getLeft() {
return left;
}
public void setLeft(HeroNode1 left) {
this.left = left;
}
public HeroNode1 getRight() {
return right;
}
public void setRight(HeroNode1 right) {
this.right = right;
}
@Override
public String toString() {
return "HeroNode1{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
//前序遍历的方法
public void preOrder(){
System.out.println(this);//先输出父节点
//递归向左子树前序遍历
if (this.left != null){
this.left.preOrder();
}
//递归向右子树前序遍历
if (this.right != null){
this.right.preOrder();
}
}
//中序遍历的方法
public void infixOrder(){
//递归向左子树中序遍历
if (this.left != null){
this.left.infixOrder();
}
//输出父节点
System.out.println(this);
//递归向右子树中序遍历
if (this.right != null){
this.right.infixOrder();
}
}
//前序遍历的方法
public void postOrder(){
//递归向左子树中序遍历
if (this.left != null){
this.left.postOrder();
}
//递归向右子树中序遍历
if (this.right != null){
this.right.postOrder();
}
//输出父节点
System.out.println(this);
}
//前序遍历查找
public HeroNode1 preOrderSearch(int no){
//比较当前节点是不是
if (this.no == no){
return this;
}
//向左递归查找
HeroNode1 resNode = null;
if (this.left != null){
resNode = this.left.preOrderSearch(no);
}
if (resNode != null){//说明在左子树找到
return resNode;
}
//向右递归查找
if (this.right != null){
resNode = this.right.preOrderSearch(no);
}
if (resNode != null){//说明在右子树找到
return resNode;
}
return resNode;
}
//中序遍历查找
public HeroNode1 infixOrderSearch(int no){
//向左递归查找
HeroNode1 resNode = null;
if (this.left != null){
resNode = this.left.infixOrderSearch(no);
}
if (resNode != null){//说明在左子树找到
return resNode;
}
//如果没找到,比较当前节点是不是
if (this.no == no){
return this;
}
//向右递归查找
if (this.right != null){
resNode = this.right.infixOrderSearch(no);
}
if (resNode != null){//说明在右子树找到
return resNode;
}
return resNode;
}
//后序遍历查找
public HeroNode1 postOrderSearch(int no){
//向左递归查找
HeroNode1 resNode = null;
if (this.left != null){
resNode = this.left.postOrderSearch(no);
}
if (resNode != null){//说明在左子树找到
return resNode;
}
//向右递归查找
if (this.right != null){
resNode = this.right.postOrderSearch(no);
}
if (resNode != null){//说明在右子树找到
return resNode;
}
//如果没找到,比较当前节点是不是
if (this.no == no){
return this;
}
return resNode;
}
//递归删除节点
//1.如果删除的是叶子结点,则删除该节点
//2.如果删除的节点是非叶子节点,则删除该子数
public void delNode(int no){
//向左节点递归删除
if (this.left != null && this.left.no == no){
this.left = null;
return;
}
//向右节点递归删除
if (this.right != null && this.right.no == no){
this.right = null;
return;
}
//向左子树递归删除
if (this.left != null){
this.left.delNode(no);
}
//向右子树递归删除
if (this.right != null){
this.right.delNode(no);
}
}
}
三、顺序存储二叉树
●从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组。
顺序存储二叉树的特点:
➢顺序二叉树通常只考虑完全二叉树
➢第n个元素的左子节点カ为【2n+ 1】
➢第n个元素的右子节点カ【2n+2】
➢第n个元素的父子节点为【(n-1)/2】
package com.xudong;
public class arrBinaryTreeDemo {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7};
ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
arrBinaryTree.preOrder();
}
}
//实现顺序存储二叉树遍历
class ArrBinaryTree{
private int[] arr;//存储数据节点的数组
public ArrBinaryTree(int[] arr) {
this.arr = arr;
}
//重载perOrder
public void preOrder(){
this.preOrder(0);
}
//顺序存储二叉树的前序遍历
public void preOrder(int index){
if (arr == null || arr.length == 0){
System.out.println("数组为空!");
}
//输出当前元素
System.out.println(arr[index]);
//向左递归遍历
if ((index * 2 + 1) < arr.length){
preOrder(2 * index + 1);
}
//向右递归遍历
if ((index * 2 + 2) < arr.length){
preOrder(2 * index + 2);
}
}
}
四、线索化二叉树
➢n个结点的二叉链表中含有n+1【公式2n-(n-1)=n+1】个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继节点的指针(这种附加的指针称为"线索")
➢这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、 中序线索二叉树和后序线索二叉树三种
➢一个结点的前一个节点,称为前驱节点
➢一个结点的后一个节点,称为后继节点
线索化二叉树应用案例
遍历线索化二叉树
➢说明:对前面的中序线索化的二叉树,进行遍历
➢分析:因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。遍历的次序应当和中序遍历保持一致。
package com.xudong.DataStructures;
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
HeroNode3 root = new HeroNode3(1, "Tom");
HeroNode3 node2 = new HeroNode3(3, "jack");
HeroNode3 node3 = new HeroNode3(6, "smith");
HeroNode3 node4 = new HeroNode3(8, "mary");
HeroNode3 node5 = new HeroNode3(10, "king");
HeroNode3 node6 = new HeroNode3(14, "dim");
//手动创建线索二叉树
root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
threadedBinaryTree.setRoot(root);
threadedBinaryTree.threadedNodes();
//测试10号节点
HeroNode3 leftNode = node5.getLeft();
HeroNode3 rightNode = node5.getRight();
System.out.println("10号节点的前驱节点是 =" + leftNode);
System.out.println("10号节点的后继节点是 =" + rightNode);
//遍历线索化二叉树
System.out.println("使用线索化的方式遍历线索化二叉树:");
threadedBinaryTree.threadedList();
}
}
//
//定义ThreadedBinaryTree二叉树
class ThreadedBinaryTree{
private HeroNode3 root;
//在递归线索化时,pre总保留前一个节点
private HeroNode3 pre = null;
public void setRoot(HeroNode3 root) {
this.root = root;
}
//重载threadedNodes方法
public void threadedNodes(){
this.threadedNodes(root);
}
//对二叉树进行中序线索化的方法.node就是当前需要线索化的节点
public void threadedNodes(HeroNode3 node){
//如果node == null,不能线索化
if (node == null){
return;
}
//1.线索话左子树
threadedNodes(node.getLeft());
//2.线索话当前节点
//处理当前节点的前驱结点
if (node.getLeft() == null){
//让当前节点的左指针指向前驱节点
node.setLeft(pre);
//修改当前节点的左指针的类型,指向前驱节点
node.setLeftType(1);
}
//处理后继节点
if (pre != null && pre.getRight() == null){
//让前驱节点的右指针指向当前节点
pre.setRight(node);
//修改前驱节点的右指针类型
pre.setRightType(1);
}
//每处理一个节点后,让当前节点是下一个节点的前驱节点
pre = node;
//3.线索话右子树
threadedNodes(node.getRight());
}
//线索化的遍历线索二叉树
public void threadedList(){
//定义一个变量,存储当前遍历的节点
HeroNode3 node = root;
while (node != null){
//当leftType == 1 时,说明该节点时按照线索化处理后的有效节点
while (node.getLeftType() == 0){
node = node.getLeft();
}
//打印当前这个节点
System.out.println(node);
//如果当前节点的右指针指向的是后继节点,就一直输出
while (node.getRightType() == 1){
//获得当前节点的后继节点
node = node.getRight();
System.out.println(node);
}
//替换这个遍历的节点
node = node.getRight();
}
}
}
//创建heroNode节点
class HeroNode3{
private int no;
private String name;
private HeroNode3 left;
private HeroNode3 right;
//如果leftType == 0 表示指向的是左子树,如果是 1 则表示指向前驱节点
//如果rightType == 0 表示指向的是右子树,如果是 1 则表示指向后继节点
private int leftType;
private int rightType;
public int getLeftType() {
return leftType;
}
public void setLeftType(int leftType) {
this.leftType = leftType;
}
public int getRightType() {
return rightType;
}
public void setRightType(int rightType) {
this.rightType = rightType;
}
public HeroNode3(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode3 getLeft() {
return left;
}
public void setLeft(HeroNode3 left) {
this.left = left;
}
public HeroNode3 getRight() {
return right;
}
public void setRight(HeroNode3 right) {
this.right = right;
}
@Override
public String toString() {
return "HeroNode3{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
五、赫夫曼树
●给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(HuffmanTree),还有的书翻译为霍夫曼树。
●赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
➢路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1
➢结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积
➢树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL(weighted path length) ,权值越大的结点离根结点越近的二叉树才是最优二叉树。
➢WPL最小的就是赫夫曼树
构成赫夫曼树的步骤
1)从小到大进行排序, 将每一个数据, 每个数据都是一个节点,每个节点可以看成是一颗最简单的二叉树
2)取出根节 点权值最小的两颗二叉树
3)组成一颗新的二叉树,该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
4)再将这颗新的二叉树,以根节点的权值大小再次排序,不断重复1-2-3-4的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
package com.xudong.DataStructures;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class HuffmanTreeDemo {
public static void main(String[] args) {
int arr[] = {13,7,8,3,29,6,1};
Node root = createHuffmanTree(arr);
preOrder(root);
}
//前序遍历的方法
public static void preOrder(Node root){
if (root != null){
root.preOrder();
}else {
System.out.println("这是个空树!");
}
}
//创建赫夫曼树
/**
* @param arr 需要创建成赫夫曼树的数组
* @return 创建好后的赫夫曼树的root节点
*/
public static Node createHuffmanTree(int[] arr){
//将arr的每个元素构成一个Node放入ArrayList中
List<Node> nodes = new ArrayList<>();
for (int value : arr){
nodes.add(new Node(value));
}
while (nodes.size() > 1){
//排序
Collections.sort(nodes);
//1.取出权值最小的节点(二叉树)
Node leftNode = nodes.get(0);
//2.取出权值第二小的节点(二叉树)
Node rightNode = nodes.get(1);
//3.创建一个新的二叉树
Node parent = new Node(leftNode.value + rightNode.value);
parent.left = leftNode;
parent.right = rightNode;
//4.从ArrayList删除处理过的二叉树
nodes.remove(leftNode);
nodes.remove(rightNode);
//5.将parent加入到nodes
nodes.add(parent);
}
//返回赫夫曼树root节点
return nodes.get(0);
}
}
//创建节点类,让Node对象持续进行Collections集合排序
class Node implements Comparable<Node>{
int value;//节点权值
Node left;//指向左子节点
Node right;//指向右子节点
//前序遍历
public void preOrder(){
System.out.println(this);
if (this.left != null){
this.left.preOrder();
}
if (this.right != null){
this.right.preOrder();
}
}
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
@Override
public int compareTo(Node o) {
//从小到大排序(从大到小加负号)
return (this.value - o.value);
}
}
六、二叉排序树
●二叉排序树BST:(Binary Sort(Search) Tree),对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
●特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点
package com.xudong.DataStructures;
public class BinarySortTreeDemo {
public static void main(String[] args) {
int[] arr = {7,3,10,12,5,1,9,2};
BinarySortTree binarySortTree = new BinarySortTree();
//添加节点
for (int i = 0; i < arr.length; i++) {
binarySortTree.add(new Node2(arr[i]));
}
//中序遍历二叉排序树
System.out.println("中序遍历二叉排序树:");
binarySortTree.infixOrder();
//删除叶子结点
binarySortTree.delNode(2);
binarySortTree.delNode(5);
binarySortTree.delNode(9);
binarySortTree.delNode(12);
binarySortTree.delNode(7);
binarySortTree.delNode(3);
binarySortTree.delNode(10);
System.out.println("删除节点后:");
binarySortTree.infixOrder();
}
}
//创建二叉排序树
class BinarySortTree{
private Node2 root;
//查找要删除的节点
public Node2 search(int value){
if (root == null){
return null;
}else {
return root.search(value);
}
}
//查找父节点
public Node2 searchParent(int value){
if (root == null){
return null;
}else {
return root.searchParent(value);
}
}
//删除右树的最小节点
public int delRightTreeMin(Node2 node){
Node2 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 {
//找到要删除的节点
Node2 targetNode = search(value);
//如果没有找到要删除的节点
if (targetNode == null){
return;
}
//如果发现二叉树只有一个节点
if (root.left == null && root.right == null){
root = null;
return;
}
//找到targetNode的父节点
Node2 parent = searchParent(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){//若targetNode是父节点的右子节点
parent.right = null;
}
}else if (targetNode.left != null && targetNode.right != null){//(三)删除右两颗子树的节点
int minVal = delRightTreeMin(targetNode.right);
targetNode.value = minVal;
}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;
}
}
}
}
}
//添加节点
public void add(Node2 node){
if (root == null){
root = node;
}else {
root.add(node);
}
}
//中序查找
public void infixOrder(){
if (root != null){
root.infixOrder();
}else {
System.out.println("二叉排序树为空!");
}
}
}
//创建Node节点
class Node2{
int value;
Node2 left;
Node2 right;
public Node2(int value) {
this.value = value;
}
//-------------删除节点-----------------
//查找要删除的节点
public Node2 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);
}
}
//查找要删除节点的父节点
public Node2 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 add(Node2 node){
if (node == null){
return;
}
//判断当前节点的值与当前子树根节点的关系
if (node.value < this.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);
}
}
}
@Override
public String toString() {
return "Node2{" +
"value=" + value +
'}';
}
//中序遍历
public void infixOrder(){
//递归向左子树中序遍历
if (this.left != null){
this.left.infixOrder();
}
//输出父节点
System.out.println(this);
//递归向右子树中序遍历
if (this.right != null){
this.right.infixOrder();
}
}
}
七、平衡二叉树(AVL树)
平衡二叉树介绍
在二叉排序树的基础上实现
●平衡二叉树也叫平衡二叉搜索树(Self-balancing binary searchtree)又被称为AVL树,可以保证查询效率较高。
●具有以下特点:它是一棵空树或它的根节点左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、 伸展树等。
左旋转
右旋转
双旋转
➢当符合右旋转条件时,它的左子树的右子树高度大于它左子树的高度时,就先对当前节点的左节点进行左旋转,再对当前节点进行右旋转
➢当符合左旋转条件时,它的右子树的左子树高度大于它右子树的高度时,就先对当前节点的右节点进行右旋转,再对当前节点进行左旋转
package com.xudong.DataStructures;
public class AVLTreeDemo {
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 avlTree = new AVLTree();
//添加节点
for (int i = 0; i < arr.length; i++) {
avlTree.add(new Node3(arr[i]));
}
System.out.println("中序遍历:");
avlTree.infixOrder();
System.out.println("在平衡处理之后:");
System.out.println("树的高度:" + avlTree.getRoot().height());
System.out.println("树的左子树高度:" + avlTree.getRoot().leftHeight());
System.out.println("树的右子树高度:" + avlTree.getRoot().rightHeight());
System.out.println("当前根节点:" + avlTree.getRoot());
}
}
//创建AVL树
class AVLTree{
private Node3 root;
public Node3 getRoot() {
return root;
}
//查找要删除的节点
public Node3 search(int value){
if (root == null){
return null;
}else {
return root.search(value);
}
}
//查找父节点
public Node3 searchParent(int value){
if (root == null){
return null;
}else {
return root.searchParent(value);
}
}
//删除右树的最小节点
public int delRightTreeMin(Node3 node){
Node3 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 {
//找到要删除的节点
Node3 targetNode = search(value);
//如果没有找到要删除的节点
if (targetNode == null){
return;
}
//如果发现二叉树只有一个节点
if (root.left == null && root.right == null){
root = null;
return;
}
//找到targetNode的父节点
Node3 parent = searchParent(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){//若targetNode是父节点的右子节点
parent.right = null;
}
}else if (targetNode.left != null && targetNode.right != null){//(三)删除右两颗子树的节点
int minVal = delRightTreeMin(targetNode.right);
targetNode.value = minVal;
}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;
}
}
}
}
}
//添加节点
public void add(Node3 node){
if (root == null){
root = node;
}else {
root.add(node);
}
}
//中序查找
public void infixOrder(){
if (root != null){
root.infixOrder();
}else {
System.out.println("二叉排序树为空!");
}
}
}
//创建Node节点
class Node3{
int value;
Node3 left;
Node3 right;
public Node3(int value) {
this.value = value;
}
//找到以根节点为节点的树高度
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();
}
//左旋转的方法
private void leftRotate(){
//创建新的节点,以当前根节点的值
Node3 newNode = new Node3(value);
//把新节点的左子树设置成当前节点的左子树
newNode.left = left;
//把新的节点的右子树设置成当前节点右子树的左子树
newNode.right = right.left;
//把当前结点的值替换成右子节点的值
value = right.value;
//把当前节点的右子树设置成当前节点右子树的右子树
right = right.right;
//把当前节点的左子节点设置成新的节点
left = newNode;
}
//右旋转
private void rightRotate(){
Node3 newNode = new Node3(value);
newNode.right = right;
newNode.left = left.right;
value = left.value;
left = left.left;
right = newNode;
}
//-------------删除节点-----------------
//查找要删除的节点
public Node3 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);
}
}
//查找要删除节点的父节点
public Node3 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 add(Node3 node){
if (node == null){
return;
}
//判断当前节点的值与当前子树根节点的关系
if (node.value < this.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);
}
}
//当添加完一个节点后,(右-左)子树高度差大于1时,左旋转
if (rightHeight() - leftHeight() > 1){
//如果它的右子树的左子树高度大于它的右子树高度
if (right != null && right.leftHeight() > right.rightHeight()){
//先对当前节点的右节点(右子树)进行右旋转
right.rightRotate();
//再对当前节点进行左旋转
leftRotate();
}else {
leftRotate();
}
return;
}
//当添加完一个节点后,(左-右)子树高度差大于1时,左旋转
if (leftHeight() - rightHeight() > 1){
//如果它的左子树的右子树高度大于它的左子树高度
if (left != null && left.rightHeight() > left.leftHeight()){
//先对当前节点的左节点(左子树)进行左旋转
left.leftRotate();
//再对当前节点进行右旋转
rightRotate();
}else {
rightRotate();
}
}
}
@Override
public String toString() {
return "Node2{" +
"value=" + value +
'}';
}
//中序遍历
public void infixOrder(){
//递归向左子树中序遍历
if (this.left != null){
this.left.infixOrder();
}
//输出父节点
System.out.println(this);
//递归向右子树中序遍历
if (this.right != null){
this.right.infixOrder();
}
}
}
八、多路查找树
●二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的节点很多(此如1亿),就存在如下问题
➢问题1:在构建叉树时,需要多次进行1/o操作海量数据存在数据库或文件中),节点海量,构建=叉树时,速度有影响
➢问题2:节点海量,也会造成二叉树的高度很大,会降低操作速度
1. 多叉树
●在二叉树中,每个节点有数据项,最多有两个子节点。如果允许每个节点可以有更多的数据项和更多的子节点,就是多叉树(multiwaytree)
●后面的2-3树,2-3-4树就是多叉树,多叉树通过重新组织节点,减少树的高度,能对二叉树进行优化。
2. B树
●如图B树通过重新组织节点,降低了树的高度
●文件系统及数据库系统的设计者利用了磁盘预读原理,将一个节点的大小设为等于一个页(页得大小通常为4k),这样每个节点只需要一次I/O就可以完全载入
●将树的度M设置为1024,在600亿个元素中最多只需要4次I/O操作就可以读取到想要的元素, B树(广泛应用于文件存储系统以及数据库系统中)
3. B+树
B+树是B树的变体,也是一种多路搜索树
4.B*树
B*树是B+树的变体,在B+树的非根和非叶子节点再增加指向兄弟的指针
5. 2-3树
2-3树是最简单的B树结构
●2-3树的所有叶子节点都在同一层(只要是B树都满足这个条件)
●有两个子节点的节点叫二节点,二节点要么没有子节点,要么有两个子节点.
●有三个子节点的节点叫三节点,三节点要么没有子节点,要么有三个子节点.
●2-3树是由二节点和三节点构成的树。