平衡二叉树平衡因子怎么计算_数据结构PHP 平衡二叉树(AVL)的平衡原理

这篇文章主要介绍一下 平衡二叉树(AVL),对于 二分搜索树 来说,如果树上的 元素 是顺序 添加的,会导致数据退化成一个 链表,这样就会造成很严重的性能问题,此时就需要在 二分搜索树 的基础上,保证元素插入时平衡,在了解 AVL 之前,需要您对 二分搜索树 有一定的了解,可以参考之前的文章。

1.二分搜索树的问题

如下图所示,若向二分搜索树中添加的元素是按照 顺序 从小到大依次插入的,这样会导致最后节点退化成链表:4826f07bd8374a663649e3c0e681604c.png

Tips:反过来依次从大到插入元素也会出现退化成 链表

2.二叉树节点高度标注示意图

ee9616fab3ffb79d0abe80024a4788e0.png

Tips:平衡因子等于左儿子高度减去右儿子高度,如45 和这个节点平衡因子 -2

3.平衡二叉树特点

  • 对于任何一个节点,左儿子树 和 右儿子树 的高度差(Δh)的绝对值不能超过 1高度差(Δh) 称为 平衡因子

  • 本质是带了平衡功能的二分搜索树

  • 平衡二叉树(AVL) 的高度(h)节点数之间的关系是 O(logn)级别的。

4.节点定义 PHP 代码

对于 二分搜索树 来说,AVL 需要在插入元素的时候保证 平衡,就需要引入节点高度(h),所以节点定义需要在 二分搜索树 节点定义的基础上增加一个 height 属性:

class AVLNode{
    
public $e;
public $left = null;
public $right = null;
public $height = 1;

/**
* 构造函数 初始化节点数据
* Node constructor.
* @param $e
*/
public function __construct($e){
$this->e = $e;
}
}

5.计算平衡因子

平衡因子等于左儿子高度减去右儿子高度,代码如下:

    /**
* 获取 AVL 节点的高度 h
* @param AVLNode $node
* @return int
*/
private function getHeight(AVLNode $node){
if ($node == null) {
return 0;
}
return $node->height;
}
/**
* 获取节点平衡因子
* @param AVLNode $node
* @return int\
*/
private function getBalanceFactor(AVLNode $node){
if ($node == null){
return 0;
}
return $this->getHeight($node->left) - $this->getHeight($node->right);
}

6.更新节点高度

若递归插入元素时,需要更新节点高度,递归时当前节点高度 h = 1 + (左右儿子中最大h),代码如下:

 /**
* 向 AVL 添加元素
* @param $e
*/
public function add($e){
$this->root = $this->recursionAdd($this->root, $e);
}

/**
* 递归向 AVL 添加元素
* @param Node $root
* @param $e
*/
private function recursionAdd($root, $e){
if ($root == null) { //若节点为空则添加元素 并且返回当前节点信息
$root = new AVLNode($e);
$this->size++;
} elseif ($e < $root->e) { //若元素小于当前节点元素 则向左节点递归添加元素
$root->left = $this->recursionAdd($root->left, $e);
} elseif ($e > $root->e) { //若元素大于当前节点元素 则向右节点递归添加元素
$root->right = $this->recursionAdd($root->right, $e);
} //若元素等于当前节点元素 则什么都不做


//更新节点高度
$root->height = 1 + ($this->getHeight($root->left) > $this->getHeight($root->right) ? $this->getHeight($root->left) : $this->getHeight($root->right));
return $root;
}

Tips:此处代码直接贴出之前 二分搜索树 代码添加元素方法。

7.判断二叉树是否为二分搜索树

若要判断二叉树是否为二分搜索树,则需要将二叉树中序遍历,若输出结果是按照顺序依次从小到大排列的,则表示该 二叉树 是一颗 二分搜索树,采用递归思想层序遍历然后将节点元素依次入队,代码如下,然后依次出队查看出队元素是否依次增大:

    /**
* 判断二叉树是否为二分搜索树
*/
public function isBST(){
$queue = new QueueByLinkedList();
$this->inOrder($this->root, $queue);
do {
$e = $queue->dequeue();
if ($queue->getSize() > 0 && $e > $queue->getFront()) {
return false;
}
} while ($queue->getSize() > 0);

return true;
}

