手撕红黑树(上、下)--插入调整

本文详细介绍了红黑树的平衡条件,包括最长路径与最短路径的关系,NIL节点的作用,以及插入节点时如何保持平衡。插入操作遵循特定的调整策略,如红色上浮和红色下沉,确保红黑树的性质得以维持。代码示例展示了插入调整的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

红黑树的平衡条件
1.每个节点非黑即红。
2.根结点是黑色。
3.叶节点(NIL)是黑色。(这个叶节点通常是不被画出来的NIL节点)
4.如果一个节点是红色,则它的两个子节点都是黑色。
5.从根结点出发到所有叶节点路径上,黑色节点数量相同。
  • 问题1: 红黑树中,最长路径和最短路径长度的关系?
    答:根据平衡条件第4、5两点最短路径,都是黑色为最短路径,红黑相间为最长路径。所以可得到最长是最短的两倍。

  • 问题2:怎么理解条件3中的NIL结点
    答:就像文章中的标点符号,虽然他不属于文章内容部分,平时也不会注意到它,可是要真没有,就会很麻烦。(作用很大一部分就相当于链表中的* 虚拟头结点 * )

平衡调整终极法门:

插入站在祖父结点
删除调整站在父节点
插入和删除的情况处理一共有五种。

  • 问题3:新插入的结点是什么颜色的?红色?黑色?
    答:红色。因为插入黑色一定引发失衡,插入红色不一定引发失衡,一个必死,一个有概率活,正常人都会选择后者。

调整原则:调整之前路径上黑色节点数量等于调整之后路径上黑色节点数量。
插入调整情况:

情况一:x节点可在4个位置;需要将1,20变黑色,15变红色
插入调整情况1
情况二:有四种情况LL、LR、RL、RR
LL情况
红黑树插入调整
红黑树插入调整LL情况解决方法:红色上浮和红色下沉。

红黑树插入代码实现:

/*
 * @Author: mikey.zhaopeng 
 * @Date: 2021-08-22 10:57:53 
 * @Last Modified by: mikey.zhaopeng
 * @Last Modified time: 2021-08-22 18:17:27
 */
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<algorithm>
#include<string>
#include<map>
#include<set>
#include<vector>
using namespace std;
//红黑树也是树
#define NIL &(node::__NIL)
struct node{
    node(int key=0,int color=0,node *lchild=NIL,node *rchild=NIL)
    :key(key),color(color),lchild(lchild),rchild(rchild){}
    int key;
    int color;//0-red,1-black,2-double-black
    node *lchild,*rchild;
    static node __NIL;
};

node node::__NIL(0,1);

node *getNewNode(int key){
    return new node(key);
}
bool has_red_child(node *root){
    return root->lchild->color == 0||root->rchild->color==0;
}
//进行左旋
node *left_rotate(node *root){
    node *temp=root->rchild;
    root->rchild = temp->lchild;
    temp->lchild = root;
    return temp;
}
//进行右旋
node *right_rotate(node *root){
    node *temp=root->lchild;
    root->lchild = temp->rchild;
    temp->rchild = root;
    return temp;
}
//进行插入调整
node *insert_maintain(node *root){
    bool flag = false;//表示是否发生了冲突
    if(root->lchild->color==0 && has_red_child(root->lchild))flag = 1;
    if(root->rchild->color==0 && has_red_child(root->rchild))flag = 2; 
    if(flag == 0) return root;//说明左右子树都没有发生冲突直接返回根结点即可
    if(root->lchild->color == 0 && root->rchild->color==0){
        root->color = 0;
        root->lchild->color = root->rchild->color = 1;
        return root;
    }
    if(flag == 1){//说明冲突在左子树
        if(root->lchild->rchild->color==0){
            //如果左子树的右子树的颜色为红色则进行小左旋
            root->lchild = left_rotate(root->lchild);
        }
        //然后进行大右旋
        root = right_rotate(root);
    }else{
        //说明冲突在右子树
        if(root->rchild->lchild->color == 0){
            root->rchild = right_rotate(root->rchild);
        }
        root = left_rotate(root);
    }
    //最后进行红色上浮或红色下沉
    root->color = 0;
    root->lchild->color = root->rchild->color = 1;
    return root;
}


