【数据结构】6.5 红黑树(C++)

2023-11-12

【数据结构】——6.5 红黑树

没有学过二叉搜索树(也叫二叉排序树或二叉查找树)的小伙伴们建议先学习一下,这样阅读会更轻松哦 点我学习二叉搜索树

一、红黑树的概念和性质

红黑树是一种近似平衡的二叉搜索树,它有以下特点:

  1. 每个节点只能是红色或黑色
  2. 根节点为黑色
  3. 红色节点的两个孩子必须是黑色
  4. 对于每个节点,到所有叶子节点的路径中,经过的黑色节点数目是相同的
  5. 叶子节点的两个空指针孩子当作黑节点来看,它被称为NIL节点,在使用时我们也可以忽略它的存在。

在这里插入图片描述

红黑树这些规则有什么用呢?

  1. 因为红色节点的孩子必须是黑色的,所以不可能存在2个连续的红色节点
  2. 因为每个节点到低的每条路径黑色数目相同,且不存在2个连续的的红色节点,所以一条路径最短的情况是每个节点都是黑色节点,最长的情况是一黑一红相间的路径
  3. 因此最长的路径一定不会超过最短路径节点数的2倍,这保证了红黑树不会出现单支树的问题,在一定程度上确保了二叉搜索树的平衡性。

红黑树的最优情况:全为黑色节点或每条路径都是一黑一红

红黑树的最差情况:左子树全黑,右子树一黑一红

红黑树是一个近似平衡搜索二叉树,它确保了没有一条路径会比其他路径长出2倍,解决了普通二叉搜索树因不平衡导致查找效率低的问题。

在这里插入图片描述

AVL树也是一颗近似平衡的二叉搜索树,它们有什么区别?

  1. AVL树中左子树与右子树的高度差不能超过1,红黑树中最长路径不会超过最短路径的2倍。所以从平衡性来讲,AVL树比红黑树更加接近平衡,AVL树的查找效率比红黑树更高
  2. 当插入节点时,AVL树中平衡因子超过2就要进行旋转,而红黑树中出现连续红色节点时才会旋转。这是红黑树的旋转次数比AVL树少,因此红黑树的插入效率比AVL树更高。同理,删除效率也比AVL树更高

虽然选择二叉搜索树作为数据结构去使用是因为它高效率的查找效率,但是在我们经常使用的一些容器中,插入和删除也是我们频繁使用的接口,因此在实际应用中,红黑树的应用比AVL树更广泛。但是对于一些特定场合,使用AVL树也是更不错的选择。

二、红黑树的存储结构和声明

  1. 红黑树的结构依然使用三叉链表来实现
  2. 我们使用enum枚举类型来定义节点的红色和黑色
  3. 我们只实现红黑树的插入,查找和中序遍历,并且提供一个函数检测我们的红黑树是否满足规则
namespace Name
{
    // 颜色定义
    enum Color
	{
		RED,
		BLACK
	};
    
    // 节点声明
    template <class K>
    struct RBTreeNode
    {
        K& _key;					// 键
        RBTreeNode<K> *_left;
        RBTreeNode<K> *_right;
        RBTreeNode<K> *_parent;
        Color _col;					// 颜色

        // 构造函数
        RBTreeNode(const K& key)
            : _key(key)
            , _left(nullptr)
            , _right(nullptr)
            , _parent(nullptr)
            , _col(RED)
        {
            ;
        }
    };
    
    // 红黑树的声明
    template <class K>
	class RBTree
	{
		typedef RBTreeNode<K> Node;		// 将节点类型重命名为Node
        
	public:
        bool Insert(const K& key);		// 插入
        bool Find(const K& key);		// 查找
        void InOrder(void);				// 中序遍历
        bool IsBalance(void);			// 检测是否满足红黑树规则
        
    private:
        Node* _root;	// 根节点指针
    };
}

三、红黑树的构建过程

红黑树通过不断插入元素,构成二叉搜索树,若是破坏了红黑树的规则,则对树进行调整,直到构成红黑树

插入的新节点该是什么颜色呢?

新创建的节点一定是红色,因为新节点是黑色会导致本条路径的黑色节点数量多出一个,破坏红黑树的规则

