【AVL树(平衡二叉树)】
对于BST树而言,其优点是顺序性。但诸如{2,1,3,5,4,6}这样的数据所形成的BST树(如下图),其根节点左子树高度为2,右子树高度为4.两边高度相差大,这将导致在巨量数据存储时,两边高度差极大,结点单边存储而导致查找效率低下
AVL树就是为了平衡这种极端而来的。AVL树具有左旋,右旋,双旋操作。都是为了降低左右子树高度差,使之不超过1
核心代码实现:
1,AVL树是BST树的升级版。在旋转前,应该先要知道左右树的高度差是多少,有没有超过1。所以在BST树的Node类中追加 height,leftHeight,rightHeight方法,用于获取当前结点为父节点的子树高度,左子树的高度,右子树的高度。(其中height方法采用递归实现,非常精妙)
/**
* 相当精妙的算法:通过递归获得二叉树的高度
* @return 二叉树的高度
*/
public int height(){
//通过递归左右结点,为空时停止递归,否则继续递归,每轮递归高度加1,递归完成选取大的作为高度。佩服佩服
return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
}
/**
* 返回当前结点的左子树高度
* @return 左子树高度
*/
public int leftHeight(){
if (left==null){
return 0;
}
return left.height();
}
/**
* 返回当前结点的右子树高度
* @return 右子树高度
*/
public int rightHeight(){
if (right==null){
return 0;
}
return right.height();
}
2,当右子树高度-左子树高度>1 时,开始左旋:
1)复制出一个与当前结点一样的结点
2)将新结点的左子树与当前结点的左子节点相连,将当前结点的右子节点的左子结点与新结点的右结点相连
3)将当前结点的右子节点的值,赋给当前结点,并将当前结点的右节点与右子节点的右子节点相连
4)将当前结点的左节点与新结点相连
在Node中添加 leftRotate 方法,对当前结点,进行左旋平衡操作
/**
* 对当前结点,进行左旋平衡操作
*/
public void leftRotate(){
//1,复制出一个与当前结点一样的结点
Node newNode=new Node(this.data);
//2,将新结点的左子树与当前结点的左子节点相连,将当前结点的右子节点的左子结点与新结点的右结点相连
newNode.setLeft(this.left);
newNode.setRight(this.right.left);
//3,将当前结点的右子节点的值,赋给当前结点,并将当前结点的右节点与右子节点的右子节点相连
this.data=this.right.data;
this.right=this.right.right;
//4,将当前结点的左节点与新结点相连
this.left=newNode;
}
3,当左子树高度-右子树高度>1 时,开始右旋:
1)复制出一个与当前结点一样的结点
2)将新结点的右节点与当前结点的右子节点相连,将当前结点的左子节点的右子结点与新结点的左结点相连
3)将当前结点的左子节点的值,赋给当前结点,并将当前结点的左节点与左子节点的左子节点相连
4)将当前结点的右节点与新结点相连
在Node中添加 rightRotate 方法,对当前结点,进行右旋平衡操作
/**
* 对当前结点,进行右旋平衡操作
*/
public void rightRotate(){
//1,复制出一个与当前结点一样的结点
Node newNode=new Node(this.data);
//2,将新结点的右节点与当前结点的右子节点相连,将当前结点的左子节点的右子结点与新结点的左结点相连
newNode.setRight(this.right);
newNode.setLeft(this.left.right);
//3,将当前结点的左子节点的值,赋给当前结点,并将当前结点的左节点与左子节点的左子节点相连
this.data=this.left.data;
this.left=this.left.left;]
//4,将当前结点的右节点与新结点相连
this.right=newNode;
}
4,有了左旋和右旋的实现方法,我们现在可以在Node类的add方法中添加对结点左旋,右旋的判断。
当添加了结点使得树满足 rightHeight()-leftHeight()>1 就需要左旋,leftHeight()-rightHeight()>1 需要右旋。
但这还不够,若有一组数据{10,11,7,6,8,9},其通过直接右旋得到的结果,仍然不平衡(如下图)
这时候,就需要对它的局部进行左旋,当局部左旋后,再进行整体右旋(如下图),这样才能得到我们需要的平衡二叉树
结合上面的分析,对Node类的add方法进行修改
public void add(Node newNode){
if (newNode.getData()<this.getData()){//小了往左放
if (this.getLeft()==null){
this.setLeft(newNode);
}else {
this.getLeft().add(newNode);
}
}else {//大了往右放
if (this.getRight()==null){
this.setRight(newNode);
}else {
this.getRight().add(newNode);
}
}
if (rightHeight()-leftHeight()>1){//需要左旋
if (right!=null && right.leftHeight()>right.rightHeight()){//需要局部右旋
right.rightRotate();
leftRotate();
}else {//直接左旋
leftRotate();
}
return;//注意,这里要返回,否则就运行下面右旋代码去了
}
if (leftHeight()-rightHeight()>1){//需要右旋
if (left!=null && left.rightHeight()>left.leftHeight()){//需要局部左旋
left.leftRotate();
rightRotate();
}else {//直接右旋
rightRotate();
}
}
}
至此AVL树创建完成。
完整实现代码:
package cn.dataStructureAndAlgorithm.demo.tree.平衡二叉树_AVLTree;
class Node{
private int data;
private Node left;
private Node right;
public Node(int data) {
this.data = data;
}
public void setData(int data) {
this.data = data;
}
public int getData() {
return data;
}
public Node getLeft() {
return left;
}
public Node getRight() {
return right;
}
public void setLeft(Node left) {
this.left = left;
}
public void setRight(Node right) {
this.right = right;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
'}';
}
/**
* 符合BST树建立规则,左子节点小于父节点,右子节点大于父节点
* @param newNode 要添加的结点
*/
public void add(Node newNode){
if (newNode.getData()<this.getData()){//小了往左放
if (this.getLeft()==null){
this.setLeft(newNode);
}else {
this.getLeft().add(newNode);
}
}else {//大了往右放
if (this.getRight()==null){
this.setRight(newNode);
}else {
this.getRight().add(newNode);
}
}
if (rightHeight()-leftHeight()>1){//需要左旋
if (right!=null && right.leftHeight()>right.rightHeight()){//需要局部右旋
right.rightRotate();
leftRotate();
}else {//直接左旋
leftRotate();
}
return;//注意,这里要返回,否则就运行下面右旋代码去了
}
if (leftHeight()-rightHeight()>1){//需要右旋
if (left!=null && left.rightHeight()>left.leftHeight()){//需要局部左旋
left.leftRotate();
rightRotate();
}else {//直接右旋
rightRotate();
}
}
}
public void infixOrder(){
if (this.left!=null){
this.left.infixOrder();
}
System.out.print(this+" ");
if (this.right!=null){
this.right.infixOrder();
}
}
/**
* 按照输入的value,查找拥有相同值的结点
* @param value 匹配的值
* @return 找到返回结点,否则返回空
*/
public Node search(int value){
if (this.getData()==value){
return this;
}
if (value<this.getData() && this.getLeft()!=null){
return this.getLeft().search(value);
}else if (value>=this.getData() && this.getRight()!=null){
return this.getRight().search(value);
}else {
return null;
}
}
/**
* 按照value,查找到拥有相同值结点的父节点
* @param value 匹配的值
* @return 找到返回父节点,否则返回空
*/
public Node searchParent(int value){
if ((this.left != null && this.left.getData() == value) || (this.right != null && this.right.getData() == value)){
return this;
}
if (value<this.getData() && this.getLeft()!=null){
return this.getLeft().searchParent(value);
}else if (value>=this.getData() && this.getRight()!=null){
return this.getRight().searchParent(value);
}else {
return null;
}
}
/**
* 相当精妙的算法:通过递归获得二叉树的高度
* @return 二叉树的高度
*/
public int height(){
//通过递归左右结点,为空时停止递归,否则继续递归,每轮递归高度加1,递归完成选取大的作为高度。佩服佩服
return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
}
/**
* 返回当前结点的左子树高度
* @return 左子树高度
*/
public int leftHeight(){
if (left==null){
return 0;
}
return left.height();
}
/**
* 返回当前结点的右子树高度
* @return 右子树高度
*/
public int rightHeight(){
if (right==null){
return 0;
}
return right.height();
}
/**
* 对当前结点,进行左旋平衡操作
*/
public void leftRotate(){
//1,复制出一个与当前结点一样的结点
Node newNode=new Node(this.data);
//2,将新结点的左子树与当前结点的左子节点相连,将当前结点的右子节点的左子结点与新结点的右结点相连
newNode.setLeft(this.left);
newNode.setRight(this.right.left);
//3,将当前结点的右子节点的值,赋给当前结点,并将当前结点的右节点与右子节点的右子节点相连
this.data=this.right.data;
this.right=this.right.right;
//4,将当前结点的左节点与新结点相连
this.left=newNode;
}
/**
* 对当前结点,进行右旋平衡操作
*/
public void rightRotate(){
//1,复制出一个与当前结点一样的结点
Node newNode=new Node(this.data);
//2,将新结点的右节点与当前结点的右子节点相连,将当前结点的左子节点的右子结点与新结点的左结点相连
newNode.setRight(this.right);
newNode.setLeft(this.left.right);
//3,将当前结点的左子节点的值,赋给当前结点,并将当前结点的左节点与左子节点的左子节点相连
this.data=this.left.data;
this.left=this.left.left;
//4,将当前结点的右节点与新结点相连
this.right=newNode;
}
}
class AVLTree{
private Node root;
public Node getRoot() {
return root;
}
public void add(Node node){
if (node==null){
System.out.println("结点为空");
return;
}
if (this.root==null){
this.root=node;
}else{
this.root.add(node);
}
}
public void infixOrder(){
if (this.root==null){
System.out.println("根节点为空");
return;
}
this.root.infixOrder();
}
public Node search(int value){
if (root==null){
System.out.println("空树");
return null;
}
return this.root.search(value);
}
public Node searchParent(int value){
if (this.root==null){
System.out.println("空树");
return null;
}
return this.root.searchParent(value);
}
public void delete(int value) {
if (this.root == null) {
System.out.println("空树");
return;
}
Node result = search(value);
if (result == null) {
System.out.println("未搜索到");
return;
}
//0.只有一个节点
if (this.root.getLeft() == null && this.root.getRight() == null) {
this.root = null;
return;
}
Node parent = searchParent(value);
if (result.getRight() == null && result.getLeft() == null) {//1.是叶子结点
if (parent.getRight().getData() == value) {
parent.setRight(null);
} else {
parent.setLeft(null);
}
} else if (result.getLeft() != null && result.getRight() != null) {//2.含有左右子树的结点
int minValue = delRightMin(result.getRight());
result.setData(minValue);
} else {//3.只含有一个子树的结点
if (parent == null) {//若是根节点加一个子节点的情况(父节点为空)
if (result.getLeft() != null) {
this.root = result.getLeft();
} else {
this.root = result.getRight();
}
return;
}
if (result.getLeft() != null) {//含有左子树
if (parent.getLeft().getData() == value) {//目标结点是父节点的左节点
parent.setLeft(result.getLeft());
} else {//目标结点是父节点的右结点
parent.setRight(result.getLeft());
}
} else {//含有右子树
if (parent.getLeft().getData() == value) {
parent.setLeft(result.getRight());
} else {
parent.setRight(result.getRight());
}
}
}
}
public int delRightMin (Node node){
Node target = node;
while (target.getLeft() != null) {
target = target.getLeft();
}
delete(target.getData());
return target.getData();
}
}
public class 平衡二叉树_AVLTree {
public static void main(String[] args) {
AVLTree avlTree=new AVLTree();
int[] data=new int[]{10,11,7,6,8,9};
for (int temp:data){
avlTree.add(new Node(temp));
}
avlTree.infixOrder();
System.out.println();
System.out.println(avlTree.getRoot().leftHeight());
System.out.println(avlTree.getRoot().rightHeight());
System.out.println(avlTree.getRoot().height());
System.out.println(avlTree.getRoot());
}
}
Node{data=6} Node{data=7} Node{data=8} Node{data=9} Node{data=10} Node{data=11}
2
2
3
Node{data=8}
【红黑树】
红黑树,我在CSDN上找了下资料,感兴趣的朋友可以看一些。以后我用到了,我再来填坑。
【B树,B+树,B*树】
这3个B(树结构),很复杂。MySQL的数据库索引用的就是B+树。我在CSDN上找了下资料,感兴趣的朋友可以看一些。以后我用到了,我再来填坑。
有关树结构的其他内容,见下各链接
【数据结构与算法整理总结目录 :>】<-- 宝藏在此(doge)