C++进阶:(七)红黑树深度解析与 C++ 实现

目录

前言

一、红黑树的核心概念

[1.1 红黑树的定义](#1.1 红黑树的定义)

[1.2 红黑树的五大规则](#1.2 红黑树的五大规则)

[1.3 红黑树的平衡原理](#1.3 红黑树的平衡原理)

[1.4 红黑树的效率分析](#1.4 红黑树的效率分析)

二、红黑树的结构设计

[2.1 结点结构定义](#2.1 结点结构定义)

[2.2 红黑树类的框架](#2.2 红黑树类的框架)

[2.3 旋转操作实现](#2.3 旋转操作实现)

[2.3.1 右单旋(RotateR)](#2.3.1 右单旋(RotateR))

[2.3.2 左单旋(RotateL)](#2.3.2 左单旋(RotateL))

三、红黑树的插入实现

[3.1 插入的基础流程](#3.1 插入的基础流程)

[3.2 平衡调整的核心场景](#3.2 平衡调整的核心场景)

[场景 1:叔叔结点存在且为红色(变色调整)](#场景 1:叔叔结点存在且为红色(变色调整))

[场景 2:叔叔结点不存在或为黑色(单旋 + 变色)](#场景 2:叔叔结点不存在或为黑色(单旋 + 变色))

[场景 3:叔叔结点不存在或为黑色(双旋 + 变色)](#场景 3:叔叔结点不存在或为黑色(双旋 + 变色))

[3.3 完整插入代码实现](#3.3 完整插入代码实现)

[3.4 插入代码关键要点](#3.4 插入代码关键要点)

四、红黑树的查找与验证实现

[4.1 查找操作实现](#4.1 查找操作实现)

[4.2 红黑树的验证实现](#4.2 红黑树的验证实现)

[4.2.1 验证辅助函数(Check)](#4.2.1 验证辅助函数(Check))

[4.2.2 验证主函数(IsBalance)](#4.2.2 验证主函数(IsBalance))

五、红黑树的应用与扩展

[5.1 红黑树的工业应用](#5.1 红黑树的工业应用)

[5.2 红黑树的扩展(删除操作)](#5.2 红黑树的扩展(删除操作))

总结


前言

在数据结构与算法领域,平衡二叉搜索树是解决高效查找、插入、删除操作的核心数据结构。AVL 树作为经典的平衡二叉搜索树,通过严格控制左右子树的高度差不超过 1,保证了 O (logN) 的时间复杂度,但频繁的旋转操作导致插入和删除效率偏低。红黑树则通过颜色约束实现 "近似平衡",在保证 O (logN) 时间复杂度的前提下,大幅减少了旋转次数,成为工业界的首选 ------C++ STL 中的 set、map 等容器底层均采用红黑树实现。

本文将从红黑树的核心概念出发,详细拆解其规则约束、平衡原理、插入逻辑、代码实现及验证方法。下面就让我们正式开始吧!


一、红黑树的核心概念

1.1 红黑树的定义

红黑树是一棵二叉搜索树,在每个结点中增加一个存储位表示颜色(红色或黑色)。通过对从根到叶子的所有路径施加颜色约束,确保没有一条路径的长度超过其他路径的 2 倍,从而实现 "近似平衡"。

二叉搜索树的基础特性:对于任意结点,其左子树中所有结点的关键字均小于该结点的关键字,右子树中所有结点的关键字均大于该结点的关键字。红黑树继承了这一特性,因此查找操作可直接复用二叉搜索树的逻辑。

1.2 红黑树的五大规则

红黑树的平衡特性由以下五条规则严格约束(参考《算法导论》中的定义):

  1. 每个结点的颜色只能是红色或黑色
  2. 根结点 必须是黑色
  3. 所有叶子结点 (包括空结点)必须是黑色
  4. 如果一个结点是红色,那么它的两个子结点必须是黑色(无连续红色结点);
  5. 对于任意一个结点,从该结点到其所有后代 NIL 结点的简单路径上,包含的黑色结点数量相同(黑高一致)。

说明

  • NIL 结点是逻辑上的空结点,用于统一路径处理逻辑,在实际实现中是可以省略的(通过 nullptr 标识),但需在思维上保留该概念;
  • 规则 3 和规则 5 是红黑树平衡的核心,规则 4 则是避免路径过长的关键约束。

1.3 红黑树的平衡原理

红黑树通过规则约束,确保最长路径长度不超过最短路径长度的 2 倍,其推导过程如下:

  1. 定义**"黑高(bh)"**:从某结点到其后代 NIL 结点的路径上,黑色结点的数量(不包含当前结点);
  2. 由规则 5 可知,所有根到 NIL 结点的路径黑高一致,设根结点的黑高为 bh;
  3. 最短路径:全由黑色结点组成(无红色结点),长度为bh(路径上的结点数);
  4. 最长路径:红黑结点交替出现(由规则 4 限制,无连续红色结点),长度为2*bh(红色结点数 = 黑色结点数);
  5. 由此可得:最短路径长度 ≤ 任意路径长度 ≤ 2 * 最短路径长度,即红黑树是 "近似平衡" 的。

1.4 红黑树的效率分析

红黑树的时间复杂度由其高度决定,设树中结点数为 N,高度为 h:

  • 由二叉搜索树特性:N ≥ 2^bh - 1(全黑路径对应的满二叉树结点数);
  • 由最长路径约束:h ≤ 2*bh
  • 推导可得:h ≈ logN,因此查找、插入、删除操作的最坏时间复杂度均为 O (logN)

与 AVL 树对比:

特性 红黑树 AVL 树
平衡条件 颜色约束(近似平衡) 高度差≤1(严格平衡)
旋转次数 插入最多 2 次旋转 插入最多 2 次旋转
删除次数 删除最多 3 次旋转 删除最多 logN 次旋转
适用场景 频繁插入 / 删除的场景 频繁查找的场景

红黑树的优势在于对平衡的要求相对宽松,因此在插入和删除操作中需要的旋转次数更少,更加适合频繁修改数据的场景。

二、红黑树的结构设计

红黑树的结点需要存储关键字、颜色、左右子指针及父指针(父指针用于向上回溯调整平衡),采用模板类设计以支持任意可比较类型的关键字。

2.1 结点结构定义

cpp 复制代码
// 颜色枚举
enum Colour {
    RED,    // 红色结点
    BLACK   // 黑色结点
};

// 红黑树结点模板类
template<class K, class V>
struct RBTreeNode {
    pair<K, V> _kv;                  // 关键字-值对
    RBTreeNode<K, V>* _left;         // 左子指针
    RBTreeNode<K, V>* _right;        // 右子指针
    RBTreeNode<K, V>* _parent;       // 父指针(用于平衡调整)
    Colour _col;                     // 结点颜色

    // 构造函数
    RBTreeNode(const pair<K, V>& kv)
        : _kv(kv)
        , _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _col(RED) {}               // 新增结点默认红色(关键设计)
};

关键说明

  • 新增结点默认设为红色:若设为黑色,会直接破坏规则 5(黑高一致),而红色结点仅可能破坏规则 4(连续红色结点),后者更容易调整;
  • 父指针是红黑树平衡调整的核心:插入或删除后需要向上回溯父结点、祖父结点、叔叔结点的颜色和位置关系。

2.2 红黑树类的框架

红黑树类包含根结点指针,以及插入、查找、验证等核心成员函数:

cpp 复制代码
template<class K, class V>
class RBTree {
    typedef RBTreeNode<K, V> Node;   // 结点类型别名
public:
    // 构造函数
    RBTree() : _root(nullptr) {}

    // 插入操作
    bool Insert(const pair<K, V>& kv);

    // 查找操作
    Node* Find(const K& key);

    // 验证红黑树合法性
    bool IsBalance();

private:
    // 右单旋(与AVL树旋转逻辑一致)
    void RotateR(Node* parent);

    // 左单旋(与AVL树旋转逻辑一致)
    void RotateL(Node* parent);

    // 递归验证红黑树规则
    bool Check(Node* root, int blackNum, const int refNum);

private:
    Node* _root;  // 根结点指针
};

2.3 旋转操作实现

红黑树的旋转操作是与 AVL 树完全一致的,目的是调整子树结构以恢复平衡,无需修改结点颜色(颜色调整单独处理)。旋转操作分为右单旋和左单旋。

2.3.1 右单旋(RotateR)

当左子树过高时,通过右单旋将左子树的根结点提升为新的父结点,原父结点变为新根的右子结点。

cpp 复制代码
template<class K, class V>
void RBTree<K, V>::RotateR(Node* parent) {
    Node* subL = parent->_left;      // 左子树的根结点
    Node* subLR = subL->_right;      // 左子树的右孩子

    // 1. 处理subLR与parent的关系
    parent->_left = subLR;
    if (subLR != nullptr) {
        subLR->_parent = parent;
    }

    // 2. 处理subL与parent父结点的关系
    Node* parentParent = parent->_parent;
    if (parentParent == nullptr) {
        // parent是根结点,旋转后subL成为新根
        _root = subL;
    } else if (parent == parentParent->_left) {
        // parent是左孩子
        parentParent->_left = subL;
    } else {
        // parent是右孩子
        parentParent->_right = subL;
    }
    subL->_parent = parentParent;

    // 3. 处理subL与parent的关系
    subL->_right = parent;
    parent->_parent = subL;
}

2.3.2 左单旋(RotateL)

当右子树过高时,通过左单旋将右子树的根结点提升为新的父结点,原父结点变为新根的左子结点。

cpp 复制代码
template<class K, class V>
void RBTree<K, V>::RotateL(Node* parent) {
    Node* subR = parent->_right;      // 右子树的根结点
    Node* subRL = subR->_left;        // 右子树的左孩子

    // 1. 处理subRL与parent的关系
    parent->_right = subRL;
    if (subRL != nullptr) {
        subRL->_parent = parent;
    }

    // 2. 处理subR与parent父结点的关系
    Node* parentParent = parent->_parent;
    if (parentParent == nullptr) {
        // parent是根结点,旋转后subR成为新根
        _root = subR;
    } else if (parent == parentParent->_left) {
        // parent是左孩子
        parentParent->_left = subR;
    } else {
        // parent是右孩子
        parentParent->_right = subR;
    }
    subR->_parent = parentParent;

    // 3. 处理subR与parent的关系
    subR->_left = parent;
    parent->_parent = subR;
}

旋转操作的要点

  • 旋转时需维护父指针的指向,避免出现悬空指针;
  • 旋转仅调整结构,不改变结点颜色,也不破坏二叉搜索树的特性。

三、红黑树的插入实现

红黑树的插入流程分为两步:

① 按二叉搜索树规则插入结点;

② 调整结点颜色和结构,恢复红黑树规则。

3.1 插入的基础流程

  1. 若树为空,直接创建根结点并设为黑色(满足规则 2);
  2. 若树非空,按二叉搜索树规则找到插入位置,创建新结点(默认红色)并插入
  3. 插入后检查红黑树规则:
  • 若新结点的父结点为黑色,规则未被破坏,插入结束;
  • 若新结点的父结点为红色,破坏规则 4(连续红色结点),需进行平衡调整。

3.2 平衡调整的核心场景

插入调整的关键在于分析新结点(cur)、父结点(parent)、祖父结点(grandfather)、叔叔结点(uncle)的颜色和位置关系。由于父结点为红色,祖父结点必为黑色(规则 4 不允许连续红色),因此核心变量是叔叔结点的颜色。

为了简化分析,我们定义以下标识:

  • cur:新增结点(红色);
  • parent:cur 的父结点(红色);
  • grandfather:parent 的父结点(黑色);
  • uncle:parent 的兄弟结点(祖父结点的另一个子结点)。

根据 parent 是 grandfather 的左孩子或右孩子,以及 uncle 的颜色,下面我们将平衡调整问题分为三大场景,每个场景对应不同的调整策略。

场景 1:叔叔结点存在且为红色(变色调整)

条件:parent 为红,grandfather 为黑,uncle 存在且为红。

问题 :cur 与 parent 连续红色(破坏规则 4)。

调整策略

  1. parentuncle设为黑色;
  2. grandfather设为红色;
  3. grandfather作为新的 cur,向上回溯调整(可能 grandfather 的父结点也是红色)。

调整原理

  • parent 和 uncle 变黑,保证了原路径的黑高不变(满足规则 5);
  • grandfather 变红,可能导致其与父结点连续红色,因此需要继续向上调整;
  • 若 grandfather 是根结点,调整后需将其设为黑色(满足规则 2)。

代码片段

cpp 复制代码
// 假设parent是grandfather的左孩子
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED) {
    // 场景1:叔叔存在且为红,变色调整
    parent->_col = BLACK;
    uncle->_col = BLACK;
    grandfather->_col = RED;

    // 向上回溯
    cur = grandfather;
    parent = cur->_parent;
}

场景 2:叔叔结点不存在或为黑色(单旋 + 变色)

条件:parent 为红,grandfather 为黑,uncle 不存在或为黑,且 cur 与 parent 同方向(cur 是 parent 的左孩子,parent 是 grandfather 的左孩子;或 cur 是 parent 的右孩子,parent 是 grandfather 的右孩子)。

问题:cur 与 parent 是连续红色的,并且无法通过单纯变色解决(uncle 为黑,变色会破坏规则 5)。

调整策略

  1. grandfather为旋转中心,进行单旋(parent 是左孩子则右旋,parent 是右孩子则左旋);
  2. parent设为黑色(新的子树根结点);
  3. grandfather设为红色

调整原理

  • 旋转后 parent 成为子树根结点,设为黑色保证黑高不变;
  • grandfather 成为 parent 的子结点,设为红色避免连续红色;
  • 调整后无需向上回溯(parent 的父结点若存在,必为黑色,否则之前会被处理)。

代码片段(parent 是 grandfather 的左孩子,cur 是 parent 的左孩子)

cpp 复制代码
else if (cur == parent->_left) {
    // 场景2:单旋(右旋)+ 变色
    RotateR(grandfather);
    parent->_col = BLACK;
    grandfather->_col = RED;
    break;  // 调整完成,无需向上回溯
}

代码片段(parent 是 grandfather 的右孩子,cur 是 parent 的右孩子)

cpp 复制代码
else if (cur == parent->_right) {
    // 场景2:单旋(左旋)+ 变色
    RotateL(grandfather);
    parent->_col = BLACK;
    grandfather->_col = RED;
    break;
}

场景 3:叔叔结点不存在或为黑色(双旋 + 变色)

条件:parent 为红,grandfather 为黑,uncle 不存在或为黑,且 cur 与 parent 反方向(cur 是 parent 的右孩子,parent 是 grandfather 的左孩子;或 cur 是 parent 的左孩子,parent 是 grandfather 的右孩子)。

问题:cur 与 parent 连续红色,且单旋无法直接调整(cur 在 parent 的另一侧)。

调整策略

  1. parent为旋转中心,进行一次单旋(cur 是 parent 的右孩子则左旋,cur 是 parent 的左孩子则右旋),将 cur调整到 parent的位置;
  2. grandfather为旋转中心,进行二次单旋(与场景 2 一致);
  3. cur设为黑色(新的子树根结点);
  4. grandfather设为红色。

调整原理

  • 第一次旋转将 cur 与 parent 的位置互换,转化为场景 2 的情况;
  • 第二次旋转调整结构,cur 设为黑色保证黑高不变;
  • 调整后无需向上回溯。

代码片段(parent 是 grandfather 的左孩子,cur 是 parent 的右孩子)

cpp 复制代码
else {
    // 场景3:双旋(先左旋parent,再右旋grandfather)+ 变色
    RotateL(parent);
    RotateR(grandfather);
    cur->_col = BLACK;
    grandfather->_col = RED;
    break;
}

代码片段(parent 是 grandfather 的右孩子,cur 是 parent 的左孩子)

cpp 复制代码
else {
    // 场景3:双旋(先右旋parent,再左旋grandfather)+ 变色
    RotateR(parent);
    RotateL(grandfather);
    cur->_col = BLACK;
    grandfather->_col = RED;
    break;
}

3.3 完整插入代码实现

cpp 复制代码
template<class K, class V>
bool RBTree<K, V>::Insert(const pair<K, V>& kv) {
    // 步骤1:空树处理
    if (_root == nullptr) {
        _root = new Node(kv);
        _root->_col = BLACK;  // 根结点必须为黑色
        return true;
    }

    // 步骤2:按二叉搜索树规则查找插入位置
    Node* parent = nullptr;
    Node* cur = _root;
    while (cur != nullptr) {
        if (cur->_kv.first < kv.first) {
            // 关键字大于当前结点,向右走
            parent = cur;
            cur = cur->_right;
        } else if (cur->_kv.first > kv.first) {
            // 关键字小于当前结点,向左走
            parent = cur;
            cur = cur->_left;
        } else {
            // 关键字已存在,插入失败
            return false;
        }
    }

    // 步骤3:创建新结点并插入
    cur = new Node(kv);
    cur->_col = RED;  // 新增结点默认红色
    if (parent->_kv.first < kv.first) {
        parent->_right = cur;
    } else {
        parent->_left = cur;
    }
    cur->_parent = parent;

    // 步骤4:平衡调整(父结点为红色时才需要调整)
    while (parent != nullptr && parent->_col == RED) {
        Node* grandfather = parent->_parent;  // 祖父结点必为黑色
        if (parent == grandfather->_left) {
            // 情况A:parent是grandfather的左孩子
            Node* uncle = grandfather->_right;

            if (uncle != nullptr && uncle->_col == RED) {
                // 场景1:叔叔存在且为红,变色调整
                parent->_col = BLACK;
                uncle->_col = BLACK;
                grandfather->_col = RED;

                // 向上回溯
                cur = grandfather;
                parent = cur->_parent;
            } else {
                // 场景2和3:叔叔不存在或为黑,旋转+变色
                if (cur == parent->_left) {
                    // 场景2:cur是parent的左孩子,右单旋
                    RotateR(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                } else {
                    // 场景3:cur是parent的右孩子,双旋
                    RotateL(parent);
                    RotateR(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;  // 调整完成,无需向上回溯
            }
        } else {
            // 情况B:parent是grandfather的右孩子(与左孩子对称)
            Node* uncle = grandfather->_left;

            if (uncle != nullptr && uncle->_col == RED) {
                // 场景1:叔叔存在且为红,变色调整
                parent->_col = BLACK;
                uncle->_col = BLACK;
                grandfather->_col = RED;

                // 向上回溯
                cur = grandfather;
                parent = cur->_parent;
            } else {
                // 场景2和3:叔叔不存在或为黑,旋转+变色
                if (cur == parent->_right) {
                    // 场景2:cur是parent的右孩子,左单旋
                    RotateL(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                } else {
                    // 场景3:cur是parent的左孩子,双旋
                    RotateR(parent);
                    RotateL(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;  // 调整完成,无需向上回溯
            }
        }
    }

    // 确保根结点始终为黑色(可能在场景1中被设为红色)
    _root->_col = BLACK;
    return true;
}

3.4 插入代码关键要点

  1. 根结点最终必须设为黑色:场景 1 中可能将根结点设为红色,因此插入结束后需强制将根结点设为黑色;
  2. 回溯终止条件:parent 为空(cur 成为根结点)或 parent 为黑色(规则 4 不再被破坏);
  3. 对称处理:parent 是 grandfather 的左孩子和右孩子的逻辑对称,仅需调整旋转方向和叔叔结点的位置;
  4. 新结点默认红色:这是红黑树插入效率高的关键设计,避免直接破坏规则 5。

四、红黑树的查找与验证实现

4.1 查找操作实现

红黑树的查找逻辑与二叉搜索树完全一致,利用其 "左小右大" 的特性遍历树即可,时间复杂度为 O (logN)

cpp 复制代码
template<class K, class V>
typename RBTree<K, V>::Node* RBTree<K, V>::Find(const K& key) {
    Node* cur = _root;
    while (cur != nullptr) {
        if (cur->_kv.first < key) {
            // 关键字大于当前结点,向右走
            cur = cur->_right;
        } else if (cur->_kv.first > key) {
            // 关键字小于当前结点,向左走
            cur = cur->_left;
        } else {
            // 找到目标结点
            return cur;
        }
    }
    // 未找到
    return nullptr;
}

4.2 红黑树的验证实现

红黑树的验证需检查所有规则是否满足,核心是规则 4 (无连续红色结点)和规则 5(黑高一致)。验证流程如下:

  1. 根结点必须为黑色;
  2. 前序遍历检查无连续红色结点;
  3. 计算所有路径的黑高,确保一致。

4.2.1 验证辅助函数(Check)

递归遍历树,检查连续红色结点和黑高一致性:

cpp 复制代码
template<class K, class V>
bool RBTree<K, V>::Check(Node* root, int blackNum, const int refNum) {
    // 递归到空结点,检查当前路径黑高是否与参考黑高一致
    if (root == nullptr) {
        if (blackNum != refNum) {
            cout << "错误:存在黑高不一致的路径,当前黑高=" << blackNum << ",参考黑高=" << refNum << endl;
            return false;
        }
        return true;
    }

    // 检查连续红色结点(当前结点为红,父结点也为红)
    if (root->_col == RED && root->_parent != nullptr && root->_parent->_col == RED) {
        cout << "错误:存在连续红色结点,关键字=" << root->_kv.first << endl;
        return false;
    }

    // 遇到黑色结点,黑高计数+1
    if (root->_col == BLACK) {
        blackNum++;
    }

    // 递归检查左右子树
    return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
}

4.2.2 验证主函数(IsBalance)

计算参考黑高(根结点到最左路径的黑高),调用辅助函数进行验证:

cpp 复制代码
template<class K, class V>
bool RBTree<K, V>::IsBalance() {
    // 空树视为平衡
    if (_root == nullptr) {
        return true;
    }

    // 检查根结点是否为黑色
    if (_root->_col != BLACK) {
        cout << "错误:根结点不是黑色" << endl;
        return false;
    }

    // 计算参考黑高(根结点到最左路径的黑高)
    int refNum = 0;
    Node* cur = _root;
    while (cur != nullptr) {
        if (cur->_col == BLACK) {
            refNum++;
        }
        cur = cur->_left;
    }

    // 递归检查所有路径
    int blackNum = 0;
    return Check(_root, blackNum, refNum);
}

验证函数说明

  • 参考黑高选择根结点到最左路径的黑高,可任意选择一条路径作为参考;
  • 递归过程中,blackNum记录当前路径的黑高,refNum为参考黑高;
  • 若存在连续红色结点或黑高不一致,则直接返回 false并输出错误信息。

五、红黑树的应用与扩展

5.1 红黑树的工业应用

  1. C++ STL 容器:set、map、multiset、multimap的底层实现;
  2. Linux 内核:进程调度中的 CFS 调度器(使用红黑树管理进程优先级);
  3. 数据库:MySQL 的B + 树索引底层使用红黑树维护索引结构;
  4. 缓存系统:用于高效管理缓存数据的插入、删除和查找。

5.2 红黑树的扩展(删除操作)

红黑树的删除操作比插入更为复杂,核心难点在于删除黑色结点后会破坏规则 5(黑高一致),需要通过**"双重黑色" 结点**的概念进行调整。在这我就不详细讲解了,大家感兴趣的话可以参考《算法导论》第 13 章或《STL 源码剖析》的相关内容进行学习。

简单说一下删除操作的核心思路:

  1. 按二叉搜索树规则删除结点,找到替代结点(中序后继或前驱);
  2. 若删除的是红色结点,直接删除,规则未被破坏;
  3. 若删除的是黑色结点,需调整以恢复黑高一致,可能涉及变色、旋转或向上回溯。

总结

红黑树的核心难点在于插入后的平衡调整,需重点掌握三大场景的处理逻辑:叔叔结点为红时的变色调整、叔叔结点为黑或不存在时的单旋 + 变色和双旋 + 变色。通过反复练习和调试代码,相信大家一定可以逐步理解红黑树的平衡原理。感谢大家的支持!

相关推荐
硅农深芯2 小时前
如何使用ptqt5实现进度条的动态显示
开发语言·python·qt
kyle~3 小时前
计算机系统---USB的四种传输方式
运维·c++·计算机系统
Lhan.zzZ3 小时前
Qt数据可视化实战:饼图、线图与表格的完整指南
开发语言·qt·信息可视化
Acrelhuang3 小时前
筑牢用电防线:Acrel-1000 自动化系统赋能 35kV 园区高效供电-安科瑞黄安南
java·大数据·开发语言·人工智能·物联网
不穿格子的程序员3 小时前
从零开始写算法-栈-最小值(记忆化pair)
数据结构·
小龙报3 小时前
《算法通关指南数据结构和算法篇(4)--- 队列和queue》
c语言·开发语言·数据结构·c++·创业创新·学习方法·visual studio
打不了嗝 ᥬ᭄3 小时前
【Linux】网络层协议
linux·网络·c++·网络协议·http
民乐团扒谱机3 小时前
深入浅出理解克尔效应(Kerr Effect)及 MATLAB 仿真实现
开发语言·matlab·光学·非线性光学·克尔效应·kerr effect
7澄13 小时前
深入解析 LeetCode 数组经典问题:删除每行中的最大值与找出峰值
java·开发语言·算法·leetcode·intellij idea