数据结构

一、线性数据结构

1.1 栈

基本概念

栈是一种后进先出(LIFO)的数据结构,支持在栈顶进行插入和删除操作。

基本操作

  • push(x):将元素 \(x\) 压入栈顶
  • pop():弹出栈顶元素
  • top():返回栈顶元素
  • empty():判断栈是否为空

时间复杂度

所有操作均为 \(O(1)\)

应用场景

  1. 表达式求值(中缀转后缀)
  2. 括号匹配
  3. 函数调用栈
  4. 浏览器前进后退

例题1.1:括号匹配

问题描述:给定一个只包含 '('、')'、'['、']'、'{'、'}' 的字符串,判断字符串是否有效。

输入格式:一个字符串 \(s\),长度不超过 \(10^5\)

输出格式:如果字符串有效,输出 "YES",否则输出 "NO"。

解题思路

  • 遍历字符串,遇到左括号则入栈
  • 遇到右括号时,检查栈顶是否匹配
  • 最后检查栈是否为空
  • 时间复杂度:\(O(n)\)

例题1.2:直方图中最大矩形

问题描述:给定 \(n\) 个非负整数,表示直方图中每个柱子的高度。每个柱子的宽度为 1。求直方图中最大矩形的面积。

输入格式

  • 第一行整数 \(n\)
  • 第二行 \(n\) 个整数 \(h_1, h_2, ..., h_n\)

输出格式:一个整数,表示最大矩形面积。

解题思路

  • 使用单调栈维护递增的高度
  • 对于每个柱子,找到它左边和右边第一个比它矮的柱子
  • 以当前柱子为高的最大矩形宽度为右边界-左边界-1
  • 时间复杂度:\(O(n)\)

1.2 队列

基本概念

队列是一种先进先出(FIFO)的数据结构,支持在队尾插入,队头删除。

基本操作

  • push(x):将元素 \(x\) 插入队尾
  • pop():删除队头元素
  • front():返回队头元素
  • empty():判断队列是否为空

时间复杂度

所有操作均为 \(O(1)\)

应用场景

  1. BFS遍历
  2. 缓存管理
  3. 任务调度
  4. 滑动窗口

例题1.3:滑动窗口最大值

问题描述:给定一个数组 \(nums\) 和滑动窗口的大小 \(k\),窗口从最左边滑动到最右边,每次向右滑动一个位置。求每个窗口中的最大值。

输入格式

  • 第一行两个整数 \(n, k\)
  • 第二行 \(n\) 个整数 \(nums_1, nums_2, ..., nums_n\)

输出格式\(n-k+1\) 个整数,表示每个窗口的最大值。

解题思路

  • 使用双端队列维护递减序列
  • 队列中存储下标,保证队头元素在窗口内
  • 每次窗口滑动,移除队头过期元素,加入新元素时移除比它小的元素
  • 时间复杂度:\(O(n)\)

1.3 链表

基本概念

链表是由节点组成的数据结构,每个节点包含数据和指向下一个节点的指针。

类型

  1. 单向链表
  2. 双向链表
  3. 循环链表

基本操作

  • 插入:\(O(1)\)(已知位置)
  • 删除:\(O(1)\)(已知位置)
  • 查找:\(O(n)\)

例题1.4:LRU缓存

问题描述:设计并实现一个 LRU(最近最少使用)缓存机制。

操作要求

  • get(key):如果密钥存在则获取值,否则返回 -1
  • put(key, value):如果密钥不存在则插入,当缓存达到容量时删除最久未使用的数据

解题思路

  • 使用哈希表+双向链表
  • 哈希表实现 \(O(1)\) 查找
  • 双向链表维护访问顺序
  • 最近访问的节点移到链表头部
  • 时间复杂度:所有操作 \(O(1)\)

二、树形数据结构

2.1 二叉树

基本概念

二叉树是每个节点最多有两个子树的树结构,子树有左右之分。

