📜  包含父节点指针的 AVL 树中的插入、搜索和删除(1)

📅  最后修改于: 2023-12-03 14:50:25.881000             🧑  作者: Mango

包含父节点指针的 AVL 树中的插入、搜索和删除

AVL 树是一种自平衡二叉查找树,它的每个节点都有一个平衡因子,即左子树高度减右子树高度的差值是否在-1到1的范围内。由于 AVL 树保持平衡,所以查找、插入、删除等操作的时间复杂度为O(log n)。

在实际应用中,我们有时需要访问 AVL 树中某个节点的父节点,因此需要将 AVL 树节点中添加一个指向父节点的指针。下面介绍包含父节点指针的 AVL 树中的插入、搜索和删除操作。

数据结构

AVL 树节点的数据结构如下:

typedef struct AVLNode {
    int key;             //键值
    int value;           //值
    int height;          //节点高度
    struct AVLNode* left;    //左子树指针
    struct AVLNode* right;   //右子树指针
    struct AVLNode* parent;  //父节点指针
} AVLNode;

其中,height表示节点高度,left和right分别是节点的左右子树指针,parent是节点的父节点指针。

插入操作

在 AVL 树中插入节点和普通二叉查找树差不多,我们首先根据二叉查找树的规则将新节点插入到适当的位置,然后递归地更新每个节点的高度,并调整树的平衡。具体步骤如下:

  1. 插入节点
  2. 递归更新节点高度,并计算平衡因子(左子树高度减右子树高度)
  3. 如果一个节点的平衡因子绝对值大于1,则需要通过旋转来保持 AVL 树平衡

旋转操作有两种:左旋和右旋。左旋指的是将一个节点的右子树“拉”到其左侧,而右旋指的是将一个节点的左子树“拉”到其右侧。通过左旋和右旋操作,我们可以将一个平衡因子绝对值大于1的节点恢复到平衡状态。

代码实现:

AVLNode* insert(AVLNode* node, int key, int value) {
    //插入新节点
    if (node == NULL) {
        AVLNode* newNode = createNode(key, value);
        return newNode;
    }

    if (key < node->key) {
        node->left = insert(node->left, key, value);
        node->left->parent = node;
    } else if (key > node->key) {
        node->right = insert(node->right, key, value);
        node->right->parent = node;
    } else {//key已经存在
        node->value = value;
        return node;
    }

    //更新高度和平衡因子
    node->height = max(height(node->left), height(node->right)) + 1;
    int balance = getBalance(node);

    //如果节点不平衡,进行旋转
    if (balance > 1 && key < node->left->key) {//左左
        return rightRotate(node);
    }
    if (balance < -1 && key > node->right->key) {//右右
        return leftRotate(node);
    }
    if (balance > 1 && key > node->left->key) {//左右
        node->left = leftRotate(node->left);
        return rightRotate(node);
    }
    if (balance < -1 && key < node->right->key) {//右左
        node->right = rightRotate(node->right);
        return leftRotate(node);
    }

    return node;
}
搜索操作

在 AVL 树中搜索节点和普通二叉查找树一样,我们比较要查找的键值和当前节点的键值,根据二叉查找树的规则递归搜索左子树或右子树。具体步骤如下:

  1. 如果节点为 NULL,说明找不到该键值的节点,返回 NULL
  2. 如果节点的键值等于要查找的键值,返回该节点
  3. 如果节点的键值大于要查找的键值,递归搜索左子树
  4. 如果节点的键值小于要查找的键值,递归搜索右子树

代码实现:

AVLNode* search(AVLNode* node, int key) {
    if (node == NULL || node->key == key) {
        return node;
    }

    if (key < node->key) {
        return search(node->left, key);
    } else {
        return search(node->right, key);
    }
}
删除操作

在 AVL 树中删除节点比较麻烦,因为在删除节点后需要递归地更新每个节点的高度,并调整树的平衡。具体步骤如下:

  1. 找到要删除的节点
  2. 如果要删除的节点是叶子节点或仅有一个子节点,则直接删除,平衡树即可
  3. 如果要删除的节点有两个子节点,则找到该节点的右子树中的最小值节点,将该节点复制给要删除的节点,并删除右子树中的最小值节点
  4. 递归更新节点高度,并计算平衡因子
  5. 如果一个节点的平衡因子绝对值大于1,则需要通过旋转来保持 AVL 树平衡

代码实现:

AVLNode* delete(AVLNode* node, int key) {
    if (node == NULL) {//找不到要删除的节点
        return node;
    }
    if (key < node->key) {//要删除的节点在左子树
        node->left = delete(node->left, key);
    } else if (key > node->key) {//要删除的节点在右子树
        node->right = delete(node->right, key);
    } else {//找到要删除的节点
        if (node->left == NULL || node->right == NULL) {//删除节点为叶子节点或仅有一个子节点
            AVLNode* temp = node->left ? node->left : node->right;

            //叶子节点
            if (temp == NULL) {
                temp = node;
                node = NULL;
            } else {//有一个子节点
                *node = *temp;
            }

            free(temp);
        } else {//删除节点有两个子节点
            AVLNode* temp = minValueNode(node->right);//找到右子树中的最小值节点,复制给要删除的节点
            node->key = temp->key;
            node->value = temp->value;

            node->right = delete(node->right, temp->key);//删除右子树中的最小值节点
        }
    }

    if (node == NULL) {
        return node;
    }

    //更新高度和平衡因子
    node->height = max(height(node->left), height(node->right)) + 1;
    int balance = getBalance(node);

    //如果节点不平衡,进行旋转
    if (balance > 1 && getBalance(node->left) >= 0) {//左左
        return rightRotate(node);
    }
    if (balance > 1 && getBalance(node->left) < 0) {//左右
        node->left = leftRotate(node->left);
        return rightRotate(node);
    }
    if (balance < -1 && getBalance(node->right) <= 0) {//右右
        return leftRotate(node);
    }
    if (balance < -1 && getBalance(node->right) > 0) {//右左
        node->right = rightRotate(node->right);
        return leftRotate(node);
    }

    return node;
}
总结

本篇文章介绍了包含父节点指针的 AVL 树中的插入、搜索和删除操作。AVL 树是一种自平衡二叉查找树,通过添加父节点指针,我们可以在访问节点的同时访问其父节点。AVL 树的插入、搜索和删除操作的时间复杂度均为 O(log n)。