二叉树(下)-数据结构

一、二叉搜索树的局限性

1.BST的局限性

二叉搜索树在多次插入和删除操作后,可能退化为链表。在这种情况下,所有操作的时间复杂度将从O(log n)劣化为O(n)。

例:
二叉搜索树的退化
如该图所示,经过两次删除节点操作,这颗二叉搜索树便会退化为链表。

再例如:
在完美二叉树中插入两个节点后,树将严重倾斜,查找操作时间复杂度也会随之劣化。
在这里插入图片描述

2.解决思路

为克服BST的局限性,需要引入自平衡机制,主要解决方案包括:

方案平衡标准特点
AVL树严格平衡查询效率最高,维护成本较高
红黑树近似平衡综合性能好,维护成本适中

二、平衡二叉树

1.平衡二叉树基础

平衡二叉树是一种特殊的二叉搜索树(BST),它能通过一定的规则或调整方式,保证树的高度始终保持在 O(log n) 级别,从而确保查找、插入、删除等操作的时间复杂度稳定在 O(log n),避免退化成链表(最坏情况 O(n))。

2. AVL树:严格平衡

(1)平衡条件

每个节点的左右子树高度差<=1。
节点平衡因子=左树高度-右树高度
|height(left)-height(right)|<=1

typedef struct TreeNode {
    int val;
    int height;//由于AVL树的相关操作需要获取节点高度,因此我们需要为节点类添加height变量
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

TreeNode *newTreeNode(int val) {
    TreeNode *node;
    node = (TreeNode *)malloc(sizeof(TreeNode));
    node->val = val;
    node->height = 0;
    node->left = NULL;
    node->right = NULL;
    return node;
}
int balance(TreeNode *node) {
    //空节点平衡因子为0
    if (node == NULL) {
        return 0;
    }
    return height(node->left) - height(node->right);
}

(2)AVL树的判断

例1:在这里插入图片描述
该树就不是AVL平衡二叉树,对于3来讲,左边有2个节点,右边有0个节点,所以不平衡。

例2:在这里插入图片描述
对于该树来讲,每一个节点左右子树高度的绝对值不超过1。

例3:
在这里插入图片描述
对于该树节点,7节点的左子树比右多两层,所以不是AVL树。

(3)节点高度

节点高度是指从该节点到它最远叶节点的距离,即所经过的“边”的数量。叶节点高度为0,而空节点的高度为-1。以下代码用于获取和更新节点高度:

//获取节点高度
int height(TreeNode *node) {
    if (node != NULL) {
        return node->height;
    }
    //空节点高度为-1,叶节点为0
    return -1;
}
//更新节点高度
void update(TreeNode *node) {
    int lh = height(node->left);
    int rh = height(node->right);
    //节点高度等于最高子树高度+1
    if (lh > rh) {
        node->height = lh + 1;
    } else {
        node->height = rh + 1;
    }
}

(4)旋转操作

①LL旋转

在这里插入图片描述
插入1后,不平衡的发现者是3,麻烦节点1在发现者左子树的左边,因而叫LL插入,需要LL旋转。
注:“不平衡的发现者” 是指从插入点向上回溯时,遇到的第一个不平衡节点。

TreeNode *right(TreeNode *node) {
    TreeNode *child, *grandChild;
    child = node->left;
    grandChild = child->right;
    //以child为原点将node向右旋转
    child->right = node;
    node->left = grandChild;
    update(node);
    update(child);
    //返回旋转后子树的根节点
    return child;
}
②LR旋转

在这里插入图片描述
插入3后,不平衡的发现者是5,麻烦节点3在发现者左子树的右边,因而叫LR插入,需要LR旋转。

TreeNode *leftright(TreeNode *node) {
    // 获取节点 node 的平衡因子
    int bf = balance(node);
    // 左偏树
    if (bf > 1 && balance(node->left) < 0) {
    	//先左旋后右旋
    	node->left = left(node->left);
        return right(node);
        }
    } 
    return node;
}
③RR旋转

在这里插入图片描述
插入3后,不平衡的发现者是1,麻烦节点3在发现者右子树的右边,因而叫RR插入,需要RR旋转。

TreeNode *left(TreeNode *node) {
	TreeNode *child, *grandChild;
    child = node->right;
    grandChild = child->left;
    //以child为原点,将node向左旋转
    child->left = node;
    node->right = grandChild;
    update(node);
    update(child);
    //返回旋转后子树的根节点
    return child;
}
④RL旋转

在这里插入图片描述
插入4后,不平衡的发现者是2,麻烦节点4在发现者右子树的左边,因而叫RL插入,需要RL旋转。