遍历方式

  1. 前序遍历:根→左→右
  2. 中序遍历:左→根→右
  3. 后序遍历:左→右→根
  4. 层序遍历:按层遍历

特殊二叉树

  1. 完全二叉树
  2. 满二叉树
  3. 二叉搜索树(BST)
  4. 平衡二叉树(AVL)

例题2.1:二叉搜索树操作

问题描述:实现二叉搜索树的插入、删除、查找操作。

操作要求

  • 插入:插入新节点,保持BST性质
  • 删除:删除指定节点,保持BST性质
  • 查找:查找指定值是否存在
  • 查找第k小元素

解题思路

  • 插入:递归找到合适位置插入
  • 删除:分三种情况处理(无子节点、一个子节点、两个子节点)
  • 查找:二分查找
  • 第k小:中序遍历计数
  • 时间复杂度:平均 \(O(\log n)\),最坏 \(O(n)\)

2.2 堆

基本概念

堆是一种完全二叉树,满足堆性质:

  • 最大堆:父节点值 ≥ 子节点值
  • 最小堆:父节点值 ≤ 子节点值

基本操作

  • push(x):插入元素,\(O(\log n)\)
  • pop():删除堆顶,\(O(\log n)\)
  • top():获取堆顶,\(O(1)\)
  • empty():判断是否为空,\(O(1)\)

应用场景

  1. 优先队列
  2. 堆排序
  3. Top-K问题
  4. Dijkstra算法

例题2.2:合并果子

问题描述:有 \(n\) 堆果子,每堆果子重量为 \(a_i\)。每次可以合并任意两堆果子,消耗体力为两堆果子重量之和。求将所有果子合并成一堆的最小体力消耗。

输入格式

  • 第一行整数 \(n\)
  • 第二行 \(n\) 个整数 \(a_1, a_2, ..., a_n\)

输出格式:一个整数,表示最小体力消耗。

解题思路

  • 使用最小堆(优先队列)
  • 每次取出最小的两堆合并,将合并后的结果放回堆中
  • 重复直到只剩一堆
  • 时间复杂度:\(O(n \log n)\)

2.3 并查集

基本概念

并查集用于处理不相交集合的合并与查询问题。

基本操作

  • find(x):查找元素所在集合的代表元素
  • union(x, y):合并两个元素所在集合
  • 路径压缩:使树更扁平,提高查找效率
  • 按秩合并:小树合并到大树

时间复杂度

  • 平均:近似 \(O(1)\)
  • 最坏:\(O(\alpha(n))\),其中 \(\alpha(n)\) 是反阿克曼函数

应用场景

  1. 连通分量
  2. Kruskal算法
  3. 动态连通性
  4. 离线查询

例题2.3:朋友圈

问题描述:有 \(n\) 个人,给定 \(m\) 个朋友关系。朋友关系具有传递性。求朋友圈的数量。

输入格式

  • 第一行两个整数 \(n, m\)
  • 接下来 \(m\) 行,每行两个整数 \(u, v\) 表示 \(u\)\(v\) 是朋友

输出格式:一个整数,表示朋友圈数量。

解题思路

  • 使用并查集维护朋友关系
  • 初始时每个人都是一个独立集合
  • 对于每个朋友关系,合并两人所在集合
  • 最后统计不同集合的数量
  • 时间复杂度:\(O(m \alpha(n))\)

三、高级树形结构

3.1 线段树

基本概念

线段树是一种二叉树,每个节点代表一个区间,用于处理区间查询和更新问题。

支持操作

  1. 单点修改:\(O(\log n)\)
  2. 区间查询:\(O(\log n)\)
  3. 区间修改:\(O(\log n)\)(需要懒标记)

结构特点

  • 叶子节点:长度为1的区间
  • 内部节点:左右子区间的并集
  • 完全二叉树结构

例题3.1:区间最大值

