跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

线段树学习笔记(C++)

线段树这一基于分治思想的二叉树数据结构,用于高效处理区间操作。对比了普通数组、前缀和数组与线段树在单点修改和区间查询上的复杂度差异,指出线段树的优势。详细讲解了线段树的 Push Up、建树、单点更新及区间查询的基本实现模板。此外,还引入了懒惰标记(Lazy Tag)机制,解决了区间修改导致复杂度退化的问题,并提供了相应的 Push Down、区间修改和区间查询代码示例。

全栈工匠发布于 2026/3/29更新于 2026/10/2491 浏览

一、什么是线段树?

线段树 (Segment Tree) 是一种二叉树数据结构,基于分治思想,用于高效地处理区间操作。

二、线段树的优点

假设有一个数组 A,大小为 N。

操作类型普通数组前缀和数组线段树
单点修改O(1)O(N) (需重建)O(\log N)
区间查询O(N)O(1)O(\log N)

普通数组:修改快,但求区间和慢(要遍历)。 前缀和:查询快,但只要修改一个数,整个前缀和数组都要更新。 线段树:修改和查询都很快,是一种完美的平衡。

三、线段树的结构原理

线段树的核心思想是分治。 线段树的每一个节点都代表一个区间。

假设我们的数组长度为 4 [1, 2, 3, 4]: 根节点:存整个区间 [1, 4] 的和。 左子节点:存左半部分 [1, 2] 的和。 右子节点:存右半部分 [3, 4] 的和。 叶子节点:存单个元素的值(如 [1, 1], [2, 2] 等)。

任何一个大区间查询,都可以拆分成几个小区间节点的组合。 例如区间 [1, 3] 可由左子节点 [1, 2] 和叶子节点 [3, 3] 结合而成。

四、线段树的基本实现(基于 C++)

以下一个经典的线段树模板。因为其为完全二叉树,我们使用堆式存储(数组模拟树),通常数组大小开到 4N 以防止越界。

1. Push Up

Push Up 是维护线段树性质的关键。使用子节点的信息来更新父节点

const int N = 1000;
int tree[N * 4]; // 线段树数组
int arr[N]; // 原数组
// 如果是求和则 tree[node] = left + right;如果是求最大值则 tree[node] = max(left, right)。
void push_up(int node) {
    tree[node] = tree[node * 2] + tree[node * 2 + 1];
}
2. 建树(Build)

node 当前线段树节点编号 (从 1 开始) start 当前节点代表区间的开始位置 end 当前节点代表区间的结束位置

void build(int node, int start, int end) {
    // 递归出口:到达叶子节点
    if (start == end) {
        tree[node] = arr[start];
        return;
    }
    int mid = (start + end) / 2;
    int left_node = node * 2;
    int right_node = node * 2 + 1;
    // 递归构建左右子树
    build(left_node, start, mid);
    build(right_node, mid + 1, end);
    // 构建完子节点后,计算当前节点的值
    push_up(node);
}
3. 单点更新(Update)

idx 要修改的原数组索引 val 要修改成的值

void update(int node, int start, int end, int idx, int val) {
    if (start == end) {
        // 找到了叶子节点,修改它
        arr[idx] = val;
        tree[node] = val;
        return;
    }
    int mid = (start + end) / 2;
    int left_node = node * 2;
    int right_node = node * 2 + 1;
    // 判断 idx 在左边还是右边
    if (idx <= mid) {
        update(left_node, start, mid, idx, val);
    } else {
        update(right_node, mid + 1, end, idx, val);
    }
    // 修改完子节点后,记得更新父节点
    push_up(node);
}
4. 区间查询(Query)

L 查询区间的左边界 R 查询区间的右边界 return 区间 [L, R] 的和

int query(int node, int start, int end, int L, int R) {
    // 情况 1: 当前节点区间完全包含在查询区间内
    // 如果当前节点区间被查询区间 [L, R] 完全覆盖,直接返回该节点存的值,不需要再往下找了
    if (L <= start && end <= R) {
        return tree[node];
    }
    int mid = (start + end) / 2;
    int left_node = node * 2;
    int right_node = node * 2 + 1;
    int sum = 0;
    // 情况 2: 查询区间有一部分在左子树
    if (L <= mid) {
        sum += query(left_node, start, mid, L, R);
    }
    // 情况 3: 查询区间有一部分在右子树
    if (R > mid) {
        sum += query(right_node, mid + 1, end, L, R);
    }
    return sum;
}

五、懒惰标记(Lazy Tag)