node *__insert(node *root,int key){
    if(root == NIL) return getNewNode(key);
    if(key == root->key) return root;
    if(root->key> key){
        root->lchild = __insert(root->lchild,key)
    }else{
        root->rchild =__insert(root->rchild,key);
    }
    return insert_maintain(root);//返回插入调整后的树
//红黑树的插入操作
node *insert(node *root,int key){
    root = __insert(root,key);//红黑树要求根结点是黑色
    root->color  = 1;
    return root;
}

void clear(node *root){
    if(root == NIL) return;
    clear(root->lchild);
    clear(root->rchild);
    delete root;
    return ;
}
//打印单个结点的信息
void print(node *root){
    print("(%d | %d,%d ,%d )\n"
        root->color,root->key,
        root->lchild->key,root->rchild->color->key
    );
    return ;
}
void output(node *root){
    if(root == NIL) return ;
    print(root);
    output(root->lchild);
    output(root->rchild);
    return ;
}   
int main(){
    int val;
    node root =NIL;
    while(cin>>val){
        root = insert(root,val);
        cout<<endl<<"===rbtee print ======"<<endl;
        output(root,)
        cout<<"=== rbtree print done====="<<endl;
    }
    return 0;
}

leetcode-1339. 分裂二叉树的最大乘积

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int avg,ans=0;//avg就是为了找距离平均值最近的那个值是多少
    int getTotal(TreeNode *root){
        if(root == nullptr) return 0;
        int val = root->val+getTotal(root->left)+getTotal(root->right);
        if(abs(val-avg)<abs(ans-avg)) ans = val;
        return val;
    }
    int maxProduct(TreeNode* root) {
//思路:如果想两个子树和的乘积最大需要两个子树和尽可能的相接近
        int total = getTotal(root);//获取和值
        avg = total/2;//均值尽可能的离total/2比较近
        ans = total;
        getTotal(root);
        return (long long)(ans)*(total-ans)%(long)(long)(1e9+7);

    }
};

leetcode-981. 基于时间的键值存储

class TimeMap {
public:
    /** Initialize your data structure here. */
    TimeMap() {}
     //map底层实现是红黑树 unordered_map底层实现是hash表
    unordered_map<string,map<int,string>>h;//h这里是string到有序表的一个映射
    void set(string key, string value, int timestamp){
        h[key][timestamp] = value;
        return ;
    }
    
    string get(string key, int timestamp) {
        if(h.find(key) == h.end()) return "";//如果不存在key这对键值直接返回""
        //此时说明外层的hash表中存在以时间戳为键值的有序表给它拿出来
        //拿出当前时间戳之前小于这个时间戳的最大时间戳对应的值string
        //先看有序表中是否存在这个时间戳
        if(h[key].find(timestamp) !=h[key].end()){
            //如果存在时间戳
            return h[key][timestamp];
        }
        //此时不存在对应的时间戳,我们通过插入这个时间戳然后找到这个时间戳的前一项
        h[key].insert(pair<int,string>(timestamp,""));
        auto iter = h[key].find(timestamp);//先定位到新插入时间戳所定位的位置
        string ret = (--iter)->second;
        //将新插入这项的时间戳给删除
        h[key].erase(h[key].find(timestamp));
        return ret;
    }
};

// class TimeMap {
//     unordered_map<string, vector<pair<int, string>>> m;

// public:
//     TimeMap() {}

//     void set(string key, string value, int timestamp) {
//         m[key].emplace_back(timestamp, value);
//     }

//     string get(string key, int timestamp) {
//         auto &pairs = m[key];
//         // 使用一个大于所有 value 的字符串,以确保在 pairs 中含有 timestamp 的情况下也返回大于 timestamp 的位置
//         pair<int, string> p = {timestamp, string({127})};
//         auto i = upper_bound(pairs.begin(), pairs.end(), p);
//         if (i != pairs.begin()) {
//             return (i - 1)->second;
//         }
//         return "";
//     }
// };

/**
 * Your TimeMap object will be instantiated and called as such:
 * TimeMap* obj = new TimeMap();
 * obj->set(key,value,timestamp);
 * string param_2 = obj->get(key,timestamp);
 */

