zoukankan      html  css  js  c++  java
  • 数据结构篇——平衡二叉树(AVL树)

    引入

    上一篇写了二叉排序树,构建一个二叉排序树,如果构建序列是完全有序的,则会出现这样的情况:

    显然这种情况会使得二叉搜索树退化成链表。当出现这样的情况,二叉排序树的查找也就退化成了线性查找,所以我们需要合理调整二叉排序树的形态,使得树上的每个结点都尽量有两个子结点,这样整个二叉树的高度就会大约在(log(n)) 左右,其中 (n) 为结点个数。

    基本性质

    AVL树也称为平衡二叉树,是一种自平衡的二叉排序树,本质上仍然是一颗二叉排序树,只是增加了“平衡”的要求,平衡是指,对AVL树中任何节点的两个子树的高度之差(称为平衡因子)的绝对值不超过 (1) 。能保证上面这一点,AVL树的高度就能始终保持在 (O(logn)) 级别。

    数据结构定义

    由于需要对每个结点都得到平衡因子,因此在AVL树的结构中加入一个变量height,用来记录当前结点为根结点的子树的高度:

    typedef struct Node
    {
    	char data;
        int height;
    	struct Node* Left;
    	struct Node* Right;
    }*AVLTree;
    

    获取 root 结点高度:

    int getHeight(Node *root){
    	if(!root) return 0;//空节点高度为0
    	return root->height;
    }
    

    基本操作

    查找

    ​ AVL树是一颗二叉查找树,因此查找操作与二叉查找树相同。因为AVL树的高度为 (O(logn)) 级别,所以查找操作的时间复杂度为 (O(logn))

    可以得到和二叉查找树完全相同的代码:

    //找不到返回NULL,找到返回该节点。
    //非递归
    Node* Find(AVLTree t, int x) {
    	if (!t)return NULL;
    	if (t->data == x) return t;
    	if (x < t->data) return BSTreeFind(t->Left, x);
    	if (x > t->data) return BSTreeFind(t->Right, x);
    }
    //非递归
    Node* Find(AVLTree T,int x) {
    	BSTree p = T;
    	while (p) {
    		if (x == p->data)
    			return p;
    		p = x > p->data ? p->Right : p->Left;
    	}
    	return NULL;
    }
    

    插入

    左旋

    先抛开AVL树的插入问题,看下面左边的二叉排序树。大家本来和平共处,突然有一天 B 觉得自己的权值比 A 大,要造反,但是B要做根结点,必须也要保证调整后的树仍然是一颗二叉排序树。

    ☆上所有权值都比A小, ∆ 上所有权值都比B大,无需在调整中进行位置变动;因为调整后B的左孩子变成了A,那么▲必须移动到其他地方去,因为A、B、▲的权值关系满足 A<▲<B ,所以让▲成为A的右子树即可。

    这个调整过程称为左旋,分解调整过程如下:

    代码如下:

    void L(AVLTree *root){
        Node* temp = (*root)->Right; //root指向结点A,temp指向结点B
        (*root)->Right = temp->Left; //图示步骤2
        temp->Left = *root; //图示步骤3
        root->height = max(getHeight(root->Left), getHeight(root->Rihgt)) + 1;//更新结点A高度
    	temp = max(getHeight(temp->Left), getHeight(temp->Rihgt)) + 1;//更新结点B高度
        *root = temp;//图示步骤4
    }
    

    右旋

    右旋是左旋的逆过程,如下:

    分解调整过程如下:

    代码如下:

    void R(AVLTree *root) {
        Node* temp = (*root)->Left;//root指向结点B,temp指向结点A
        (*root)->Left = temp->Right;
        temp->Right = *root;
        root->height = max(getHeight(root->Left), getHeight(root->Rihgt)) + 1;
    	temp = max(getHeight(temp->Left), getHeight(temp->Rihgt)) + 1;
        *root = temp;
    }
    

    ​ 接下来讨论AVL树的插入操作,假设现在已经有一颗平衡二叉树,那么在向其中插入一个结点时,一定会有结点的平衡因子发生改变,此时就可能会有结点的平衡因子大于1 ,这样以该结点为根结点的子树就是失去平衡的,会使平衡二叉树发生失衡的情况可以分为下面四种:

    LL、RR型

    左左(LL)、右右(RR),LL,RR只表示树型(导致树失去平衡的插入位置),不是左旋、右旋的意思。

    img

    对于LL型,需要以A结点为根进行右旋;

    对于RR型,需要以A为根结点进行左旋。

    所以代码如下:

    void RR_Rotate(AVLTree *root){
      	L(root);
    }
    
    void LL_Rotate(AVLTree *root) {
        R(root);
    }
    

    LR,RL型

    左右(LR)、右左(RL)。

    img

    对于LR型,需要先以B结点为根结点进行一次左旋,再以A结点为根结点进行一次右旋。

    对于RL型,需要先以B结点为根结点进行一次右旋,再以A结点为根结点进行一次左旋。

    void LR_Rotate(AVLTree *root) {
        L(&(*root)->Left);
        R(root);
    }
    void RL_Rotate(AVLTree *root) {
        R(&(*root)->Right);
        L(root);
    }
    

    插入结点

    插入算法就是出现不平衡状态时,判断需要使用哪种旋转方式来使得二叉树保持平衡

    AVLTree InsertAVLTree(AVLTree root, int x) {
        if (root == NULL) {
            root = new Node;
            root->Left = NULL;
            root->Right = NULL;
            root->data = x;
            return root;
        }
        if (x > root->data) { 
            	//递归返回插入位置的父节点或者祖父……。
                root->Right = InsertAVLTree(root->Right, x); 
                //如果插入之后失去了平衡
                if (height(root->Left) - height(root->Right) == -2) {
                    //如果插入的值大于,当前节点的左孩子节点,说明该节点是插在root的右子树上的
                    if (x > root->Left->data) RR_Rotate(&root);
                    else RL_Rotate(&root);
                }
        }
    
        else if (x < root->data) {
            root->Left = InsertAVLTree(root->Left, x);
            if (height(root->Left) - height(root->Right) == 2) {
                if (x < root->Left->data) LL_Rotate(&root);
                else LR_Rotate(&root);
            }
        }
        else { 
            cout << "the number is already included." << endl;
            return NULL; 
        }
        return root;
    }
    

    删除结点

    和二叉排序树的节点的删除差不多,就是多出来一个判断从哪个子树删除节点的问题。

    void AVLTreeDel(AVLTree *root, int data)
    {
        if (!*root) {
            cout << "delete failed" << endl;
            return;
        }
        Node *p = *root;  
        if (data == p->data) {
            //左右子树都非空  
            if (p->Left && p->Right) {
                //在高度更大的那个子树上进行删除操作
                //进左子树,右转到底,进右子树,左转到底,转弯碰壁,杀孩子。
                if (height(p->Left) > height(p->Right)) {
                    Node *pre=NULL,*q = p->Left;
                    if (!q->Right)
                        q->Right = p->Right;
                    else {
                        while (q->Right) {
                            pre = q;
                            q = q->Right;
                        }
                        pre->Right = q->Left;
                        q->Left = p->Left;
                        q->Right = p->Right;
                    }
                    *root = q;
                }
                else {
                    Node *pre = NULL, *q = p->Right;
                    if (!q->Left)
                        q->Left = p->Left;
                    else {
                        while (q->Left) {
                            pre = q;
                            q = q->Left;
                        }
                        pre->Left = q->Right;
                        q->Left = p->Left;
                        q->Right = p->Right;
                    }
                    *root=q;
                }
            }
            else 
                (*root) = (*root)->Left ? (*root)->Left : (*root)->Right;
            delete p;
        }
        else if (data < p->data){//要删除的节点在左子树中  
            //在左子树中进行递归删除  
            AVLTreeDel(&(*root)->Left, data);
            //判断是否仍然满足平衡条件  
            if (height(p->Right) - height(p->Left) == 2){
                //如果当前节点右孩子的左子树更高
                if (height(p->Right->Left) > height(p->Right->Right))
                    RL_Rotate(root);
                else  
                    RR_Rotate(root);
            }
        }
        else{
            AVLTreeDel(&(*root)->Right, data);
            if (height(p->Left) - height(p->Right) == 2) {
                if (height((*root)->Left->Left) > height((*root)->Left->Right))
                    LL_Rotate(root);
                else
                    LR_Rotate(root);
            }
        }
    }
    

    完整测试代码:

    #pragma once
    #include "top.h"
    
    typedef BTreeNode Node, *AVLTree;
    
    void RR_Rotate(AVLTree *root){
        Node* Right = (*root)->Right;
        (*root)->Right = Right->Left;
        Right->Left = *root;
        *root = Right;
    }
    
    void LL_Rotate(AVLTree *root) {
        Node* Left = (*root)->Left;
        (*root)->Left = Left->Right;
        Left->Right = *root;
        *root = Left;
    }
    void LR_Rotate(AVLTree *root) {
        RR_Rotate(&(*root)->Left);
        return LL_Rotate(root);
    }
    void RL_Rotate(AVLTree *root) {
        LL_Rotate(&(*root)->Right);
        RR_Rotate(root);
    }
    AVLTree AVLTreeInsert(AVLTree root, int x) {
        if (root == NULL) {
            root = new Node;
            root->Left = NULL;
            root->Right = NULL;
            root->data = x;
            return root;
        }
        if (x > root->data) { 
                root->Right = AVLTreeInsert(root->Right, x); 
                //递归返回插入位置的父节点或者祖父……,如果失去了平衡
                if (height(root->Left) - height(root->Right) == -2) {
                //如果插入的值大于,当前节点的右孩子节点,说明该节点是插在root的右子树上的
                    //if (x > root->Left->data) RR_Rotate(&root);不能保证该节点一定有左子树
                    if (x > root->Right->data)RR_Rotate(&root);
                    else RL_Rotate(&root);
                }
        }
        else if (x < root->data) {
            root->Left = AVLTreeInsert(root->Left, x);
            if (height(root->Left) - height(root->Right) == 2) {
                if (x < root->Left->data) LL_Rotate(&root);
                else LR_Rotate(&root);
            }
        }
        else { 
            cout << "the number is already included." << endl;
            return NULL; 
        }
        return root;
    }
    
    AVLTree AVLTreeCreat(int *a, int length) {
        AVLTree T = NULL;
        for (int i = 0; i < length; i++) {
            T = AVLTreeInsert(T, a[i]);
        }
        return T;
    }
    Node* AVLFind(AVLTree T, int x) {
        Node *p = T;
        while (p) {
            if (x == p->data) break;
            p = x > p->data ? p->Right : p->Left;
        }
        return p;
    }
    
    AVLTree AVLMax(AVLTree p)
    {
        if (!p) return NULL;
        if (p->Right == NULL)
            return p;
        return AVLMax(p->Right);
    }
    
    AVLTree AVLMin(AVLTree p)
    {
        if (!p)
            return NULL;
        if (p->Left == NULL)
            return p;
        return AVLMin(p->Left);
    }
    
    void AVLTreeDel(AVLTree *root, int data)
    {
        if (!*root) {
            cout << "delete failed" << endl;
            return;
        }
        Node *p = *root;  
        if (data == p->data) {
            //左右子树都非空  
            if (p->Left && p->Right) {
                //在高度更大的那个子树上进行删除操作
                //进左子树,右转到底,进右子树,左转到底,转弯碰壁,杀孩子。
                if (height(p->Left) > height(p->Right)) {
                    Node *pre=NULL,*q = p->Left;
                    if (!q->Right)
                        q->Right = p->Right;
                    else {
                        while (q->Right) {
                            pre = q;
                            q = q->Right;
                        }
                        pre->Right = q->Left;
                        q->Left = p->Left;
                        q->Right = p->Right;
                    }
                    *root = q;
                }
                else {
                    Node *pre = NULL, *q = p->Right;
                    if (!q->Left)
                        q->Left = p->Left;
                    else {
                        while (q->Left) {
                            pre = q;
                            q = q->Left;
                        }
                        pre->Left = q->Right;
                        q->Left = p->Left;
                        q->Right = p->Right;
                    }
                    *root=q;
                }
            }
            else 
                (*root) = (*root)->Left ? (*root)->Left : (*root)->Right;
            delete p;
        }
        else if (data < p->data){//要删除的节点在左子树中  
            //在左子树中进行递归删除  
            AVLTreeDel(&(*root)->Left, data);
            //判断是否仍然满足平衡条件  
            if (height(p->Right) - height(p->Left) == 2){
                //如果当前节点右孩子的左子树更高
                if (height(p->Right->Left) > height(p->Right->Right))
                    RL_Rotate(root);
                else  
                    RR_Rotate(root);
            }
        }
        else{
            AVLTreeDel(&(*root)->Right, data);
            if (height(p->Left) - height(p->Right) == 2) {
                if (height((*root)->Left->Left) > height((*root)->Left->Right))
                    LL_Rotate(root);
                else
                    LR_Rotate(root);
            }
        }
    }
    
    int height(BTree L) {
    	if (L == NULL)
    		return 0;
    	int left = height(L->Left);
    	int right = height(L->Right);
    	return left >= right ? left + 1 : right + 1;
    }
    
    void checkCreat() {
        int length = 10;
        int *a = getNoRepateRandomArray(length, 10);
    
        for (int i = 0; i < length; i++) {
            cout << a[i] << ",";
        }
            
        cout << endl;
        AVLTree T = AVLTreeCreat(a, length);
        int t = rand() % length;
        AVLTreeDel(&T, a[t]);
        for (int i = t; i < length - 1; i++) {
            a[i] = a[i + 1];
        }
        
        Preorder(T);
        cout << endl;
        Inorder(T);
        cout << endl;
        Postorder(T);
        cout << endl;
        free(a);
    }
    
  • 相关阅读:
    ubuntu 安装(install) pwntcha[一个做"验证码识别"的开源程序]
    MySQL 二进制日志过滤
    MySQL 分区表各个分区的行数
    MySQL 表分区A
    MySQL show binglog event in 'log_name'
    MySQL、You are using safe update mode
    SQL 设计心得、逗号分隔列表
    MYSQL @、@@、@x
    SQL Server
    建设供内网访问的网站
  • 原文地址:https://www.cnblogs.com/czc1999/p/11794808.html
Copyright © 2011-2022 走看看