问题描述:给定 \(n\) 个数的序列,支持两种操作:

  1. 将第 \(i\) 个数修改为 \(x\)
  2. 查询区间 \([l, r]\) 的最大值

输入格式

  • 第一行两个整数 \(n, m\)
  • 第二行 \(n\) 个整数
  • 接下来 \(m\) 行,每行表示一个操作

输出格式:对于每个查询操作,输出结果。

解题思路

  • 建立线段树,每个节点存储区间最大值
  • 单点修改:更新叶子节点,向上更新父节点
  • 区间查询:递归查询,合并左右子树结果
  • 时间复杂度:每次操作 \(O(\log n)\)

例题3.2:区间加与区间求和

问题描述:给定 \(n\) 个数的序列,支持两种操作:

  1. 区间 \([l, r]\) 中每个数加上 \(x\)
  2. 查询区间 \([l, r]\) 的和

解题思路

  • 使用带懒标记的线段树
  • 懒标记记录区间需要加的值,不下传到叶子节点
  • 查询和更新时根据懒标记进行计算
  • 时间复杂度:每次操作 \(O(\log n)\)

3.2 树状数组

基本概念

树状数组(Fenwick Tree)是一种支持单点修改和前缀查询的数据结构。

核心操作

  • update(i, x):将第 \(i\) 个元素加上 \(x\)
  • query(i):查询前 \(i\) 个元素的和

特点

  • 代码简洁
  • 常数小
  • 空间复杂度 \(O(n)\)
  • 不支持区间修改(可通过差分支持)

时间复杂度

所有操作 \(O(\log n)\)

例题3.3:逆序对数量

问题描述:给定一个整数序列,求逆序对的数量。

输入格式

  • 第一行整数 \(n\)
  • 第二行 \(n\) 个整数

输出格式:一个整数,表示逆序对数量。

解题思路

  1. 离散化:将原数组映射到 \([1, n]\) 的区间
  2. 从后往前遍历,用树状数组统计每个数后面有多少个比它小的数
  3. 时间复杂度:\(O(n \log n)\)

3.3 平衡树

基本概念

平衡树是一种自平衡的二叉搜索树,保证树的高度为 \(O(\log n)\)

常见类型

  1. AVL树:严格平衡,旋转操作多
  2. 红黑树:近似平衡,工程常用
  3. Treap:随机平衡,实现简单
  4. Splay:通过伸展操作平衡
  5. 替罪羊树:基于重构的平衡树

支持操作

  • 插入、删除、查找:\(O(\log n)\)
  • 前驱、后继:\(O(\log n)\)
  • 第k大:\(O(\log n)\)
  • 排名:\(O(\log n)\)

例题3.4:普通平衡树

问题描述:实现一种数据结构,支持以下操作:

  1. 插入 \(x\)
  2. 删除 \(x\)
  3. 查询 \(x\) 的排名
  4. 查询排名为 \(k\) 的数
  5. \(x\) 的前驱
  6. \(x\) 的后继

解题思路

  • 使用Treap或Splay实现
  • 每个节点存储值、子树大小、随机优先级
  • 通过旋转维护平衡性
  • 时间复杂度:所有操作 \(O(\log n)\)

四、字符串数据结构

4.1 Trie树

基本概念

Trie树(字典树)是一种用于字符串检索的多叉树结构。

特点

  • 根节点不包含字符
  • 从根到某一节点的路径表示一个字符串
  • 节点存储额外信息(如出现次数、是否为单词结尾)

应用场景

  1. 字符串检索
  2. 前缀匹配
  3. 自动补全
  4. 词频统计

例题4.1:最大异或对

问题描述:给定 \(n\) 个整数 \(a_1, a_2, ..., a_n\),求两个数异或的最大值。

输入格式

  • 第一行整数 \(n\)
  • 第二行 \(n\) 个整数

输出格式:一个整数,表示最大异或值。