/**
* 中序遍历二分搜索树
*/
private function inOrder($root, $queue){
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
#include <iostream> #include<stack> #include<queue> using namespace std; const int LH=1; //左子树比右子树高1 const int EH=0; //左右子树一样高 const int RH=-1;//右子树比左子树高1 const int MAX_NODE_NUM=20; //结点数目上限 class AvlNode { int data; int bf; //平衡因子 AvlNode *lchild; AvlNode *rchild; friend class AVL_Tree; }; class AVL_Tree { public: int Get_data(AvlNode *p) { return p->data; } void Create_AVl(AvlNode *&T) //建树 { cout<<"输入平衡二叉树的元素,输入-1代表结束输入:"; int num[MAX_NODE_NUM]; int a,i=0; while(cin>>a && a!=-1) { num[i]=a; i++; } if(num[0]==-1) { cout<<"平衡树为空"<<endl; T=NULL; return; } int k=i; bool taller=false; for(i=0;i<k;i++) Insert_Avl(T,num[i],taller);//逐个进行插入,插入过程看下面的示意图 cout<<"_____建树完成____"<<endl; } void L_Rotate(AvlNode *&p) { //以p为根节点的二叉排序树进行单向左旋处理 AvlNode *rc=p->rchild; p->rchild=rc->lchild; rc->lchild=p; p=rc; } void R_Rotate(AvlNode *&p) { //以p为根节点的二叉排序树进行单向右旋处理 AvlNode *lc=p->lchild; p->lchild=lc->rchild; lc->rchild=p; p=lc; } void Left_Balance(AvlNode *&T) { //以T为根节点的二叉排序树进行左平衡旋转处理 AvlNode *lc,*rd; lc=T->lchild; switch(lc->bf) { case LH: //新结点插在T的左孩子的左子树上,做单向右旋处理 T->bf=lc->bf=EH; R_Rotate(T); break; case RH: //新结点插在T的左孩子的右子树上,要进行双旋平衡处理(先左后右) rd=lc->rchild; switch(rd->bf) { case LH: //插在右子树的左孩子上 T->bf=RH; lc->bf=EH; break; case EH: T->bf=lc->bf=EH; break; case RH: T->bf=EH; lc->bf=LH; break; } rd->bf=EH; L_Rotate(T->lchild);//先对T的左子树进行单向左旋处理 R_Rotate(T); //再对T进行单向右旋处理 } } void Right_Balance(AvlNode *&T) { //以T为根节点的二叉排序树进行右平衡旋转处理 AvlNode *rc,*ld; rc=T->rchild; switch(rc->bf) { case RH: //新结点插在右孩子的右子树上,进行单向左旋处理 T->bf=rc->bf=EH; L_Rotate(T); break; case LH: //新结点插在T的右孩子的左子树上,要进行右平衡旋转处理(先右再左) ld=rc->lchild; switch(ld->bf) { case LH: T->bf=LH; rc->bf=EH; break; case EH: T->bf=rc->bf=EH; break; case RH: T->bf=EH; rc->bf=RH; break; } ld->bf=EH; R_Rotate(T->rchild);//先对T的右子树进行单向右旋处理 L_Rotate(T); //再对T进行单向左旋处理 } } bool Insert_Avl(AvlNode *&T,int num,bool &taller) //插入 { //若在平衡二叉树中不存在结点值和num一样大小的结点 //则插入值为num的新结点,并返回true //若因为插入而使得二叉排序树失去平衡,则做平衡旋转处理 //taller反映树是否长高 if(!T) { //插入新结点,树长高,taller为true T=new AvlNode; T->data=num; T->lchild=T->rchild=NULL; T->bf=EH; taller=true; } else { if(num==T->data) { //不重复插入 taller=false; return false; } if(num<T->data) //继续在T的左子树中进行搜索 { if(!Insert_Avl(T->lchild,num,taller))//插入不成功 return false; if(taller) //已插入T的左子树,且左子树长高 { switch(T->bf) { case LH: /*————————————————————— / 插入前左子树高于右子树,需要进行做平衡处理 / 不管是单向左旋处理,还是先左后右平衡处理 / 处理结果都是使得插入新结点后,树的高度不变 /—————————————————————*/ Left_Balance(T); taller=false; break; case EH: //插入前左右子树等高,现在插入新街点后,左子树比右子树高 T->bf=LH; taller=true; break; case RH: //插入前右子树比左子树高,现在新结点插入左子树后,树变为左右子树等高 T->bf=EH; taller=false; break; } } } else { //num>T->data 在T的右子树中继续搜索 if(!Insert_Avl(T->rchild,num,taller)) return false; if(taller) { switch(T->bf) { case LH: //插入前左子树比右子树高,现在插入T的右子树后,左右子树等高 T->bf=EH; taller=false; break; case EH: //插入前左右子树等高,现在插入后,右子树比左子树高 T->bf=RH; taller=true; break; case RH: //插入前右子树比坐子树高,插入后,排序树失去平衡,需要进行右平衡处理 Right_Balance(T); taller=false; break; } } } } return true; } bool Search_Avl(AvlNode *T,int num,AvlNode *&f,AvlNode *&p) //搜索 { //用p带回查找到的顶点的地址,f带回p的双亲结点 p=T; while(p) { if(p->data==num) return true; if(p->data>num) { f=p; p=p->lchild; } else { f=p; p=p->rchild; } } return false; } void Delete_AVL(AvlNode *&T,int num) //删除,删除后没有回溯到根节点,算法有错,待日后修改完善,有心的朋友可以自己加一个栈或者其他方式来实现 { /*--------------------------------------------------------- / 从树中删除一个节点后,要保证删后的树还是一棵平衡二叉树, / 删除前,首先是在树中查找是否有这个结点,用p指向该结点, / 用f指向p的双亲结点,这个结点在树中的位置有下面四种情况: / / 1:如果p指向的结点是叶子结点,那么直接将f指针的左子树或者 / 右子树置空,然后删除p结点即可。 / / 2:如果p指向的结点是只有左子树或右子树,那么只需要让p结点 / 原来在f中的位置(左子树或右子树)用p的子树代替即可。 / 代替后,要修改f的平衡因子,在失去平衡的时候,要调用相应的 / 做平衡旋转或右平衡旋转进行恢复. / / 3:如果p所指向的结点是根节点,那么直接将根节点置空 / / 4:如果p所指向的结点左右子树都非空,为了删除p后原序列的顺 / 序不变,就需要在原序列中先找出p的直接前驱(或者直接后继) / 结点用那个结点的值来代替p结点的值,然后再删掉那个直接前 / 驱(或者直接后继)结点。 / 其中s指向的是要删除的结点,也就是p的直接前驱,q指向的是 / s的双亲结点,此时,应该看s的平衡因子,在会出现失去平衡的 / 情况时,就要根据实际情况采用左平衡旋转或是右平衡旋转,让 / 树恢复平衡,这点和插入操作时是相对应的。 / / 在中序遍历序列中找结点的直接前驱的方法是顺着结点的左孩子 / 的右链域开始,一直到结点右孩子为空为止。 /---------------------------------------------------------*/ AvlNode *f=NULL; AvlNode *p=NULL; AvlNode *q=NULL; AvlNode *s=NULL; if(Search_Avl(T,num,f,p)) { if(p->lchild && p->rchild) //左右子树均存在时 { q=p; s=p->lchild; while(s->rchild) { q=s; s=s->rchild; } p->data=s->data; if(q!=p) { //q结点的右子树高度减少1 //所以平衡因子会+1 q->rchild=s->lchild; switch(q->bf) { //删除前右子树高,现在就变成一样高 case RH: q->bf=EH; break; //删除前等高,现在就变成左子树比右子树高 case EH: q->bf=LH; break; //删除前左子树高,现在左子树又高了一,所以失去平衡 case LH: q->bf=EH; Left_Balance(q); break; } } else { //p的左子树的右子树为空时 //q结点也就是p结点,由于s的右子树为空 //所以q结点的左子树高度降低1 //平衡因子-1 q->lchild=s->lchild; switch(q->bf) { case LH: q->bf=EH;break; case EH: q->bf=RH;break; case RH: q->bf=EH; Right_Balance(q); break; } } delete s; cout<<"删除结点成功"<<endl; return ; } else { if(!p->lchild) { q=p; p=p->rchild; } else { q=p; p=p->lchild; } if(!T) { T->bf=EH; T=p; } else if(q==f->lchild) { f->lchild=p; switch(f->bf) { case LH: f->bf=EH; break; case EH: f->bf=RH; break; case RH: f->bf=EH; Right_Balance(f); break; } } else { f->rchild=p; switch(f->bf) { case RH: f->bf=EH; break; case EH: f->bf=LH; break; case LH: f->bf=EH; Left_Balance(f); break; } } delete q; cout<<"删除结点成功"<<endl; return; } } else { cout<<"要删除的结点不存在"<<endl; return; } } InOrder_Traverse(AvlNode *T) //中序遍历 { stack<AvlNode *> s; AvlNode *p=T; while(p || !s.empty()) { if(p) { s.push(p); p=p->lchild; } else { p=s.top(); s.pop(); cout<<p->data<<" "; p=p->rchild; } } } void Level_Traverse(AvlNode *T) //层次遍历 { queue<AvlNode *> q; AvlNode *p=T; q.push(p); while(!q.empty()) { p=q.front(); q.pop(); cout<<p->data<<" "; if(p->lchild) q.push(p->lchild); if(p->rchild) q.push(p->rchild); } } }; //测试文件"main.cpp" //#include"tree.h" int main() { AVL_Tree tree; AvlNode *root=NULL; cout<<"____建立平衡二叉树____"<<endl; tree.Create_AVl(root); cout<<"中序遍历二叉树为:"; tree.InOrder_Traverse(root); cout<<endl; cout<<"层次遍历二叉树为:"; tree.Level_Traverse(root); cout<<endl; int num; bool taller=false; cout<<"输入你要插入的结点的值:"; cin>>num; tree.Insert_Avl(root,num,taller); cout<<"中序遍历二叉树为:"; tree.InOrder_Traverse(root); cout<<endl; AvlNode *f=NULL; AvlNode *p=NULL; cout<<"输入你要搜索的结点的值:"; cin>>num; if(tree.Search_Avl(root,num,f,p)) { cout<<"查找得到的结点值为:"<<tree.Get_data(p)<<"的地址为:"<<p<<endl; if(f==NULL) cout<<"因为结点"<<tree.Get_data(p)<<"是根结点,所以没有双亲结点"<<endl; else cout<<"该结点的双亲结点的值为:"<<tree.Get_data(f)<<endl; } else cout<<"查找的结点不存在"<<endl; cout<<"输入你要删除的结点的值:"; cin>>num; tree.Delete_AVL(root,num); cout<<"中序遍历二叉树为:"; tree.InOrder_Traverse(root); cout<<endl; return 0; }
平衡二叉树平衡因子是指每个节点的左子树高度减去右子树高度的值。平衡因子可以用来判断一个节点是否平衡,如果平衡因子的绝对值大于1,则该节点不平衡。 在Java中实现平衡二叉树时,可以通过计算每个节点的平衡因子来判断是否需要进行平衡操作。当插入或删除节点后,如果某个节点的平衡因子超过了1或小于-1,就需要进行相应的平衡操作,以保持树的平衡性。 以下是一个示例代码,演示了如何计算平衡二叉树平衡因子: ```java class Node { int value; Node left; Node right; public Node(int value) { this.value = value; this.left = null; this.right = null; } } class AVLTree { Node root; // 计算节点的高度 private int height(Node node) { if (node == null) { return 0; } return Math.max(height(node.left), height(node.right)) + 1; } // 计算节点的平衡因子 private int balanceFactor(Node node) { if (node == null) { return 0; } return height(node.left) - height(node.right); } // 插入节点 public void insert(int value) { root = insertNode(root, value); } private Node insertNode(Node node, int value) { if (node == null) { return new Node(value); } if (value < node.value) { node.left = insertNode(node.left, value); } else if (value > node.value) { node.right = insertNode(node.right, value); } else { // 如果节点值已存在,则不进行插入操作 return node; } // 更新节点的平衡因子 int balance = balanceFactor(node); // 左子树不平衡 if (balance > 1) { if (value < node.left.value) { // 左-左情况,进行右旋转 return rightRotate(node); } else { // 左-右情况,先左旋转再右旋转 node.left = leftRotate(node.left); return rightRotate(node); } } // 右子树不平衡 if (balance < -1) { if (value > node.right.value) { // 右-右情况,进行左旋转 return leftRotate(node); } else { // 右-左情况,先右旋转再左旋转 node.right = rightRotate(node.right); return leftRotate(node); } } return node; } // 左旋转 private Node leftRotate(Node node) { Node newRoot = node.right; node.right = newRoot.left; newRoot.left = node; return newRoot; } // 右旋转 private Node rightRotate(Node node) { Node newRoot = node.left; node.left = newRoot.right; newRoot.right = node; return newRoot; } } public class Main { public static void main(String[] args) { AVLTree tree = new AVLTree(); tree.insert(10); tree.insert(20); tree.insert(30); tree.insert(40); tree.insert(50); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值