没有 Lazy Tag,线段树只能做'单点修改'。如果要做'区间修改',普通方法需要一个个叶子节点去改,复杂度会退化成 O(N \log N),引入 Lazy Tag 后,区间修改和区间查询的复杂度依旧能保持在 O(\log N)。

Lazy Tag 的本质是'不到万不得已,绝不干活'。当我们需要更新一个区间时,无需立即更新所有子节点,而是在当前节点打上标记,等到真正需要访问子节点时再下放标记。

1. Push Down
const int MAXN = 1000;
int tree[MAXN * 4];
int lazy[MAXN * 4]; // 增加一个 lazy[] 数组存放懒惰标记,和 tree[] 数组一一对应。
int arr[MAXN];
void push_down(int node, int start, int end) {
    // 如果当前节点没有标记,就不用下放
    if (lazy[node] == 0) return;
    int mid = (start + end) / 2;
    int left_node = node * 2;
    int right_node = node * 2 + 1;
    // 下放给左孩子
    // 左孩子的值 = 原值 + (标记值 * 左孩子区间长度)
    tree[left_node] += lazy[node] * (mid - start + 1);
    lazy[left_node] += lazy[node]; // 标记叠加
    // 下放给右孩子
    tree[right_node] += lazy[node] * (end - mid);
    lazy[right_node] += lazy[node]; // 标记叠加
    // 以此节点的任务已完成,清零标记
    lazy[node] = 0;
}
2. 区间修改 (Range Update)
void update_range(int node, int start, int end, int L, int R, int val) {
    // 情况 1: 当前区间完全被包含在修改范围内
    if (L <= start && end <= R) {
        // 直接更新当前节点的值
        tree[node] += val * (end - start + 1);
        // 打上标记
        lazy[node] += val;
        return;
    }
    // 情况 2: 区间只有一部分重叠,必须往下走,先下放之前的标记
    push_down(node, start, end);
    int mid = (start + end) / 2;
    if (L <= mid) update_range(node * 2, start, mid, L, R, val);
    if (R > mid) update_range(node * 2 + 1, mid + 1, end, L, R, val);
    // 子节点改完了,更新父节点
    push_up(node);
}
3. 区间查询 (Range Query)
int query_range(int node, int start, int end, int L, int R) {
    if (L <= start && end <= R) {
        return tree[node];
    }
    // 在分裂查询之前,须先把当前节点的标记下放
    push_down(node, start, end);
    int mid = (start + end) / 2;
    int sum = 0;
    if (L <= mid) sum += query_range(node * 2, start, mid, L, R);
    if (R > mid) sum += query_range(node * 2 + 1, mid + 1, end, L, R);
    return sum;
}

目录

  1. 一、什么是线段树?
  2. 二、线段树的优点
  3. 三、线段树的结构原理
  4. 四、线段树的基本实现(基于 C++)
  5. 1. Push Up
  6. 2. 建树(Build)
  7. 3. 单点更新(Update)
  8. 4. 区间查询(Query)
  9. 五、懒惰标记(Lazy Tag)
  10. 1. Push Down
  11. 2. 区间修改 (Range Update)
  12. 3. 区间查询 (Range Query)

更多推荐文章

查看全部
  • Go 语言开发规范与最佳实践指南
  • Spring AI Agent 开发:用 Skills 构建代码评审助手
  • Docker Compose 多实例 Tomcat 部署示例
  • C/C++ 全局变量跨文件机制:链接属性与 static 关键字
  • 神经网络 Embedding 原理及作用详解
  • PyCharm 安装 Python 模块失败?常见 pip 报错原因与解决方案
  • 基于 LangChain 快速搭建 RAG 知识库实战
  • Rust 异步并发安全与内存管理最佳实践
  • Talker-Reasoner:基于双系统架构的 AI Agent 设计
  • 基于 FastAPI 自动构建 SSE MCP 服务器
  • FPGA 摄像头采集处理显示指南:OV5640 到 HDMI 实时显示
  • Python 网络爬虫技术基础与实战
  • 链表基础概念及常用算法题解析
  • C++ STL list 容器详解:使用与模拟实现
  • PyCharm 集成 AI 插件配置指南:OpenAI 与 DeepSeek
  • Java 调用百度地图 API 实现长沙热门路段与景点实时路况检索
  • PyTorch 与 TensorFlow 环境搭建及硬件配置指南
  • Mac Mini M4 本地 AI 模型实战:从 Ollama 到 Stable Diffusion 的配置指南
  • 基于 JavaAI 辅助生成 SpringBoot 图书借阅平台实战
  • 服务器繁忙?电脑配置低?DeepSeek 满血版访问方案

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online