[数据结构和算法003]二叉搜索树(Binary Sort Tree)和二叉平衡树(Balanced Binary Tree)

目录

前言

一、BTS部分

1.BST的定义:

2.创建BST(由一个关键字序列生成BST):

3.向二叉搜索树中插入元素:

4.向二叉搜索树中删除某个元素:

二、BBT部分

1.定义:

2.准备函数:

3.插入的过程:

4.代码层对四种情况的识别:

5.全部代码 :



一、BTS部分

1.BST的定义:

为空树或者具有下列性质的二叉树:①左子树不空:则左子树所有节点小于根结点;②右子树不空:则右子树所有的结点大于根结点;③左右子树也分别为二叉搜索树。显然它的定义是递归的,而且根据这个定义我们可以得到一个重要的性质:当中序遍历一棵二叉搜索树的时候,会得到一个以结点值递增的有序序列

首先给出二叉树的结构体定义

struct TreeNode {
    int value;
    TreeNode* left;
    TreeNode* right;
    //构造函数:接受一个int类型的参数val并且初始化数据域value
    TreeNode(int val) : value(val), left(nullptr), right(nullptr) {}
};

2.创建BST(由一个关键字序列生成BST):

一般情况下让第一个元素作为根节点即可,随后对关键字序列进行访问,让他和根结点比较,决定进入那一侧(递归)。注意的是新的元素被插入后都变成了叶子结点。整体逻辑是非常简单的。

3.向二叉搜索树中插入元素:

由于会出现是否允许有重复元素的先决条件我们分情况讨论:

当不支持有重复元素的时候:我们选择忽略掉该元素即可。可以采用先查后找的方法,或者更简单直接利用if...else...语句将待插入关键字vlaue等于当前结点的key的情况处理方法为返回根节点即可。

当支持有重复的元素时候:可以人为的选择插入左侧还是右侧。

下面我们给出实现的代码:(这里选择了不支持重复元素的方法)

TreeNode* insert(TreeNode* root, int value) {
    if (root == nullptr) {
        return new TreeNode(value);
    }
    if (value < root->value) {
        root->left = insert(root->left, value);
    } else if (value > root->value) {
        root->right = insert(root->right, value);
    }
    return root;
}

4.向二叉搜索树中删除某个元素:

删除一个元素的逻辑一句话概括为:要用其左子树最大的元素或者右子树最小的元素代替即可。

// 查找最小值节点,我们要用这个函数去找待删除结点的右子树的最小值
TreeNode* findMin(TreeNode* node) {
    while (node->left != nullptr) {
        node = node->left;
    }
    return node;
}

// 删除节点
TreeNode* deleteNode(TreeNode* root, int value) {
    if (root == nullptr) {
        return root;
    }
    if (value < root->value) {//第一步先找到待删除结点
        root->left = deleteNode(root->left, value);
    } else if (value > root->value) {
        root->right = deleteNode(root->right, value);
    } else {
        // 找到要删除的节点
        if (root->left == nullptr) {//该节点没有左子树那么直接用右子树的根节点代替之即可
            TreeNode* temp = root->right;
            delete root;
            return temp;
        } else if (root->right == nullptr) {//该节点没有右子树那么直接用左子树的根节点代替之即可
            TreeNode* temp = root->left;
            delete root;
            return temp;
        }
        // 有两个子节点的情况找右子树的最小值即可
        TreeNode* temp = findMin(root->right);
        root->value = temp->value;
        root->right = deleteNode(root->right, temp->value);
    }
    return root;
}

二、BBT部分

1.定义:

我们在前面提到过由一个序列生成二叉搜索树的时候,我们一般选择第一个元素作为二叉树的根结点,当这个序列为有序的时候,它会变成一条链表,此时查找的时间复杂度由O(logn)退化为O(n),我们引入平衡二叉树的概念。①基于BST而实现的。②左右子树的深度之差的绝对值小于等于1,而且左右子树也是平衡二叉树(递归)。此外考虑到后面会频繁的访问父节点,我们需要采用三叉链的结构。下面给出结构体的定义:

struct Node {
    int key; // 存放int类型关键字
    Node* left;//指向左孩子
    Node* right;//指向右孩子
    Node* parent; // 指向父节点的指针
    int balanceFactor; // 平衡因子
};

2.准备函数:

在实现插入操作之前要有以下准备:

求二叉树的高度的方法:

// 获取两个整数的最大值
int max(int a, int b) {
    return (a > b) ? a : b;
}
int height(Node* N) {
    if (N == nullptr)
        return 0;
    return 1 + max(height(N->left), height(N->right));
}