leetcode-971. 翻转二叉树以匹配先序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int ind; //当前匹配到序列中的位置
    bool preorder(TreeNode *root,vector<int>&voyage,vector<int>&ret){
        //这里返回的是遍历的树是否满足相应的形成序列
        if(root == nullptr) return true;
        //如果当前节点的值和预期翻转后的结点值不相等则怎么也不可能翻转得到预期结果
        if(root->val != voyage[ind]){
            ret.clear();
            ret.push_back(-1);//当清空的ret数组时候,翻转节点无法令先序遍历匹配预期行程在结尾添加一个-1。
            return false;
        }
        //判断当前节点的左右子树是否需要交换
        ind+=1;
        //如果当前节点就是整颗树的最后一个节点直接返回true即可
        if(ind+1 == voyage.size())return true;
        if(root->left && root->left->val != voyage[ind]){
            swap(root->left,root->right);
            ret.push_back(root->val);
        }
        if(!preorder(root->left,voyage,ret)) return false;
        if(!preorder(root->right,voyage,ret)) return false;
        return true;

    }
    vector<int> flipMatchVoyage(TreeNode* root, vector<int>& voyage) {
        vector<int>ret;//需要翻转结点的列表
        ind = 0;
        preorder(root,voyage,ret);
        return ret;
    }   
};

leetcode-117. 填充每个节点的下一个右侧节点指针 II

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;
    Node* next;

    Node() : val(0), left(NULL), right(NULL), next(NULL) {}

    Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}

    Node(int _val, Node* _left, Node* _right, Node* _next)
        : val(_val), left(_left), right(_right), next(_next) {}
};
*/

class Solution {
public:
    Node *layer_connect(Node *head){//这里head就是本层的链表首地址
        Node *p = head,*pre=nullptr,*new_head=nullptr;//pre代表下一层连接的前一个节点,new_head代表下一层链表的首节点
        while(p){
            if(p->left){
                if(pre) pre->next = p->left;
                pre = p->left;
            }
            if(new_head == nullptr)new_head = pre;
            if(p->right){
                if(pre) pre->next=p->right;
                pre = p->right;
            }
            if(new_head == nullptr) new_head = pre;
            p = p->next;
        }
        return new_head;
    }
    Node* connect(Node* root) {
        Node *p = root;
        while(p = layer_connect(p));//只要我本层的下一层还有结点我就会调用lay_connect()进行连接
        return root;
    }
};

leetcode-剑指 Offer II 053. 二叉搜索树中的中序后继

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode *pre,*ans;
    bool inorder(TreeNode *root,TreeNode *p){//这里返回值表示我们是否能结束
        if(root == nullptr)return false;
        if(inorder(root->left,p)) return true;
        if(pre == p){
            ans = root;
            return true;
        }
        pre = root;
        if(inorder(root->right,p))return true;
        return false;
    }
    TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
        ans = pre = nullptr;
        inorder(root,p);
        return ans;
    }
};

leetcode-449. 序列化和反序列化二叉搜索树
重点提醒!! 尤其是反序列化的状态机思维

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:
//这里使用广义表进行转换 将二叉树转化成字符串
    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root == nullptr) return "";

        string ret="";
        stringstream ss;
        //root->val 放到=> ss 放到=> ret
        ss<<root->val;
        ss>>ret;

        if(root->left == nullptr && root->right == nullptr)return ret;
        //此时说明存在子树则字符后面存在括号
        ret+="(";
        ret+=serialize(root->left);//ret先对左子树进行编码然后对右子树进行编码
        if(root->right){//如果右子树不为空则左子树后面放右子树的信息
            ret+=",";
            ret+=serialize(root->right);
        }
        ret+=")";
        return ret;

    }   

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        //将字符串转化成二叉排序树
        //需用到栈
        int scode=0,ind=0,k=0;//scode表示当前处理流程处在第几个分支,ind表示当前字符处在第几个位置 k表示当前处理的是左(1)、右子树(2)
        stack<TreeNode *>s;//存储相关的结点地址
        TreeNode *p,*root;//p就表示当前处理出来的这个结点,root指向整个根结点
        while(ind < data.size()){
            //这段编码结构就是自动机的编码思维
            switch(scode){
                case 0:{//0用来判断当前字符到底是干什么的,比如如果是左子树说明该节点存在子树且刚才解析出来的结点需要压到栈里面,如果是逗号说明接下来解析的是右子树,如果是有括号说明此时栈顶元素已经处理完了应该从栈顶弹出
                    if(data[ind]<='9'&&data[ind]>='0') scode = 1;//说明当前是个数字就放到case1里面处理
                    else if(data[ind]=='(') scode = 2;
                    else if(data[ind]==',')scode = 3;
                    else if(data[ind]==')')scode = 4;
                }break;
                case 1: {
                    int num=0;
                    while(ind<data.size() && data[ind]<='9'&& data[ind]>='0'){
                        num = num*10+(data[ind]-'0');
                        ind+=1;
                    }
                    //将这段数字变成结点地址
                    p = new TreeNode(num);
                    if(root == nullptr) root = p;
                    if(k == 1)s.top()->left = p;
                    else if(k == 2)s.top()->right = p;
                    scode = 0;
                }break;
                case 2:{
                    //因为是左括号所以说明之前解析出来的结点是有左括号的因此需要将之前解析出来的字符压入到栈里面
                    s.push(p);
                    k = 1;//马上要处理左子树所以将k置为1
                    ind+=1;
                    scode = 0;//每次当前任务完成之后都将scode置为0,来回判断是否是数字
                }break;
                case 3: {
                    k = 2;
                    ind +=1;
                    scode = 0;
                }break;
                case 4: {
                    //此时说明栈顶元素的左右子树信息我们已经处理完了需要弹栈
                    s.pop();
                    ind+=1;
                    scode = 0;
                }break;

            }
        }
        return root;

    }
};

