红黑树(有图解)

news2024/11/27 3:15:00

目录

介绍

概念

性质

模拟实现

结点定义 

插入 

保证平衡的原因

一般情况

特殊情况(uncle为黑) 

uncle不存在

旋转方式

右旋

迭代器

++ 

-- 

代码


介绍

概念

红黑树是一种自平衡的二叉搜索树

  • 它是在每个节点上引入额外的颜色信息,通过对任何一条从根到叶子的路径上各个结点着色方式的限制,确保没有一条路径会比其他路径长出俩倍,从而达到高度差的平衡
  • 保证了在最坏情况下的时间复杂度为O(log n)
  • 同时,它也是c++标准库中两种关联容器(set和map)的底层实现

性质

  • 结点颜色只有红色,黑色两种
  • 根结点必须是黑色
  • 每个叶子结点(也就是平常被我们忽略的空结点(NIL结点))都是黑色的
  • 不能有两个连续的红色节点(从上到下的路径来看)
  • 从任意一个节点到其每个叶子节点的路径必须包含相同数目的黑色节点,这被称为黑色高度(Black Height)
满足以上性质后,可以保证红黑树中,其最长路径中节点个数不会超过最短路径节点个数的两倍

模拟实现

结点定义 

  • 和avl树一样,需要频繁用到父结点,所以需要一个parent成员
  • 除此之外,他还需要存储每个结点的颜色信息
  • 库中定义:

  • stl库中,红黑树实际上还有一个哨兵位的头结点(当然没有也可以)
  • 可以看到,树中定义了一个成员变量header
  • 且可以从命名来看,header的left指向树的最小结点,right指向树的最大结点,parent指向树的根结点

其次,结点插入一般设置为红色

因为有不能有连续红色的性质,所以直接插入红色结点,有助于确保平衡

插入 

  • 首先,和avl树一样,需要先找到插入结点的位置(如果重复就不插入了)
  • 主要需要修改的是parent,uncle,grandfather的颜色,而cur(或是新插入结点)的位置确保是红色
  • !!!!注意!!!!一定要记住我们的红黑树是有一个头结点的
  • 记得一定要在旋转时  改根结点和头结点之间的连接
  • 以及判断循环的时候,有些结束条件就是遍历到头结点!!!

保证平衡的原因

  • 黑色结点数目相等:保证了每条路径的高度在可控范围内
  • 红色不能连续:保证红色节点的父节点和子节点之间的黑色节点数目是相等的,从而保持了黑色平衡性
  • 由于黑色结点数相等,虽然没有限制红色结点数,但不能有连续红色结点,这两个就保证红结点最多有黑结点个,最少没有,所以不会超过两倍

一般情况

需要让parent和uncle都变黑,而grandfather变红(注意:因为parent是红色,所以grandfather是一定存在的,注意性质嗷)

如果grandfather不是根结点,就需要继续向上调整,让cur指向grandfather的位置

如果是根结点,就结束

!!!!注意,循坏外一定要让根结点的颜色为黑,因为可能会调整根为红色

特殊情况(uncle为黑) 

一轮变色后,此时uncle为黑:

如果让parent,uncle变黑,grandfather变红,会让parent那条路径多一个黑色结点,而uncle那条路数目没有变,所以单纯的变色满足不了这种情况了

同理,uncle不存在也是一样 

uncle不存在

会发现,此时无法变色,否则会在parent分支中,多出一个黑色结点

而且,这个结构,是不是让我们想起了avl树中的右旋

所以,在这两种情况下,需要旋转+变色,才能保证红黑树的平衡

旋转方式

像上面这种情况,很明显是要对grandfather右旋,最后parent成为这支树的根结点

由此,旋转方式其实就是由cur,parent,grandfather的相对位置,来确定

右旋

像这个例子,就是非常典型的右旋

旋转后,让新的根结点为黑色,剩下两个为红色

这个变色方式确实很神奇

再来分析一下,上面的uncle为黑时的旋转方式

因为此时,这三个结点的相对位置依然是右旋

 

右旋后:

然后进行变色,依然是根为黑色,其他两个为红色:

很神奇吧,这样就能让红黑树平衡了 