创建新的结点:

// 创建一个新的节点
Node* newNode(int key) {
    Node* node = new Node();
    node->key = key;
    node->left = nullptr;
    node->right = nullptr;
    node->parent = nullptr; // 新节点默认没有父节点
    node->balanceFactor = 0; // 新节点默认平衡因子为0
    return(node);
}

3.插入的过程:

第一步:按照BST的插入逻辑将待插入元素插到指定的位置。第二步:更新平衡因子。第三步:检查失衡并且去调整。

对于第三步的若检查处失去平衡的四种情况以及处理方法:

①LL型:(插入较高的左子树的左侧)

首先我们给出最开始的形式

 随后我们进行插入操作首先插入元素,后修改bf,出现了不平衡的结点

 接下来第一步将curr的父结点与currL链接:

1

注意:curr的父结点对currL是唯一确定的,因为curr只有一个parent。所以在操作①直接currL.parent=curr.parent即可。而curr的父节点要连接currL的时候要判断curr是左还是右,以确定用哪一个指针来指向currL。

接下来是旋转的过程:

就做了两件事情:①让curr成为currL的右孩子;②让currLR成为curr的左孩子。

最后,补充curr和currLR的父结点:

 当然,最后再修改一下bf即可。

代码实现

// 右旋操作
Node* rightRotate(Node*curr ) {//LL情况下对结点curr进行右旋
    Node* currL = curr->left;//curr结点的左孩子
    Node* currLR = currL->right;//currL的右孩子

    // 更新currL的父节点
    currL->parent = curr->parent;
    if (curr->parent != nullptr) {
        if (curr->parent->left == curr)
            curr->parent->left = currL;
        else
            curr->parent->right = currL;
    }

    // 执行旋转
    currL->right = curr;
    curr->left = currLR;

    // 更新curr和currLR的父节点
    curr->parent = currL;
    if (currLR != nullptr)
        currLR->parent = curr;

    // 更新平衡因子
    curr->balanceFactor = height(curr->left) - height(curr->right);
    currL->balanceFactor = height(currL->left) - height(currL->right);

    // 返回新的根节点
    return currL;
}

②对于RR型(对于左旋而言逻是辑一致的直接给出代码):

// 左旋操作
Node* leftRotate(Node* curr) {//RR情况对结点curr进行左旋
    Node* currR = curr->right;
    Node* currRL = currR->left;

    // 更新currR的父节点
    currR->parent = curr->parent;
    if (curr->parent != nullptr) {
        if (curr->parent->left == curr)
            curr->parent->left = currR;
        else
            curr->parent->right = currR;
    }

    // 执行旋转
    currR->left = curr;
    curr->right = currRL;

    // 更新curr和currRL的父节点
    curr->parent = currR;
    if (currRL != nullptr)
        currRL->parent = curr;

    // 更新平衡因子
    curr->balanceFactor = height(curr->left) - height(curr->right);
    currR->balanceFactor = height(currR->left) - height(currR->right);

    // 返回新的根节点
    return currR;
}

③对于LR型:(在较高左子树的右侧插入)

首先来看插入元素前的状态:

接下来我们通过BST的插入元素以及修改bf值后会得到两种不同的情况,但是都属于LR类型。在旋转的过程中是一样的,区别仅仅在如何修改bf:

接下来我们以左侧的情况为例分析,

第一步:对currL进行左旋:

 第二步:对curr进行右旋:

如图此时bf回归正常

④对于RL型:逻辑上是类似的唯一的不同是先对currR右旋,再对curr左旋即可。

4.代码层对四种情况的识别:

首先判断平衡因子属于大于1还是小于-1的情况,再判断关键字与node的左/右孩子的大小:

// 插入节点并保持AVL树平衡
Node* insert(Node* node, int key) {//node为被插入的树,而key表示被插入元素
    // 1. 执行正常的BST插入:插入过程为递归地寻找
    if (node == nullptr) {//当前树为空时候
        Node* new_node = newNode(key);//利用newNode创建新的结点
        new_node->parent = nullptr; // 根节点没有父节点
        return new_node;
    }

    if (key < node->key) {//向左插入
        Node* leftChild = insert(node->left, key);
        node->left = leftChild;
        leftChild->parent = node; // 更新左子节点的父节点指针
    }
    else if (key > node->key) {//向右插入
        Node* rightChild = insert(node->right, key);
        node->right = rightChild;
        rightChild->parent = node; // 更新右子节点的父节点指针
    }
    else // 不允许重复键
        return node;

    // 2. 更新节点的平衡因子
    node->balanceFactor = height(node->left) - height(node->right);

    // 3. 获取节点的平衡因子,检查是否失衡
    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;
}

 