leetcode-78. 子集
version_1:使用二进制枚举 且逐个位循环,低效

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
//子集枚举法 ,使用二进制进行自己枚举
        int n = nums.size();
        vector<vector<int>>ret;
        //I就是枚举的上限,i左移n位
        for(int i=0,I=(1<<n);i<I;i++){//这里假设nums为1,2,3所以枚举的为0 ~ 7
            //将选取的元素压入到arr数组中
            vector<int>arr;
            //枚举i这个数字的n位
            for(int j=0;j<n;j++){
                //如果第i个数字的第n位为1则将nums[j]放到arr数组中 
                if(i&(1<<j))arr.push_back(nums[j]);
            }
            ret.push_back(arr);
        }
        return ret;
    }
};

version_2:使用hashmap存储二的整数次幂并使用二进制枚举
我二进制表示中有多少1就操作多少次

/**
*x&(-x) => 取这个数字在二进制表示中的最后一位1
*x&=(x-1) =>去掉二进制表示的最后一位
*/
class Solution{
public:
    vector<vector<int>> subsets(vector<int>& nums){
        vector<vector<int>> ret;
        int n = nums.size();
        unordered_map<int,int> mark;//1 - 0;2 - 1;4 - 2;8 - 3 
        for(int i=0,j=1;i<10;i++,j<<=1) mark[j] = i;
        for(int i=0,I=(1<<n);i<I;i++){
            vector<int>arr;
            
            int val = i;
            while(val){
            //将二进制数的最后一位1给取出来
                arr.push_back(nums[mark[val&(-val)]]);
                //去掉二进制表示的最后一位1
                val&=(val-1);
            }
                ret.push_back(arr);
        }
        return ret;
    }
};

leetcode-47. 全排列 II

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<vector<int>>ret;
        do{
            //将全排列结果记录到ret数组中
            ret.push_back(nums);
        }while(next_permutation(nums.begin(),nums.end()));//next_permutation用来获取所有的全排列
          return ret;                                                                                                                                                       
    }
};

存在重复元素
因为看到下标之差我们不难想到滑动窗口解法,在这里题目给出的窗口下标范围是小于等于k所以我们要注意窗口的大小为k+1!!!

leetcode-41. 缺失的第一个正数

class Solution {
public:
//解题思路:我们将数值1存到下标0位置,将2存到1,将3存到2,以此类推只要找到第一个不符合存储标准的数即可
    int firstMissingPositive(vector<int>& nums) {
        for(int i=0;i<nums.size();i++){
            while(nums[i]!=i+1){
                //将nums[i]放到正确的位置上去
                if(nums[i]<=0 || nums[i]>=nums.size())//比如题目中一共就4个数字 其中给了个100 超出题目数组空间范围肯定不符合标准 不予考虑 直接跳出循环
                break;
                int ind = nums[i] - 1;
                if(nums[i] == nums[ind])break;
                swap(nums[i],nums[ind]);
            }
        }
        //现在已经将所有数字都放到了正确的位置上去了
        //遍历数组中的每一个数查看其下标是否对应,直到遇到下标不合符返回其对应的数字即可
        int ind = 0;
        while(ind<nums.size() && nums[ind]==ind+1)++ind;
        return ind+1;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值