其它旋转方式都是一样的,旋转完后让根为黑色,其他两个为红色即可 

迭代器

底层实际上就是结点指针,在此之上进行了封装

++ 

还记得我们如果要遍历平衡树得用中序遍历吗,指针的挪动也是借助中序的思想

中序:左根右

  • 那么如果当前结点有右树,就找到右树中的最小结点
  • 如果当前结点没有右子树,那么就判断它和父亲的位置
  • 如果父亲的左子树就是当前结点,那么++后就是父亲
  • 如果是当前结点在右树,说明parent这一支子树已经遍历完毕,该从parent的位置继续判断
  • 直到parent遍历到了phead的位置(那个头结点)

-- 

和上面的++类似,只不过是倒着走

也就是右根左的方向

  • 那么如果当前结点有左树,就找到左树中的最大结点
  • 如果当前结点没有左子树,那么就判断它和父亲的位置
  • 如果父亲的右子树就是当前结点,那么--后就是父亲
  • 如果是当前结点在左树,说明parent这一支子树已经遍历完毕,该从parent的位置继续判断
  • 直到parent遍历到了phead的位置(那个头结点)

代码

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <cassert>
#include <cstdlib>
#include <utility>

// 有迭代器的红黑树
namespace my_RB_Tree
{
    enum colour
    {
        black,
        red
    };

    template <class T>
    struct RBTreeNode // 结点
    {
        RBTreeNode(const T &data)
            : _left(nullptr),
              _right(nullptr),
              _parent(nullptr),
              _col(red),
              _data(data)
        {
        }
        RBTreeNode *_left;
        RBTreeNode *_right;
        RBTreeNode *_parent;
        colour _col;
        T _data;
    };

    template <class T, class Ptr, class Ref> // T是元素类型,ptr是指针类型,ref是引用类型(后两种会有const类型)
    struct RBTreeIterator                    // 迭代器
    {
        typedef RBTreeNode<T> Node;
        typedef RBTreeIterator<T, Ptr, Ref> Self;

        RBTreeIterator(Node *pNode)
            : _pNode(pNode)
        {
        }

        // 让迭代器具有类似指针的行为
        Ref &operator*()
        {
            return _pNode->_data;
        }
        Ptr *operator->()
        {
            return &(_pNode->_data);
        }

        // 让迭代器可以移动:前置/后置++
        Self &operator++()
        {
            Increament();
            return *this;
        }
        Self operator++(int)
        {
            Self tmp(*this);
            Increament();
            return tmp;
        }
        // 让迭代器可以移动:前置/后置--
        Self &operator--()
        {
            DeIncreament();
            return *this;
        }
        Self operator--(int)
        {
            Self tmp(*this);
            DeIncreament();
            return tmp;
        }

        // 让迭代器可以比较
        bool operator!=(const Self &s) const
        {
            return _pNode != s._pNode;
        }
        bool operator==(const Self &s) const
        {
            return _pNode == s._pNode;
        }

    private:
        void Increament();
        void DeIncreament();

        Node *_pNode;
    };

    // 为了后序封装map和set,本代码的红黑树会有一个作为哨兵位的头结点
    template <class K, class T, class KeyOfT> // K是关键字的类型,T是元素类型(区分这两个的原因:会用该红黑树封装成set和map,而map是key_value的)
                                              // keyofT是返回关键字类型的值(否则map无法返回)
    class RBTree                              // 红黑树
    {
    public:
        typedef RBTreeNode<T> Node;
        typedef RBTreeIterator<T, T *, T> iterator;
        typedef RBTreeIterator<T, const T *, const T> const_iterator;

    public:
        RBTree()
        {
            _pHead = new Node(T());
            _pHead->_left = _pHead;
            _pHead->_parent = nullptr;
            _pHead->_right = _pHead;
        }

        // 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
        std::pair<iterator, bool> Insert(const T &data);
        

        // 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
        Node *Find(const K &data);

        // 获取红黑树最左侧节点
        Node *LeftMost();

        // 获取红黑树最右侧节点
        Node *RightMost();