TreeNode *rightleft(TreeNode *node) {
    int bf=balance(node);
    //右偏树
    if (bf < -1 && balance(node->right) > 0) {
    	// 先右旋后左旋
        node->right = right(node->right);
        return left(node);
    }
    return node;
}

(5)基本操作

①处理所有不平衡情况
//执行旋转操作,使该子树重新恢复平衡
TreeNode *turn(TreeNode *node) {
    int bf = balance(node);
    //左偏树
    if (bf > 1) {
        if (balance(node->left) >= 0) {
            //右旋
            return right(node);
        } else {
            //先左旋后右旋
            node->left = left(node->left);
            return right(node);
        }
    }
    //右偏树
    if (bf < -1) {
        if (balance(node->right) <= 0) {
            //左旋
            return left(node);
        } else {
            //先右旋后左旋
            node->right=right(node->right);
            return left(node);
        }
    }
    return node;
}
②插入节点

AVL树的节点插入操作和二叉搜索树在主体上类似。唯一的区别在于,在AVL树中插入节点后,从该节点到根节点的路径上可能会出现一系列不平衡衡节点。因此,我们需要从这个节点开始,自下向上执行旋转操作,使所有不平衡节点恢复平衡。

//插入
void insert(AVLTree *tree, int val) {
    tree->root = inserthelp(tree->root, val);
}

//递归插入节点
TreeNode *inserthelp(TreeNode *node, int val) {
    if (node == NULL) {
        return newTreeNode(val);
    }
    //查找插入位置并插入节点
    if (val < node->val) {
        node->left = inserthelp(node->left, val);
    } else if (val > node->val) {
        node->right = inserthelp(node->right, val);
    } else {
        //重复节点不插入,直接返回
        return node;
    }
    update(node);
    //执行旋转操作,使该子树重新恢复平衡
    int bf=balance(node);
    if (bf > 1) {
    	if (balance(node->left) >= 0) {  // LL型
        	node = right(node);
    	} else {                        // LR型
        	node = leftright(node);
    	}
	}
	if (bf < -1) {
   		if (balance(node->right) <= 0) { // RR型
        	node = left(node);
    	} else {                        // RL型
        	node = rightleft(node);
    	}
	}
    //返回子树的根节点
    return node;
}
③删除节点

在二叉搜索树的删除节点的基础上,需要从下至上执行旋转操作,使所有失衡节点恢复平衡。

//删除节点
void delete(AVLTree *tree, int val) {
    tree->root = deletehelp(tree->root, val);
}

//递归删除节点
TreeNode *deletehelp(TreeNode *node, int val) {
    TreeNode *child, *grandChild;
    if (node == NULL) {
        return NULL;
    }
    //查找节点并删除
    if (val < node->val) {
        node->left = deletehelp(node->left, val);
    } else if (val > node->val) {
        node->right = deletehelp(node->right, val);
    } else {
        if (node->left == NULL || node->right == NULL) {
            child = node->left;
            if (node->right != NULL) {
                child = node->right;
            }
            //子节点数量=0,直接删除node并返回
            if (child == NULL) {
                return NULL;
            } else {
                //子节点数量=1,直接删除node
                node = child;
            }
        } else {
            //子节点数量=2,则将中序遍历的下个节点删除,并用该节点替换当前节点
            TreeNode *temp = node->right;
            while (temp->left != NULL) {
                temp = temp->left;
            }
            int Val = temp->val;
            node->right = deletehelp(node->right, temp->val);
            node->val = Val;
        }
    }
    update(node);
    //执行旋转操作,使该子树重新恢复平衡
    node = turn(node);
    // 返回子树的根节点
    return node;
}

3.红黑树:近似平衡

(1).红黑树的定义

红黑树是一种自平衡二叉搜索树,通过在节点中增加颜色和约束规则确保树始终保持近似平衡,从而保证基本操作的时间复杂度为O(log n)。(任一节点左右子树的高度相差不超过两倍)

#include <stdio.h>
#include <stdlib.h>

//红黑树结点颜色定义,采用枚举类型
typedef enum { RED, BLACK } Color;

//红黑树结点结构
typedef struct RBNode {
    int data;//数据
    Color color;//颜色
    struct RBNode *left, *right, *parent;//左右孩子和父节点指针
} RBNode;

// 创建新结点
RBNode* createNode(int data) {
    RBNode* newNode = (RBNode*)malloc(sizeof(RBNode));
    newNode->data = data;
    newNode->color = RED; //新插入的结点默认为红色
    newNode->left = newNode->right = newNode->parent = NULL;
    return newNode;
}

