首先明白我们为什么使用二叉树来存储,并进行查找,它和我们通常的链式存储和线性存储有什么优势,分析完毕之后,我们再学习二叉树就很有针对性。
- 对于数组来说,我们查找数据替换数据很方便,但是遇到插入数据和删除数据来说就很麻烦。
- 对于单链表来说,方便进行插入和删除,但是查找的话就很不方便。基本事件复杂度都在O(n)
- 对于二叉树来说,用它存储的数据对于查询和删除,时间复杂度得到很大的提升。对于二叉排序树,如根节点的左右孩子的高度相差为零的话,查找的效率和数组的二分查找差不多。对于二叉树结点的删除来说,要进行分情况讨论,本文暂时不解释。
二叉排序树的构建规则
- 给出要进行构建二叉树的数组
int[] arr = {21,3,11,2,13,4,5}
- 添加结点时,如果添加的结点大于当前结点(当前结点没有右孩子)就往该结点的右子树上进行添加;如果要添加的结点小于当前结点(当前结点没有左孩子)就往该节点的左子树上进行添加。
示例图
具体的构建代码如下
树的结点类
class NodeSort{
int value;
NodeSort left;
NodeSort right;
public NodeSort(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
public void middleOrder(){//中序遍历
if(this.left!=null){
this.left.middleOrder();
}
System.out.println(this.toString());
if(this.right!=null){
this.right.middleOrder();
}
}
protected void addNode(NodeSort node){//核心算法
if(this.value>node.value){
if(this.left!=null) {
this.left.addNode(node);
}else{
this.left=node;
}
}else{
if(this.right!=null){
this.right.addNode(node);
}else{
this.right=node;
}
}
}
}
二叉排序树里面的方法
class BinarySort{
NodeSort root;
//添加结点
public static void addNode(NodeSort node){
if(root==null){
root = node;
}else{
root.addNode(node);
}
}
//中序遍历
public static void moiddleSort(){
if(root!=null){
root.middleOrder();
}else{
System.out.println("无结点值");
}
}
public static void main(String[] args) {
int[] arr = {21,3,11,2,13,4,5}
for(int i =0;i<arr.length;i++){
addNode(new NodeSort(arr[i]));
}
}
}
总结 对于上述构建的二叉排序树,我们发现根节点没有右子树,而且该二叉树的高度很高。
二叉树的高度越高,查询的效率就会变慢。因此我们需要经该二叉树进行平衡化处理,以此来提高二叉树的查找效率。
平衡二叉树
右旋
示例数组int[] arr ={10,12,8,9,7,6};
对应的二叉排序树如下
右旋思想
- 如果在添加结点的时候,根节点的左子树的高度减去根节点的右子树的高度大于1的话就进行右旋
- 当前结点的值重新构建一个新的结点(调用addNode(NodeSort node)方法的对象)在这里root调用的addNode方法,因此当前结点就是root
NodeSort newnode = new NodeSort(value)
– value是root的结点值。 - 新结点的右子树是当前结点的右子树
newnode.right=right
- 新结点的左子树是当前结点的左子树的右子树
newnode.left = left.right
- 当前结点的值是当前结点左子树的值
value = left.value
- 左子树等于左子树的左子树
left = left.left
- 右子树是新节点
right= newnode
右旋调整后的二叉树是
左旋
示例数组int[] arr ={4,3,6,5,7,8}
对应的二叉排序树如下
左旋思想
- 如果在添加结点的时候,根节点的右子树的高度减去根节点的左子树的高度
大于1
的话就进行左旋 - 当前结点的值重新构建一个新的结点(调用addNode(NodeSort node)方法的对象)在这里root调用的addNode方法,因此当前结点就是root
NodeSort newnode = new NodeSort(value)
– value是root的结点值。 - 新结点的左子树是当前结点的左子树
newnode.left=left
- 新结点的右子树是当前结点的右子树的左子树
newnode.right = right.left
- 当前结点的值是当前结点右子树的值
value = right.value
- 右子树等于右子树的右子树
right= right.right
- 左子树是新节点
left= newnode
但是我们有时会遇到这样的情况,左旋或者右旋都无法达到平衡二叉树
例如如下。
int[] arr ={10,11,7,6,8,9};
这样的情况,因此在进行旋转的时候需要加上一条判断
如果左子树的高度减去右子树的高度大于1的话
如果左子树的左子树的高度小于左子树的右子树的高度,那么要对该左子树进行左旋处理,最后再进行右旋处理
如果右子树的高度减去左子树的高度大于1的话
如果右子树的右子树的高度小于右子树的左子树的高度,那么要对该右子树进行右旋处理,最后再进行左旋处理
package com.sofency.Tree;
/**
* @auther sofency
* @date 2020/3/8 14:30
* @package com.sofency.Tree
* @description 平衡二叉树
*/
public class AVLTree {
public static NodeTest root;//根节点
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};
int[] arr = {21,3,11,2,13,4,5};
for(int i=0;i<arr.length;i++){
addNode(new NodeTest(arr[i]));
}
System.out.println("平衡处理");
System.out.println("左孩子高度"+root.leftHeight());
System.out.println("右孩子高度"+root.rightHeight());
middleSort();//中序遍历输出
}
//中序遍历
public static void middleSort(){
if(root==null){
return;
}else{
root.middleOrder();//中序遍历
}
}
public static void addNode(NodeTest node){
if(root==null){
root=node;
}else{
root.addNodes(node);
}
}
}
class NodeTest implements Comparable{
int value;
NodeTest left;
NodeTest right;
public NodeTest(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//左孩子的结点
public int leftHeight(){
if(left==null){
return 0;
}
return left.height();
}
//右孩子的高度
public int rightHeight(){
if(right==null){
return 0;
}
return right.height();
}
//计算结点的深度 因为每个节点也可以简介的看错是一个是
public int height(){//返回深度
return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
}
public void middleOrder(){
if(this.left!=null){
this.left.middleOrder();
}
System.out.println(this.toString());
if(this.right!=null){
this.right.middleOrder();
}
}
//左旋
public void leftRotate(){
//1. 获取当前结点的值构建新结点
NodeTest nodeTest = new NodeTest(value);//
//新节点的左孩子连接的是当前结点的左孩子
nodeTest.left=left;
//新节点的右孩子是当前结点的右孩子的左孩子
nodeTest.right=right.left;
//当前的值是右孩子的值
value=right.value;
//右孩子是右孩子的的右孩子
right=right.right;
//左孩子是新构建的结点
left = nodeTest;
}
//右旋
public void rightRotate(){
NodeTest nodeTest = new NodeTest(value);
nodeTest.right=right;
nodeTest.left=left.right;
value=left.value;
left =left.left;
right=nodeTest;
}
//添加结点
public void addNodes(NodeTest node){
if(this.value>node.value){
if(this.left!=null){
this.left.addNodes(node);
}else{
this.left=node;
}
}else {
if (this.right != null) {
this.right.addNodes(node);
} else {
this.right = node;
}
}
if(rightHeight()-leftHeight()>1){//如果右孩子的高度减去左孩子的高度大于1进行左旋转
if(right!=null&&(right.leftHeight()>right.rightHeight())){
right.rightRotate();
}
leftRotate();
return;
}
//如果左孩子的高度-右孩子的高度大于1
if(leftHeight()-rightHeight()>1){//如果右孩子的高度减去左孩子的高度大于1进行左旋转
//如果左孩子的左孩子
if(left!=null&&(left.rightHeight()>left.leftHeight())){//如果左孩子的左孩子的高度小于左孩子的右孩子的高度 要进行左旋
left.leftRotate();
}
rightRotate();
}
}
@Override
public int compareTo(Object o) {
return this.value-((Node)o).value;
}
}