        iterator begin()
        {
            return iterator(LeftMost());
        }
        iterator end()
        {
            return iterator(_pHead);
        }
        const_iterator begin() const
        {
            return const_iterator(LeftMost());
        }
        const_iterator end() const
        {
            return const_iterator(_pHead);
        }

        // 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
        bool IsValidRBTRee()
        {
            Node *root = _pHead->_parent;
            if (root->_col == red)
            {
                return false;
            }
            int count = 0;
            find_blacknode(count, _pHead->_parent);
            return _IsValidRBTRee(_pHead->_parent, count, 0);
        }

    private:
        bool _IsValidRBTRee(Node *pRoot, size_t blackCount, size_t pathBlack);
        // 左单旋
        void RotateL(Node *pParent);
        // 右单旋
        void RotateR(Node *pParent);
        // 为了操作树简单起见:获取根节点
        Node *&GetRoot()
        {
            return _pHead->_parent;
        }
        void find_blacknode(int &count, Node *root)
        {
            if (root == nullptr)
            {
                return;
            }
            if (root->_col == black)
            {
                ++count;
            }
            find_blacknode(count, root->_left);
            find_blacknode(count, root->_right);
        }

    private:
        Node *_pHead = nullptr;
    };

    template <class K, class T, class KeyOfT>
    void RBTree<K, T, KeyOfT>::RotateL(Node *pParent)
    {
        Node *cur = pParent->_right, *curleft = cur->_left;

        // 连接p和cur左树,因为该位置被p占据
        pParent->_right = curleft;
        if (curleft)
        {
            curleft->_parent = pParent;
        }

        // 连接父结点
        if (pParent->_parent != _pHead)
        {
            Node *ppnode = pParent->_parent;
            if (ppnode->_left == pParent)
            {
                ppnode->_left = cur;
            }
            else
            {
                ppnode->_right = cur;
            }
            cur->_parent = ppnode;
        }
        else
        {
            _pHead->_parent = cur;
            cur->_parent = _pHead;
        }
        // 连接p和cur
        pParent->_parent = cur;
        cur->_left = pParent;
    }
    template <class K, class T, class KeyOfT>
    void RBTree<K, T, KeyOfT>::RotateR(Node *pParent)
    {
        Node *cur = pParent->_left, *curright = cur->_right;

        // 连接p和cur右树,因为该位置被p占据
        pParent->_left = curright;
        if (curright)
        {
            curright->_parent = pParent;
        }

        // 连接父结点
        if (pParent->_parent != _pHead)
        {
            Node *ppnode = pParent->_parent;
            if (ppnode->_left == pParent)
            {
                ppnode->_left = cur;
            }
            else
            {
                ppnode->_right = cur;
            }
            cur->_parent = ppnode;
        }
        else
        {
            _pHead->_parent = cur;
            cur->_parent = _pHead;
        }
        // 连接p和cur
        pParent->_parent = cur;
        cur->_right = pParent;
    }

    template <class K, class T, class KeyOfT>
    typename RBTree<K, T, KeyOfT>::Node *RBTree<K, T, KeyOfT>::LeftMost()
    {
        Node *cur = _pHead->_parent;
        while (cur->_left)
        {
            cur = cur->_left;
        }
        return cur;
    }
    template <class K, class T, class KeyOfT>
    typename RBTree<K, T, KeyOfT>::Node *RBTree<K, T, KeyOfT>::RightMost()
    {
        Node *cur = _pHead->_parent;
        while (cur->_right)
        {
            cur = cur->_right;
        }
        return cur;
    }

    template <class K, class T, class KeyOfT>
    typename RBTree<K, T, KeyOfT>::Node *RBTree<K, T, KeyOfT>::Find(const K &data) // 注意这里,
    {
        Node *cur = _pHead->_parent;
        KeyOfT kot;
        while (cur)
        {
            if (data > kot(cur->_data))
            {
                cur = cur->_right;
            }
            else if (data < kot(cur->_data))
            {
                cur = cur->_left;
            }
            else
            {
                return cur;
            }
        }
        return nullptr;
    }