在这里插入图片描述

(2).红黑树的性质

①.左根右

前提:二叉搜索树(左<根<右)

②.根叶黑

根和叶子节点(NULL)都是黑色

③.不红红

不存在连续的两个红色节点

④.黑路同

任一节点到叶所有路径路径黑节点数量相同

(3).插入

插入节点默认为红色节点。
如果插入后性质被破坏,则根据下面三种情况做调整:

①破坏了根叶黑

插入节点是根节点 --> 直接变黑
在这里插入图片描述

②破坏了不红红

  • 插入结点的叔叔是红色 --> 叔父爷变色,爷爷变插入结点,如果为根节点,则变黑。
    在这里插入图片描述

  • 插入结点的叔叔是黑色 --> (LL<RR<LR<RL)旋转,然后变色

    1.LL型(右旋)
    在这里插入图片描述

    2.RR型(左旋)
    在这里插入图片描述

    3.LR型:左旋左孩子,然后右旋
    在这里插入图片描述

    4.RL型:右旋右孩子,然后左旋
    在这里插入图片描述
    代码的具体实现如下:

//左旋
void left(RBNode **root, RBNode *x) {
    if (x == NULL || x->right == NULL) return;
    RBNode *y = x->right;
    x->right = y->left;
    if (y->left != NULL) {
        y->left->parent = x;
    }
    y->parent = x->parent;
    if (x->parent == NULL) {
        *root = y;
    } else if (x == x->parent->left) {
        x->parent->left = y;
    } else {
        x->parent->right = y;
    }
    y->left = x;
    x->parent = y;
}

//右旋
void right(RBNode **root, RBNode *y) {
	if (y == NULL || y->left == NULL) return;
    RBNode *x = y->left;
    y->left = x->right;
    if (x->right != NULL) {
        x->right->parent = y;
    }
    x->parent = y->parent;
    if (y->parent == NULL) {
        *root = x;
    } else if (y == y->parent->left) {
        y->parent->left = x;
    } else {
        y->parent->right = x;
    }
    x->right = y;
    y->parent = x;
}

//插入修改树,使其满足红黑树的特性
void Inserthelp(RBNode **root, RBNode *z) {
    while (z != *root && z->parent->color == RED) {
        if (z->parent == z->parent->parent->left) {
            RBNode *y = z->parent->parent->right; // 叔叔结点
            //叔叔是红色
            if (y != NULL && y->color == RED) {
                z->parent->color = BLACK;
                y->color = BLACK;
                z->parent->parent->color = RED;
                z = z->parent->parent;
            } else {
                //叔叔是黑色,且z是右孩子
                if (z == z->parent->right) {
                    z = z->parent;
                    left(root, z);
                }
                //叔叔是黑色,且z是左孩子
                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                right(root, z->parent->parent);
            }
        } else {
            // 对称的情况
            RBNode *y = z->parent->parent->left; // 叔叔结点
            
            // 情况1:叔叔是红色
            if (y != NULL && y->color == RED) {
                z->parent->color = BLACK;
                y->color = BLACK;
                z->parent->parent->color = RED;
                z = z->parent->parent;
            } else {
                // 情况2:叔叔是黑色,且z是左孩子
                if (z == z->parent->left) {
                    z = z->parent;
                    right(root, z);
                }
                
                // 情况3:叔叔是黑色,且z是右孩子
                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                left(root, z->parent->parent);
            }
        }
    }
    (*root)->color = BLACK; // 根结点始终为黑色
}

// 插入结点
void insert(RBNode **root, int data) {
    RBNode *z = createNode(data);
    RBNode *y = NULL;
    RBNode *x = *root;
    while (x != NULL) {
        y = x;
        if (z->data < x->data) {
            x = x->left;
        } else {
            x = x->right;
        }
    }
    z->parent = y;
    if (y == NULL) {
        *root = z; // 树为空
    } else if (z->data < y->data) {
        y->left = z;
    } else {
        y->right = z;
    }
    Inserthelp(root, z);
}

三、小结

红黑树和 AVL 树都是自平衡二叉搜索树,适用场景因需求而异。红黑树平衡条件较宽松,插入和删除时旋转操作少,插入效率高,但树高可能较高,影响查询性能。AVL 树平衡条件严格,左右子树高度差不超 1,树高较低,查询性能佳,不过插入和删除时需更多旋转,效率较低。
若应用场景以插入和删除操作为主,对查询性能要求不是特别苛刻,那么红黑树是更合适的选择;若应用场景主要是进行频繁的查询操作,插入和删除操作相对较少,AVL 树则能更好地满足需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值