解题思路

  1. 将每个数看作32位二进制串
  2. 建立二进制Trie树
  3. 对于每个数,在Trie中找使异或值最大的路径
  4. 时间复杂度:\(O(n \log C)\),其中 \(C\) 是值域

4.2 后缀数组

基本概念

后缀数组是对字符串所有后缀进行排序后得到的数组。

核心概念

  • \(SA[i]\):排名第 \(i\) 的后缀起始位置
  • \(rank[i]\):以 \(i\) 开头的后缀的排名
  • \(height[i]\)\(SA[i]\)\(SA[i-1]\) 的最长公共前缀

应用场景

  1. 最长重复子串
  2. 不同子串数量
  3. 字符串匹配
  4. 回文子串

例题4.2:不同子串个数

问题描述:给定一个字符串,求所有不同子串的个数。

输入格式:一个字符串 \(s\)

输出格式:一个整数,表示不同子串个数。

解题思路

  1. 构建后缀数组和height数组
  2. 所有子串个数为 \(\frac{n(n+1)}{2}\)
  3. 重复子串个数为 \(\sum_{i=1}^n height[i]\)
  4. 不同子串个数 = 总子串数 - 重复子串数
  5. 时间复杂度:\(O(n \log n)\)

4.3 自动机

类型

  1. KMP自动机:单模式匹配
  2. AC自动机:多模式匹配
  3. 后缀自动机:子串相关问题

例题4.3:关键词搜索

问题描述:给定一个文本串 \(T\)\(n\) 个模式串 \(P_i\),求每个模式串在文本串中出现的次数。

输入格式

  • 第一行文本串 \(T\)
  • 第二行整数 \(n\)
  • 接下来 \(n\) 行,每行一个模式串

输出格式\(n\) 行,每行一个整数表示出现次数。

解题思路

  1. 构建AC自动机
  2. 将模式串插入Trie树,构建失败指针
  3. 在文本串上匹配,统计出现次数
  4. 时间复杂度:\(O(\sum |P_i| + |T|)\)

五、可持久化数据结构

5.1 可持久化线段树

基本概念

可持久化线段树(主席树)可以访问所有历史版本,每次修改创建新节点。

特点

  • 空间复杂度:\(O(n + m \log n)\)
  • 每次修改只创建 \(O(\log n)\) 个新节点
  • 支持区间第k大查询

例题5.1:区间第k小

问题描述:给定 \(n\) 个数的序列,\(m\) 次询问,每次询问区间 \([l, r]\) 中第 \(k\) 小的数。

输入格式

  • 第一行两个整数 \(n, m\)
  • 第二行 \(n\) 个整数
  • 接下来 \(m\) 行,每行三个整数 \(l, r, k\)

输出格式:对于每个询问,输出结果。

解题思路

  1. 离散化原数组
  2. 建立权值线段树的可持久化版本
  3. \(i\) 个版本表示前 \(i\) 个数建立的线段树
  4. 区间 \([l, r]\) 的信息 = 版本 \(r\) - 版本 \(l-1\)
  5. 在权值线段树上二分查找第k小
  6. 时间复杂度:\(O((n+m) \log n)\)

5.2 可持久化Trie

应用场景

  • 区间最大异或
  • 区间mex查询
  • 可持久化字典

例题5.2:区间最大异或

问题描述:给定 \(n\) 个数的序列,\(m\) 次询问,每次询问区间 \([l, r]\) 中选一个数与 \(x\) 异或的最大值。

输入格式

  • 第一行两个整数 \(n, m\)
  • 第二行 \(n\) 个整数
  • 接下来 \(m\) 行,每行三个整数 \(l, r, x\)

输出格式:对于每个询问,输出结果。

解题思路

  1. 建立可持久化Trie,第 \(i\) 个版本表示前 \(i\) 个数
  2. 对于查询 \([l, r]\),使用版本 \(r\) 减去版本 \(l-1\) 得到区间Trie
  3. 在区间Trie中找与 \(x\) 异或最大的数
  4. 时间复杂度:\(O((n+m) \log C)\)