插入步骤:

  1. 按照二叉搜索树的规则,将新节点插入对应的位置,新节点为红色
  2. 当父亲节点是黑色时,没有破坏红黑树的规则,插入结束
  3. 当父亲节点是红色时,出现连续的红色,需要对红黑树进行调整,使之重新构成红黑树

当出现连续的红色节点时,当前节点为红色父节点为红色祖父节点一定为黑色,所以我们通过叔叔节点来判断当前红黑树的状况。

在这里插入图片描述

新节点插入,父亲节点为红色时,叔叔节点只有2种情况:叔叔节点为红色或不存在

在这里插入图片描述

(1)叔叔节点为红色

当叔叔节点为红色时,需要将父亲节点和叔叔节点变成黑色,祖父节点变成红色,此时整颗子树每条路径的黑色节点仍然是一个,与其他路径的黑色节点数量一致。

在这里插入图片描述

当叔叔节点不存在时插入新节点,我们通过变色的方式调整为红黑树,此时子树的顶端被我们修改为了红色。

  1. 若是子树的父节点为红色,那么就会出现连续的红色节点,我们需要继续向上调整
  2. 若是子树的顶端就是根节点,那么根节点不能被修改为红色

对于根节点问题,我们可以判断子树顶端是否为根节点,如果是则不改变根节点的颜色。也可以在插入完成时将根节点的颜色改为黑色。我们采用第二种方案,因为不需要判断,写起来方便

向上调整时,叔叔节点一定存在,若是叔叔节点还是红色,则继续变色,直到调整到根节点或叔叔节点为黑色

红色方框的意思是,该节点为红色或不存在

在这里插入图片描述

(2)叔叔节点为黑色或不存在

当叔叔节点为黑色或者不存在时,需要对树的形状进行调整,调整的方式就是旋转

根据此时树的形状,将旋转分为4类,分别是左单旋,右单旋,左右双旋,右左双旋

当旋转完毕后要对对应的节点进行变色

黑色方框代表节点为黑色或不存在

<1>右单旋

  1. p是g的左孩子,c是p的左孩子时,进行右单旋
  2. 当旋转完毕后,将父节点变成黑色当前节点和叔叔节点均为红色

右单旋的过程:

  1. 让祖父节点g成为父节点p的右孩子
  2. 若是父节点p有右孩子,则将其交给祖父节点p领养,成为p的左孩子。(图中p没有右孩子)
  3. 此时父节点p在子树的最顶端,将其改为黑色。祖父节点g改为红色

在这里插入图片描述

代码实现

以下代码的实现与AVL树中的代码一样,只是没有平衡因子的更新。还有一个区别是这里的参数传递的是祖父节点,但是操作和AVL树中的父节点一样,可以理解成红黑树中的当前节点比AVL树中的矮一层

// 右单旋
void RotateR(Node* grandpa)
{
    Node* grandpa_p = grandpa->_parent;		// 祖父节点的父节点,用来连接旋转后的子树
    Node* parent = grandpa->_left;			// 父节点
    Node* rightSun = parent->_right;		// 父节点的右孩子

    // 链接孩子节点
    parent->_right = grandpa;
    grandpa->_left = rightSun;
    if (grandpa_p != nullptr)
    {
        // 祖父节点不为根节点
        if (grandpa_p->_left == grandpa)
        {
            grandpa_p->_left = parent;
        }
        else
        {
            grandpa_p->_right = parent;
        }
    }
    else
    {
        // 祖父节点为根节点
        _root = parent;
    }

    // 更新父节点
    parent->_parent = grandpa_p;
    grandpa->_parent = parent;
    if (rightSun != nullptr)
    {
        rightSun->_parent = grandpa;
    }
}

<2>左单旋

  1. p是g的右孩子,c是p的右孩子时,进行左单旋
  2. 当旋转完毕后,将父节点变成黑色当前节点和叔叔节点均为红色

右单旋的过程:

  1. 让祖父节点g成为父节点p的左孩子
  2. 若是父节点p有左孩子,则将其交给祖父节点p领养,成为p的右孩子。(图中p没有左孩子)
  3. 此时父节点p在子树的最顶端,将其改为黑色。祖父节点g改为红色

在这里插入图片描述

代码实现:

// 左单旋
void RotateL(Node* grandpa)
{
    Node* grandpa_p = grandpa->_parent;
    Node* parent = grandpa->_right;
    Node* leftSub = grandpa->_left;

    // 链接孩子节点
    grandpa->_left = grandpa;
    grandpa->_right = leftSub;
    if (grandpa_p != nullptr)
    {
        if (grandpa_p->_left == grandpa)
        {
            grandpa_p->_left = grandpa;
        }
        else
        {
            grandpa_p->_right = grandpa;
        }
    }
    else
    {
        _root = grandpa;
    }

    // 更新父节点
    grandpa->_parent = grandpa_p;
    grandpa->_parent = grandpa;
    if (leftSub != nullptr)
    {
        leftSub->_parent = grandpa;
    }
}

<3>左右双旋

  1. p是g的左孩子,c是p的右孩子时,进行左右双旋
  2. 先以c节点为轴进行左单旋,再以旋转后的c节点为轴进行右单旋
  3. 当旋转完毕后,将父节点变成黑色当前节点和叔叔节点均为红色

在这里插入图片描述

由于红黑树的左右双旋和右左双旋可以直接调用左单旋和右单旋,并且最后只需要更新节点颜色,不需要多余的操作,我们就不将这两个封装成为一个单独的函数了,使用时直接展开书写即可。

<4>右左双旋

  1. p是g的右孩子,c是p的左孩子时,进行右左双旋
  2. 先以c节点为轴进行右单旋,再以旋转后的c节点为轴进行左单旋
  3. 当旋转完毕后,将父节点变成黑色当前节点和叔叔节点均为红色

在这里插入图片描述

这4种旋转方式得到的结果都有一个共同点:

子树的顶端一定是黑色节点,不可能再出现两个连续的红色节点,不需要再向上调整了

四、红黑树的实现

1. 默认成员函数

(1)构造函数

根节点指针_root初始化为空指针即可

RBTree(void)
    : _root(nullptr)
{
    ;
}

(2)析构函数

后序遍历依次释放每个节点,由于要使用递归,参数要传递节点指针,因此我们将递归过程单独封装成为一个函数,让析构函数调用它

// 析构函数
~RBTree(void)
{
    _Destroy(_root);
    _root = nullptr;
}

private:
// 后序遍历释放节点
void _Destroy(Node* root)
{
    if (root == nullptr)
    {
        return;
    }

    _Destroy(root->_left);
    _Destroy(root->_right);
    delete root;
}

(3)拷贝构造

先序遍历依次拷贝每个节点,也将递归过程单独封装为一个函数

// 拷贝构造
RBTree(const AVLTree& t)
{
    _root = _Copy(_root, t._root);
}

private:
// 先序遍历递归拷贝节点
Node* _Copy(Node* root, const Node* src)
{
    if (src == nullptr)
    {
        return nullptr;
    }

    root = new Node(src->_key);
    root->_left = _Copy(root->_left, src->_left);
    root->_right = _Copy(root->_right, src->_right);
    return root;
}

(4)赋值重载

创建临时对象拷贝用来赋值的对象,然后交换自己和它的根节点指针内容,获取临时对象的数据。

// 赋值重载函数
RTree& operator=(const AVLTree& t)
{
    if (this != &t)
    {
        BSTree temp(t);
        std::swap(temp._root, _root);
    }
    return *this;
}

2. 插入Insert

红黑树插入元素的步骤:

  1. 根据二叉搜索树规则将新节点插入到对应的位置
  2. 判断新节点的父节点为黑色,则插入完毕
  3. 如果新节点的父节点为红色,则根据它的叔叔节点做出相应的调整