    template <class K, class T, class KeyOfT>
    std::pair<typename RBTree<K, T, KeyOfT>::iterator, bool> RBTree<K, T, KeyOfT>::Insert(const T &data) // 为了和map适配,要返回pair类型
                                                                                                         //(first是插入元素所在的迭代器,second是bool值,判断是否成功插入)
    {
            KeyOfT kot;
            Node *newnode = nullptr;
            if (_pHead->_parent == nullptr)
            {
                newnode = new Node(data);
                newnode->_col = black;
                _pHead->_parent = newnode;
                newnode->_parent = _pHead;
                return std::make_pair(iterator(newnode), true);
            }
            else
            {
                Node *cur = _pHead->_parent, *parent = cur;
                while (cur)
                {
                    if (kot(data) > kot(cur->_data))
                    {
                        parent = cur;
                        cur = cur->_right;
                    }
                    else if (kot(data) < kot(cur->_data))
                    {
                        parent = cur;
                        cur = cur->_left;
                    }
                    else
                    {
                        return std::make_pair((iterator)cur, false);
                    }
                }

                newnode = new Node(data);
                cur = newnode;
                cur->_parent = parent;
                if (kot(parent->_data) > kot(cur->_data))
                {
                    parent->_left = cur;
                }
                else
                {
                    parent->_right = cur;
                }

                Node *grandfather = nullptr;
                while (parent && parent->_col == red)
                {
                    grandfather = parent->_parent; // 因为父结点是红色,所以肯定有爷爷结点(注意红黑树规则:根结点必须是黑色)

                    if (grandfather->_left == parent) // 确定父亲位置
                    {
                        Node *uncle = grandfather->_right; // 也就能确定叔叔位置

                        if (uncle && uncle->_col == red)
                        {
                            parent->_col = uncle->_col = black;
                            grandfather->_col = red;
                        }
                        else // 如果uncle不存在/为黑,就需要旋转+变色了
                        {
                            // 需要先判断旋转类型(也就是判断 -- parent和cur的相对位置)
                            if (parent->_left == cur)
                            {
                                // 一条偏右的直线,需要右旋
                                RotateR(grandfather);
                                // 旋转完后parent成为根结点

                                // 更改完结点指向后,就可以改颜色了(都是根结点为黑,另外两个为红)
                                parent->_col = black;
                                cur->_col = grandfather->_col = red; // 和cur一层
                            }
                            else
                            {
                                // 拐角在左边,也就是先左旋,再右旋
                                RotateL(parent);
                                RotateR(grandfather);
                                // cur成为根结点

                                // 改颜色
                                cur->_col = black;
                                parent->_col = grandfather->_col = red;
                            }
                            break;
                        }
                    }
                    else // parent在grandfather的右树
                    {
                        Node *uncle = grandfather->_left;

                        if (uncle && uncle->_col == red)
                        {
                            parent->_col = uncle->_col = black;
                            grandfather->_col = red;
                        }
                        else // 如果uncle不存在/为黑,就需要旋转+变色了
                        {
                            // 需要先判断旋转类型(也就是判断 -- parent和cur的相对位置)
                            if (parent->_right == cur)
                            {
                                // 一条偏左的直线,需要左旋
                                RotateL(grandfather);
                                parent->_col = black;
                                cur->_col = grandfather->_col = red; // 和cur一层
                            }
                            else
                            {
                                // 拐角在right,也就是先右旋,再左旋
                                RotateR(parent);
                                RotateL(grandfather);
                                // 改颜色
                                cur->_col = black;
                                parent->_col = grandfather->_col = red;
                            }
                            break;
                        }
                    }
                    cur = grandfather; // 注意,这里会改cur的指向,但返回值需要返回插入位置的迭代器,所以需要另外保存
                    parent = cur->_parent;
                }
                (_pHead->_parent)->_col = black; // 根结点必须为黑(防止它在上面的循环中被修改)
            }

            return std::make_pair(iterator(newnode), true);
        }