六、特殊数据结构

6.1 单调栈/队列

基本概念

栈/队列中元素保持单调性,用于解决滑动窗口、下一个更大元素等问题。

例题6.1:接雨水

问题描述:给定 \(n\) 个非负整数表示每个柱子的高度,计算按此排列的柱子能接多少雨水。

输入格式\(n\) 个整数

输出格式:一个整数,表示雨水总量。

解题思路

  1. 单调栈法:栈中存储递减的高度
  2. 当当前高度大于栈顶时,可以形成凹槽接水
  3. 计算凹槽宽度和高度,累加雨水
  4. 时间复杂度:\(O(n)\)

6.2 分块

基本概念

将序列分成若干块,块内暴力,块间预处理,平衡复杂度。

特点

  • 简单易实现
  • 灵活性高
  • 时间复杂度:\(O(\sqrt{n})\) 级别

例题6.2:区间加法与区间求和

问题描述:给定 \(n\) 个数的序列,支持区间加法和区间求和。

解题思路

  1. 将序列分成 \(\sqrt{n}\) 大小的块
  2. 每个块维护块内和、加法标记
  3. 区间操作:
    • 完整块:更新标记和块内和
    • 不完整块:暴力更新
  4. 查询时考虑标记
  5. 时间复杂度:\(O(\sqrt{n})\) 每次操作

6.3 树套树

基本概念

在树状数组或线段树的每个节点上再建立一棵树,用于解决二维问题。

常见类型

  1. 树状数组套线段树
  2. 线段树套平衡树
  3. 二维线段树

例题6.3:二维数点

问题描述:平面上有 \(n\) 个点,\(m\) 次询问,每次询问矩形内点的数量。

输入格式

  • 第一行两个整数 \(n, m\)
  • 接下来 \(n\) 行,每行两个整数 \((x, y)\) 表示点坐标
  • 接下来 \(m\) 行,每行四个整数 \((x_1, y_1, x_2, y_2)\) 表示矩形

输出格式:对于每个询问,输出结果。

解题思路

  1. 离散化 \(y\) 坐标
  2. 建立树状数组套线段树
  3. 树状数组维护 \(x\) 维度,每个节点维护 \(y\) 维度的线段树
  4. 查询时在树状数组上求前缀和
  5. 时间复杂度:\(O((n+m) \log^2 n)\)

总结

数据结构选择指南

问题类型 推荐数据结构 时间复杂度
单点修改,区间查询 树状数组/线段树 \(O(\log n)\)
区间修改,区间查询 线段树(带懒标记) \(O(\log n)\)
第k大查询 平衡树/主席树 \(O(\log n)\)
字符串匹配 AC自动机/KMP \(O(n+m)\)
区间最值 线段树/ST表 \(O(\log n)/O(1)\)
离线查询 莫队算法 \(O(n\sqrt{n})\)
二维问题 树套树/二维树状数组 \(O(\log^2 n)\)

学习建议

  1. 掌握基础:先熟练掌握数组、链表、栈、队列等基础结构
  2. 理解原理:理解每个数据结构的设计思想和适用场景
  3. 熟练模板:掌握线段树、树状数组、平衡树等常用数据结构的实现
  4. 灵活应用:学会根据问题特点选择合适的数据结构
  5. 进阶技巧:学习可持久化、树套树、分块等高级技术

常见考点

  1. 数据结构的选择与组合
  2. 空间复杂度的优化
  3. 时间复杂度的分析
  4. 边界条件的处理
  5. 代码实现的细节

数据结构是OI竞赛的核心内容,需要大量的练习来掌握。建议从基础题开始,逐步提高难度,最终能够解决复杂的数据结构问题。

posted @ 2026-01-12 17:27  Chestify  阅读(0)  评论(0)    收藏  举报