bool Insert(const pair<K, V>& kv)
{
    // 1.以二叉搜索树的方式插入新节点
    if (_root == nullptr)
    {
        _root = new Node(kv);
        _root->_col = BLACK;		// 根节点为黑色
        return true;
    }

    Node *cur = _root, *parent = nullptr;
    while (cur != nullptr)
    {
        if (kv.first < cur->_kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else if (kv.first > cur->_kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else
        {
            return false;
        }
    }

    cur = new Node(kv);
    if (kv.first < parent->_kv.first)
    {
        parent->_left = cur;
        cur->_parent = parent;
    }
    else
    {
        parent->_right = cur;
        cur->_parent = parent;
    }
    
    // 2.调整为红黑树
    while (parent != nullptr && parent->_col == RED)
    {
        Node* grandpa = parent->_parent;	// 红色节点一定有父亲,所以不用担心空指针
        Node* uncle = grandpa->_left;		// 获取叔叔节点
        if (parent == uncle)
        {
            uncle = grandpa->_right;
        }

        if (uncle != nullptr && uncle->_col == RED)
        {
            // 叔叔节点为红:变色
            parent->_col = BLACK;
            uncle->_col = BLACK;
            grandpa->_col = RED;

            cur = grandpa;
            parent = cur->_parent;
        }
        else
        {
            // 叔叔节点为黑或不存在:旋转+变色
            if (parent == grandpa->_left && cur == parent->_left)
            {
                // 右单旋
                RotateR(grandpa);
                // 变色
                parent->_col = BLACK;
                grandpa->_col = RED;
            }
            else if (parent == grandpa->_right && cur == parent->_right)
            {
                // 左单旋
                RotateL(grandpa);
                // 变色
                parent->_col = BLACK;
                grandpa->_col = RED;
            }
            else if (parent == grandpa->_left && cur == parent->_right)
            {
                // 左右双旋
                RotateL(parent);
                RotateR(grandpa);
                // 变色
                cur->_col = BLACK;
                grandpa->_col = RED;
            }
            else if (parent == grandpa->_right && cur == parent->_left)
            {
                // 右左双旋
                RotateR(parent);
                RotateL(grandpa);
                // 变色
                cur->_col = BLACK;
                grandpa->_col = RED;
            }
            else
            {
                assert(false);		// 不会出现的情况,使用断言处理
            }

            break;	// 旋转之后,顶一定是黑,不需要再判断连续红节点了
        }
    }
    
    _root->_col = BLACK;	// 将根节点置为黑色
    return true;
}

3. 查找Find

就是二叉搜索树的查找方式进行查找

bool Find(const K& key)
{
    Node* cur = _root;
    while (cur != nullptr)
    {
        if (key < cur->_key)
        {
            // 比当前节点小,往左走
            cur = cur->_left;
        }
        else if (key > cur->_key)
        {
            // 比当前节点大,往右走
            cur = cur->_right;
        }
        else
        {
            // 与当前节点相等,返回true
            return true;
        }
    }

    return false;
}

4. 中序遍历

递归实现中序遍历

// 中序遍历
void InOrder(void)
{
    _InOrder(_root);
    std::cout << std::endl;
}

// 中序遍历的递归过程
void _InOrder(Node* root)
{
    if (root == nullptr)
    {
        return;
    }

    _InOrder(root->_left);
    std::cout << root->_key << " ";
    _InOrder(root->_right);
}

5. 检查是否构成红黑树

  1. 先检查空树是红黑树,再判断根节点是否为红色
  2. 为了对比每条路径上的黑色节点数量,我们先获取最左边路径的黑色根节点数量作为参考。若是每条路径的黑色根节点数量与黑色根节点数量一样,则满足红黑树的规则
  3. 使用递归遍历每个节点,并判断是否存在连续的节点
bool IsBalance(void)
{
    // 空树是红黑树
    if (_root == nullptr)
    {
        return true;
    }

    // 根节点为红色不是红黑树
    if (_root->_col != BLACK)
    {
        return false;
    }

    // 获取最左边路径的黑色节点数目
    int ref = 0;
    Node* left = _root;
    while (left != nullptr)
    {
        if (left->_col == BLACK)
        {
            ++ref;
        }
        left = left->_left;
    }

    return _IsBalance(_root, 0, ref);
}

private:
// 递归遍历节点,并判断是否满足红黑树的规则
bool _IsBalance(const Node* root, int blackNum, const int ref) const
{
    if (root == nullptr)
    {
        if (blackNum != ref)
        {
            cout << "路径黑色节点数跟最左路径不相等" << endl;
            return false;
        }
        return true;
    }

    // 判断是否有连续红色节点
    if (root->_col == RED && root->_parent->_col == RED)	// 红色节点一定有父亲,不担心空指针问题
    {
        cout << "error : " << root->_kv.first << " and " << root->_parent->_kv.first << endl;
        return false;
    }

    if (root->_col == BLACK)
    {
        ++blackNum;
    }

    return _isBalance(root->_left, blackNum, ref) 
        && _isBalance(root->_right, blackNum, ref);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【数据结构】6.5 红黑树(C++) 的相关文章

  • 将 SQL Server varBinary 数据转换为字符串 C#

    我需要帮助弄清楚如何转换来自SQL服务器表列设置为varBinary 最大 转换为字符串以便将其显示在标签中 这是在C 我正在使用数据读取器 我可以使用以下方式提取数据 var BinaryString reader 1 我知道该列包含之前
  • C中函数指针的递归声明

    我想声明一个返回指向相同类型函数的指针的函数 我想用它来实现如下状态机 typedef event handler t event handler t event t compilation error event handler t st
  • is_integral 与 is_integer:其中之一是多余的吗?

    是积分 http en cppreference com w cpp types is integral and 是整数 http en cppreference com w cpp types numeric limits is inte
  • 如果两个线程同时访问同一个 bool 变量会发生什么?

    我有一个跨平台 C 程序 其中使用 boost 库创建异步计时器 我有一个全局变量 bool receivedInput false 一个线程等待并处理输入 string argStr while 1 getline cin argStr
  • 如何将不记名令牌发送到 ASP NET MVC 5 中的视图?

    我有一个 NET MVC and WEB API项目 我想打电话给WEB API controllers来自 javascript 但我没有找到将令牌发送到我的视图的方法 我想添加bearer token in Viewbag变量 使用以下
  • 动态选择和更新 LINQ 结果集中的列值

    我有一个场景 其中存在 LINQ 结果集 我使用了以下查询 var stockDetails from d in db BloodBanks where d bbUserName Session username ToString sele
  • 如何通知父线程所有子线程都已终止?

    我有一个控制台应用程序正在移植到 WPF 该应用程序有 3 个工作线程 在将一些输出结果打印到屏幕上之前 这些线程都连接到主线程 我的理解是 如果我尝试在 WPF 应用程序中执行相同的操作 GUI 将被阻止并且不会响应用户 那么如何通知父线
  • WPF Dispatchertimer 延迟反应/冻结

    在我的 WPF 应用程序中 我使用 3 个不同的 DispatcherTimers 一种是用于显示当前时间 一种是每 5 秒运行一次数据库查询 第三个每 1 秒刷新一次自定义按钮的值 当我的程序运行时 有很多延迟 冻结 例如 时间开始正确计
  • 计算复杂数组的abs()值的最快方法

    我想计算 C 或 C 中复杂数组元素的绝对值 最简单的方法是 for int i 0 i lt N i b i cabs a i 但对于大向量来说 速度会很慢 有没有办法加快速度 例如使用并行化 语言可以是 C 或 C 鉴于所有循环迭代都是
  • 我的 WPF 应用程序未触发 MainWindow_Loaded

    我目前正在关注Pluralsight C Fundamentals Part 1并在Classes and Objects视频部分指导我在 Visual Studio 中创建一个新的 WPF 应用程序并填写代码 这导致以下结果 namesp
  • 试图使用加密来混淆我的项目打破了它

    我试图尝试不同的混淆选项 为了做到这一点 我首先尝试了加密货币 以下是我遵循的步骤 打开加密向导并选择一些选项 选择我的解决方案文件 完成向导后 我看到有些 Dll 被很好地混淆了 但我的项目现在无法构建 我注意到的两件事是 我的文件夹中有
  • Nuget - 对象引用未设置为对象的实例

    我在 vs 2015 中遇到了 nuget 包管理器的问题 像Unity这样的一些包已经安装没有问题了 某些软件包 例如 EF 在安装时出现问题 像 Automapper 这样的一些软件包也有同样的问题 但是当我安装这个软件包的另一个版本时
  • 从视图模型调用方法的命令

    好吧 我倾向于避免使用命令 因为它们总是让我感到困惑 但我正在进行一个新项目 并且正在尝试正确构建它 并且在我看来没有任何代码隐藏 基本上我现在想做的就是连接一个按钮来触发一个命令 在我的视图模型上执行一些操作 但不知何故 如此简单的事情仍
  • Request.Form 和 Request.QueryString 之间的区别?

    有人可以告诉我两者之间的确切区别吗Request Form and Request QueryString 我知道一个区别 比如 如果HTTP请求方式为POST 则用户提交的数据在申请表 收藏 如果HTTP请求方法是GET 则用户提交的数据
  • 为什么未到达的 try-catch 块会增加运行时间?

    我目前正在创建自己的容器库 但我已经看到无法访问 if 语句无效 try catch阻止增加运行时间 这是我的测试 Vector cpp template
  • 使用 _Alignas 进行结构成员对齐

    我想知道以下问题 是新的吗 Alignas结盟 C11 中的说明符适用于结构成员吗 我一直假设这么多 但彻底阅读了 N1570 公开草案似乎表明对齐说明符不能 出现在一个说明符限定符列表 这就是我所期望的 如果得到支持的话 我已经读过几遍语
  • 如何创建和使用类箭头运算符? [复制]

    这个问题在这里已经有答案了 因此 在到处研究之后 我似乎找不到如何创建类箭头运算符 即 class Someclass operator gt 我只需要知道如何使用它并正确使用它 它的输入是什么 它返回什么 我如何正确地声明 原型化它 运算
  • Gridview 错误:对 Bind 的调用格式不正确

    我有以下 gridview 代码
  • 使用 MVC5、Ajax、C# 和 MSSQL Server 级联 DropdownList

    我对来自 Windows 窗体和三层架构的 MVC 非常陌生 我试图找出使用从数据库填充的级联下拉列表 DDL 我使用 MS SQL Server 2012 VS 2013 目前我正在研究用户调查问卷 用户可以从 DDL 的多个答案中进行选
  • 如何使用 Ioc Unity 注入依赖属性

    我有以下课程 public interface IServiceA string MethodA1 public interface IServiceB string MethodB1 public class ServiceA IServ

随机推荐

  • mysql count(*)、count(1) 、count(列名)、count(distinct expr)

    文章目录 概述 优化 MyISAM InnoDB 参考文档 https dev mysql com doc refman 8 0 en group by functions html function count 概述 count 为 SQ
  • 蓝桥杯每日一题2023.9.8

    蓝桥杯2023年第十四届省赛真题 飞机降落 C语言网 dotcpp com 题目描述 N 架飞机准备降落到某个只有一条跑道的机场 其中第 i 架飞机在 Ti 时刻到达机场上空 到达时它的剩余油料还可以继续盘旋 Di 个单位时间 即它最早 可
  • Learning Video Object Segmentation from Static Images

    Abstract 论文灵感来源于 实例分割和目标跟踪 特点 1 我们的模型在每帧的基础上进行 并由前一帧的输出导向下一帧中的关注对象 2 一个高度准确的视频目标分割可以用一个卷积神经网络并用静态的图片来训练 3 使用在线和离线的策略 前者产
  • 为什么那么多的人选择到Java培训机构学习

    目前IT行业Java编程是最炙手可热的技术 Java应用范围广泛 企业在大量招收Java人才 薪水也随之上涨 发展前景越来越好 因此现在有越来越多的人发现了这片美丽的新大陆 都正在拼命往里的挤 一些觉得Java培训机构费用贵的同学会选择自学
  • public Map kaoYanAllStation() { Map map = new HashMap<>(); ...

    首先 根据代码中的注释可以看出 该方法主要是获取各种气象数据 对其进行计算和比较 然后将结果存储在一个 Map 对象中返回 为了优化这段代码 可以考虑以下几个方面 减少重复代码 在代码中可以看到 获取历年同期降水和温度数据的代码几乎一模一样
  • LVS常用模式(DR、NAT、TUN)以及ldirector和keepalived

    1 LVS简单介绍 1 lvs定义LVS是Linux Virtual Server的简写 意即Linux虚拟服务器 是一个虚拟的服务器集群系统 LVS集群采用IP负载均衡技术和基于内容请求分发技术 调度器具有很好的吞吐率 将请求均衡地转移到
  • Java学习教程,Java从入门到精通,全套Java视频教程+笔记+配套工具

    目录 一 大纲 一 Java基础 二 计算机基础 三 工具的使用 四 数据库 五 web前端 六 JavaWeb 七 框架 八 互联网分布式技术 发现身边很多自学java却放弃的 真的挺可惜的 白白浪费了几个月宝贵的时间 且放弃一次 就会有
  • 第二十二章 Spring AOP⾥⾯的代理知识

    1 静态代理和动态代理 什么是代理 为某 个对象创建 个代理对象 程序不直接 原本的对象 是由创建的代理对象来控制对原对象 通过代理类这中间 层 能有效控制对委托类对象的直接访问 也可以很好地隐藏和保护委托类对象 同时也为实施不同控制策略预
  • 05-网络的四层协议和七层协议

    TCP IP网络分层模型 TCP IP的设计创造性的提出了分层的概念 把复杂的网络通信划分出多个层次 再为每一个层次分配不同的职责 层次内只专心做好自己的事情 用分而治之的思想把一个大麻烦拆分成了数个小麻烦 从而解决了网络的难题 TCP I
  • JAVA中的for循环使用方法

    一 循环结构 1 概念 在学习Java里的循环之前 我们先来了解一下到底什么是循环 以及循环的作用 我们先来看下面这张图 大家想一下 我们在400米的跑道上参加万米长跑 正常情况下要跑25圈 这25圈每一圈的跑步过程其实都是一样的 相当于是
  • springboot过滤器和拦截器

    一 过滤器和拦截器的区别 1 过滤器和拦截器触发时机不一样 过滤器是在请求进入容器后 但请求进入servlet之前进行预处理的 请求结束返回也是 是在servlet处理完后 返回给前端之前 2 拦截器可以获取IOC容器中的各个bean 而过
  • Volatility3内存取证工具使用详解

    Volatility 介绍 Volatility是一款开源的内存取证分析工具 是一款开源内存取证框架 能够对导出的内存镜像进行分析 通过获取内核数据结构 使用插件获取内存的详细情况以及系统的运行状态 支持Windows Linux MaC
  • org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder 找不到依赖包

    org springframework boot autoconfigure jdbc DataSourceBuilder 找不到依赖包 org springframework boot autoconfigure jdbc DataSou
  • PowerOJ2546: fork【C++ STL __gnu_cxx::rope】

    题目链接 我们可以这样定义一个可持久化数组 rope
  • MIPI TX控制器的设计

    MIPI接口在移动设备中被广泛应用 主要用于传输图像传感器 液晶显示器等外设的数据 以MIPI DPHY v1 2为例 它包含一个CLK lane和若干个DATA lane 可配置 每个lane的最高速率可达到2 5Gbps 对比SerDe
  • 向win7旗舰版U盘启动盘 添加usb3.0driver

    以前的主板usb采用的是ehci controller 仅支持usb2 0 而现在的主板一般采用xhci controller 同时支持usb2 0和usb3 0 win7的镜像安装包里面的驱动并没有xhci的驱动 所以在如今的很多新平台的
  • 怎么样对阿里云ECS主机进行绑定域名

    首先我有个阿里的 域名 虚拟云主机 搭建了一个wordpress 的网站 地址为 www liuxun name wordpress liuxun name wordpress 现在我想把一个阿里云的ECS主机 里面装了tomcat 希望以
  • GET请求里的body问题

    故事还得从一个bug说起 今天有人问我 为什么发到后端的请求400了 我说肯定是参数不对 你去检查检查GET POST之类的方法写没写对 要么就是字段没对上 无非是这几个问题 然后他说检查过了 没问题啊 我不太相信 但是看了看前端发送的请求
  • springmvc 03(JSR303和拦截器)

    目录 一 JSR303 1 服务端验证 2 步骤 二 拦截器 1 简介 2 拦截器与过滤器 2 1 什么是过滤器 2 2 拦截器和过滤器的区别 3 拦截器案例 3 1 使用原理 一 JSR303 1 服务端验证 2 步骤 1 导入pom x
  • 【数据结构】6.5 红黑树(C++)

    数据结构 6 5 红黑树 没有学过二叉搜索树 也叫二叉排序树或二叉查找树 的小伙伴们建议先学习一下 这样阅读会更轻松哦 点我学习二叉搜索树 目录 一 红黑树的概念和性质 二 红黑树的存储结构和声明 三 红黑树的构建过程 四 红黑树的实现 1