5.全部代码 :

#include <iostream>
using namespace std;

// 定义AVL树节点结构
struct Node {
    int key; // 存放int类型关键字
    Node* left;//指向左孩子
    Node* right;//指向右孩子
    Node* parent; // 指向父节点的指针
    int balanceFactor; // 平衡因子
};

// 获取节点的高度
int height(Node* N) {
    if (N == nullptr)
        return 0;
    return 1 + max(height(N->left), height(N->right));
}

// 获取两个整数的最大值
int max(int a, int b) {
    return (a > b) ? a : b;
}

// 创建一个新的节点
Node* newNode(int key) {
    Node* node = new Node();
    node->key = key;
    node->left = nullptr;
    node->right = nullptr;
    node->parent = nullptr; // 新节点默认没有父节点
    node->balanceFactor = 0; // 新节点默认平衡因子为0
    return(node);
}

// 右旋操作
Node* rightRotate(Node*curr ) {//LL情况下对结点curr进行右旋
    Node* currL = curr->left;//curr结点的左孩子
    Node* currLR = currL->right;//currL的右孩子

    // 更新currL的父节点
    currL->parent = curr->parent;
    if (curr->parent != nullptr) {
        if (curr->parent->left == curr)
            curr->parent->left = currL;
        else
            curr->parent->right = currL;
    }

    // 执行旋转
    currL->right = curr;
    curr->left = currLR;

    // 更新curr和currLR的父节点
    curr->parent = currL;
    if (currLR != nullptr)
        currLR->parent = curr;

    // 更新平衡因子
    curr->balanceFactor = height(curr->left) - height(curr->right);
    currL->balanceFactor = height(currL->left) - height(currL->right);

    // 返回新的根节点
    return currL;
}

// 左旋操作
Node* leftRotate(Node* curr) {//RR情况对结点curr进行左旋
    Node* currR = curr->right;
    Node* currRL = currR->left;

    // 更新currR的父节点
    currR->parent = curr->parent;
    if (curr->parent != nullptr) {
        if (curr->parent->left == curr)
            curr->parent->left = currR;
        else
            curr->parent->right = currR;
    }

    // 执行旋转
    currR->left = curr;
    curr->right = currRL;

    // 更新curr和currRL的父节点
    curr->parent = currR;
    if (currRL != nullptr)
        currRL->parent = curr;

    // 更新平衡因子
    curr->balanceFactor = height(curr->left) - height(curr->right);
    currR->balanceFactor = height(currR->left) - height(currR->right);

    // 返回新的根节点
    return currR;
}

// 获取平衡因子
int getBalance(Node* N) {
    if (N == nullptr)
        return 0;
    return N->balanceFactor;
}

// 插入节点并保持AVL树平衡
Node* insert(Node* node, int key) {//node为被插入的树,而key表示被插入元素
    // 1. 执行正常的BST插入:插入过程为递归地寻找
    if (node == nullptr) {//当前树为空时候
        Node* new_node = newNode(key);//利用newNode创建新的结点
        new_node->parent = nullptr; // 根节点没有父节点
        return new_node;
    }

    if (key < node->key) {//向左插入
        Node* leftChild = insert(node->left, key);
        node->left = leftChild;
        leftChild->parent = node; // 更新左子节点的父节点指针
    }
    else if (key > node->key) {//向右插入
        Node* rightChild = insert(node->right, key);
        node->right = rightChild;
        rightChild->parent = node; // 更新右子节点的父节点指针
    }
    else // 不允许重复键
        return node;

    // 2. 更新节点的平衡因子
    node->balanceFactor = height(node->left) - height(node->right);

    // 3. 获取节点的平衡因子,检查是否失衡
    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树
void inOrder(Node* root) {
    if (root != nullptr) {
        inOrder(root->left);
        cout << root->key << "(" << root->balanceFactor << ") "; // 打印平衡因子
        inOrder(root->right);
    }
}

int main() {
    Node* root = nullptr;

    // 插入节点
    root = insert(root, 14);
    root = insert(root, 2);
    root = insert(root, 13);
    root = insert(root, 4);
    root = insert(root, 5);
    root = insert(root, 12);

    // 打印中序遍历结果
    cout << "Inorder traversal of the constructed AVL tree is \n";
    inOrder(root);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值