    template <class K, class T, class KeyOfT>
    bool RBTree<K, T, KeyOfT>::_IsValidRBTRee(Node *cur, size_t blackCount, size_t pathBlack)
    {
        if (cur == nullptr)
        {
            // 到空结点后,就说明一条路径已经走通了,可以用得到的黑色结点数与基准数对比,不一样就说明红黑树错误
            if (pathBlack != blackCount)
            {
                return false;
            }
            else
            {
                return true;
            }
        }
        if (cur->_parent)
        {
            Node *ppnode = cur->_parent;
            if (cur->_col == red && ppnode->_col == red)
            {
                return false;
            }
        }
        if (cur->_col == black)
        {
            ++pathBlack;
        }
        return _IsValidRBTRee(cur->_left, blackCount, pathBlack) && _IsValidRBTRee(cur->_right, blackCount, pathBlack);
    }

    template <class T, class Ptr, class Ref>
    void RBTreeIterator<T, Ptr, Ref>::Increament()
    {
        Node *cur = _pNode, *parent = _pNode->_parent;
        if (cur->_right)
        {
            // 找到右子树的最小结点
            Node *curright = cur->_right;
            while (curright->_left)
            {
                curright = curright->_left;
            }
            _pNode = curright;
        }
        else
        {
            while (parent->_parent != cur && parent->_right == cur) // 找到cur是parent的左结点的位置,这样parent的位置就是下一个位置
            {
                cur = parent;
                parent = parent->_parent;
            }
            _pNode = parent;
        }
    }
    template <class T, class Ptr, class Ref>
    void RBTreeIterator<T, Ptr, Ref>::DeIncreament()
    {
        Node *cur = _pNode, *parent = _pNode->_parent;
        if (cur->_left)
        {
            // 找到左子树的最大结点
            Node *curleft = cur->_left;
            while (curleft->_right)
            {
                curleft = curleft->_right;
            }
            _pNode = curleft;
        }
        else
        {
            while (parent->_parent != cur && parent->_left == cur) // 找到cur是parent的左结点的位置,这样parent的位置就是下一个位置
            {
                cur = parent;
                parent = parent->_parent; 
            }
            _pNode = parent;
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1056137.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目管理之高效合作

序 一件事能不能做成&#xff0c;和你有什么关系&#xff1f;靠的是你的努力吗&#xff1f;还是说靠的只是一个运气&#xff1f; 就像买彩票一样&#xff0c;你觉得中奖和个人努力有没有关系&#xff1b;就像和高考一样&#xff0c;你觉得考上北大清华和个人努力有没有关系&…

IDEA git操作技巧大全,持续更新中

作者简介 目录 1.创建新项目 2.推拉代码 3.状态标识 5.cherry pick 6.revert 7.squash 8.版本回退 9.合并冲突 1.创建新项目 首先我们在GitHub上创建一个新的项目&#xff0c;然后将这个空项目拉到本地&#xff0c;在本地搭建起一个maven项目的骨架再推上去&#xff0…

两条链表相同位数相加[中等]

优质博文IT-BLOG-CN 一、题目 给你两个非空的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照逆序的方式存储的&#xff0c;并且每个节点只能存储一位数字。请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。你可以假设除了数字0之外&#xff0c;这…

一文带你掌握 优先级队列

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;…

Leetcode.965 单值二叉树

本专栏内容为&#xff1a;leetcode刷题专栏&#xff0c;记录了leetcode热门题目以及重难点题目的详细记录 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;八大排序汇总 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &…

【算法练习Day9】用栈实现队列用队列实现栈

、​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 用栈实现队列用队列实…

代码随想录算法训练营第五十一天 |309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费、总结

一、309.最佳买卖股票时机含冷冻期 题目链接/文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;动态规划来决定最佳时机&#xff0c;这次有冷冻期&#xff01;| LeetCode&#xff1a;309.买卖股票的最佳时机含冷冻期_哔哩哔哩_bilibili 思考&#xff1a; 1.确定dp数组&…

创建型设计模式 原型模式 建造者模式 创建者模式对比

创建型设计模式 单例 工厂模式 看这一篇就够了_软工菜鸡的博客-CSDN博客 4.3 原型模式 4.3.1 概述 用一个已经创建的实例作为原型&#xff0c;通过复制该原型对象来创建一个和原型对象相同的新对象。 4.3.2 结构 原型模式包含如下角色&#xff1a; 抽象原型类&#xff1a;规定了…

FFmpeg 命令:从入门到精通 | ffmpeg 命令视频录制

FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg 命令视频录制 FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg 命令视频录制安装软件&#xff1a;Screen Capturer Recorder查看可用设备名字音视频录制录制视频&#xff08;默认参数&#xff09;录制声音&#xff08;默认参数&am…

计算机竞赛 深度学习疲劳检测 驾驶行为检测 - python opencv cnn

文章目录 0 前言1 课题背景2 相关技术2.1 Dlib人脸识别库2.2 疲劳检测算法2.3 YOLOV5算法 3 效果展示3.1 眨眼3.2 打哈欠3.3 使用手机检测3.4 抽烟检测3.5 喝水检测 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习加…

AJAX--Express速成

一、基本概念 1、AJAX(Asynchronous JavaScript And XML)&#xff0c;即为异步的JavaScript 和 XML。 2、异步的JavaScript 它可以异步地向服务器发送请求&#xff0c;在等待响应的过程中&#xff0c;不会阻塞当前页面。浏览器可以做自己的事情。直到成功获取响应后&#xf…

1558. 得到目标数组的最少函数调用次数

1558. 得到目标数组的最少函数调用次数 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 1558. 得到目标数组的最少函数调用次数 https://leetcode.cn/problems/minimum-numbers-of-function-calls-to-make-target…

毛玻璃用户卡交互

效果展示 页面结构组成 从效果展示可以看到&#xff0c;此效果都是比较常规的。主要的核心就是卡片的悬停效果。 CSS 知识点 backdrop-filter 回顾transitiontransform 页面基础布局实现 <section><div class"container"><div class"card&q…

Academic accumulation|社会创业研究:过去的成就和未来的承诺

文献来源&#xff1a;Saebi T, Foss N J, Linder S. Social entrepreneurship research: Past achievements and future promises[J]. Journal of management, 2019, 45(1): 70-95. 一、文章介绍 &#xff08;一&#xff09;文章主要包含什么&#xff1f; SE越来越受到学术界的…

凉鞋的 Unity 笔记 104. 测试所涉及的窗口

104. 测试所涉及的窗口 在上一篇&#xff0c;笔者简单介绍了检视器窗口&#xff0c;如图所示&#xff1a; 我们接着介绍上图中的最后一个部分内容&#xff0c;测试部分。 测试部分我们只做了一件非常简单的操作&#xff0c;就是点击了一下运行按钮&#xff0c;查看结果&#…

【浅记】分而治之

归并排序 算法流程&#xff1a; 将数组A[1,n]排序问题分解为A[1,n/2]和A[n/21,n]排序问题递归解决子问题得到两个有序的子数组将两个子数组合并为一个有序数组 符合分而治之的思想&#xff1a; 分解原问题解决子问题合并问题解 递归式求解 递归树法 用树的形式表示抽象递…

JAVA面经整理(4)

一)Volitaile关键字的作用: volatile的使用:常常用于一写多读的情况下&#xff0c;解决内存可见性和指令重排序 JAVA内存的JMM模型:主要是用来屏蔽不同硬件和操作系统的内存访问差异的&#xff0c;在不同的硬件和不同的操作系统内存的访问是有差异的&#xff0c;这种差异会导致…

C. Best Binary String

题目&#xff1a;样例&#xff1a; 输入 4 ??01? 10100 1??10? 0?1?10?10输出 00011 10100 111101 011110010 思路&#xff1a; 根据题意&#xff0c; 题目意思是 ‘&#xff1f;’ 字符 该替换何值&#xff0c;使得原字符串&#xff0c;最少操作 翻转任意区间&#…

贪心算法+练习

正值国庆之际&#xff0c;祝愿祖国繁荣昌盛&#xff0c;祝愿朋友一生平安&#xff01;终身学习&#xff0c;奋斗不息&#xff01; 目录 1.贪心算法简介 2.贪心算法的特点 3.如何学习贪心算法 题目练习&#xff08;持续更新&#xff09; 1.柠檬水找零&#xff08;easy&…

QT:鼠标画线(双画布)

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPoint> //点 #include <QMouseEvent> //鼠标事件 #include <QPaintEvent> //绘图事件class Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent 0);~Wi…