Part03 数据结构

CSP-S 初赛数据结构知识文档(2)

2.2.3 数据结构

1. 线性结构

双端栈 【5】
  • 特点:两个栈共享存储空间,可从两端进行push/pop操作
  • 应用:特定场景下的高效操作
cpp 复制代码
#define MAX 1000
int stack1[MAX], top1 = -1; // 左栈
int stack2[MAX], top2 = -1; // 右栈

void push1(int x) { stack1[++top1] = x; }  // 左栈入栈
void push2(int x) { stack2[++top2] = x; }  // 右栈入栈
int pop1() { return stack1[top1--]; }      // 左栈出栈
int pop2() { return stack2[top2--]; }      // 右栈出栈
双端队列 【5】
  • 特点:两端都可进行插入和删除操作
  • 时间复杂度 :所有操作 O ( 1 ) O(1) O(1)
cpp 复制代码
int dq[MAX * 2], head = MAX, tail = MAX; // 中间开始存储

void push_front(int x) { dq[--head] = x; } // 前端插入
void push_back(int x) { dq[tail++] = x; }  // 后端插入
int pop_front() { return dq[head++]; }     // 前端删除
int pop_back() { return dq[--tail]; }     // 后端删除
单调队列 【5】
  • 特点:维护队列元素的单调性
  • 应用:滑动窗口最值问题
cpp 复制代码
int nums[MAX], q[MAX]; // q存储下标
int n, k; // 数组长度和窗口大小

void maxSlidingWindow() {
    int head = 0, tail = 0;
    for (int i = 0; i < n; i++) {
        // 维护单调递减队列
        while (head < tail && nums[q[tail-1]] <= nums[i]) tail--;
        q[tail++] = i;
        // 移除窗口外的元素
        if (q[head] <= i - k) head++;
        // 输出窗口最大值
        if (i >= k - 1) printf("%d ", nums[q[head]]);
    }
}
优先队列 【6】
  • 特点:元素按优先级出队
  • 实现:通常用二叉堆实现
cpp 复制代码
int heap[MAX], size = 0; // 最小堆

void push(int x) {
    heap[++size] = x;
    // 上浮操作
    for (int i = size; i > 1 && heap[i] < heap[i/2]; i /= 2)
        swap(heap[i], heap[i/2]);
}

int pop() {
    int res = heap[1]; // 堆顶元素
    heap[1] = heap[size--];
    // 下沉操作
    for (int i = 1, j; i * 2 <= size; i = j) {
        j = i * 2;
        if (j + 1 <= size && heap[j+1] < heap[j]) j++;
        if (heap[i] <= heap[j]) break;
        swap(heap[i], heap[j]);
    }
    return res;
}
ST表(Sparse Table) 【6】
  • 特点:静态区间查询,支持幂等性操作
  • 预处理 : O ( n log ⁡ n ) O(n \log n) O(nlogn)
  • 查询 : O ( 1 ) O(1) O(1)
cpp 复制代码
int st[MAX][20], log2[MAX]; // st[i][j]表示区间[i,i+2^j-1]的最小值

void init() {
    // 预处理对数
    log2[1] = 0;
    for (int i = 2; i <= n; i++) log2[i] = log2[i/2] + 1;
    // 动态规划预处理ST表
    for (int j = 1; (1 << j) <= n; j++)
        for (int i = 1; i + (1 << j) - 1 <= n; i++)
            st[i][j] = min(st[i][j-1], st[i+(1<<(j-1))][j-1]);
}

int query(int l, int r) {
    int k = log2[r - l + 1]; // 区间长度对数
    return min(st[l][k], st[r-(1<<k)+1][k]); // 两个重叠区间取min
}

2. 集合与森林

并查集 【6】
  • 特点:高效处理不相交集合的合并与查询
  • 优化:路径压缩 + 按秩合并
cpp 复制代码
int parent[MAX], rank[MAX]; // parent存储父节点,rank存储秩

int find(int x) {
    return parent[x] == x ? x : parent[x] = find(parent[x]); // 路径压缩
}

void unite(int x, int y) {
    x = find(x); y = find(y);
    if (x == y) return;
    // 按秩合并
    if (rank[x] < rank[y]) parent[x] = y;
    else {
        parent[y] = x;
        if (rank[x] == rank[y]) rank[x]++;
    }
}
树的孩子兄弟表示法 【6】
  • 特点:用二叉链表表示多叉树
  • 优点:统一了树和二叉树的存储结构
cpp 复制代码
struct TreeNode {
    int val;
    int firstChild; // 第一个孩子节点下标
    int nextSibling; // 下一个兄弟节点下标
} tree[MAX];

3. 特殊树

二叉堆 【6】
  • 特点:完全二叉树,满足堆性质
  • 操作 :插入 O ( log ⁡ n ) O(\log n) O(logn),删除 O ( log ⁡ n ) O(\log n) O(logn),取最值 O ( 1 ) O(1) O(1)
cpp 复制代码
// 实现同优先队列
树状数组 【6】
  • 特点:高效处理前缀和与单点更新
  • 应用:逆序对计数等
cpp 复制代码
int tree[MAX], n; // tree数组维护前缀和

int lowbit(int x) { return x & -x; } // 获取最低位的1

void update(int i, int delta) {
    while (i <= n) {
        tree[i] += delta;
        i += lowbit(i); // 向上更新父节点
    }
}

int query(int i) {
    int res = 0;
    while (i > 0) {
        res += tree[i];
        i -= lowbit(i); // 向前查询前驱
    }
    return res;
}
线段树 【6】
  • 特点:完全二叉树结构,支持区间查询和更新
  • 空间 : O ( 4 n ) O(4n) O(4n)
cpp 复制代码
int arr[MAX], tree[MAX * 4]; // tree数组存储线段树

// 建树
void build(int node, int start, int end) {
    if (start == end) {
        tree[node] = arr[start]; // 叶子节点
    } else {
        int mid = (start + end) / 2;
        build(2*node, start, mid);   // 左子树
        build(2*node+1, mid+1, end); // 右子树
        tree[node] = tree[2*node] + tree[2*node+1]; // 合并子节点
    }
}

// 单点修改
void modify(int node, int start, int end, int idx, int val) {
    if (start == end) {
        arr[idx] = tree[node] = val; // 找到目标位置
    } else {
        int mid = (start + end) / 2;
        if (idx <= mid) modify(2*node, start, mid, idx, val);
        else modify(2*node+1, mid+1, end, idx, val);
        tree[node] = tree[2*node] + tree[2*node+1]; // 更新父节点
    }
}

// 区间查询
int query(int node, int start, int end, int l, int r) {
    if (r < start || end < l) return 0; // 区间无交集
    if (l <= start && end <= r) return tree[node]; // 完全包含
    int mid = (start + end) / 2;
    return query(2*node, start, mid, l, r) +  // 查询左子树
           query(2*node+1, mid+1, end, l, r); // 查询右子树
}
字典树(Trie树) 【6】
  • 特点:多叉树结构,高效存储和查询字符串
  • 应用:前缀匹配、词频统计
cpp 复制代码
int trie[MAX][26], cnt = 1; // trie树节点
bool isEnd[MAX]; // 标记单词结束

void insert(char *s) {
    int p = 0;
    for (int i = 0; s[i]; i++) {
        int c = s[i] - 'a';
        if (!trie[p][c]) trie[p][c] = cnt++; // 新建节点
        p = trie[p][c];
    }
    isEnd[p] = true; // 标记单词结束
}

bool search(char *s) {
    int p = 0;
    for (int i = 0; s[i]; i++) {
        int c = s[i] - 'a';
        if (!trie[p][c]) return false; // 不存在该字符
        p = trie[p][c];
    }
    return isEnd[p]; // 检查是否是完整单词
}
笛卡尔树 【7】
  • 特点:中序遍历为原序列,满足堆性质
  • 构建 : O ( n ) O(n) O(n),使用单调栈
cpp 复制代码
int stk[MAX], top = 0;
struct Node { int val, l, r; } tree[MAX];

void build(int *a, int n) {
    for (int i = 0; i < n; i++) {
        int k = top;
        // 维护单调栈
        while (k > 0 && a[stk[k-1]] > a[i]) k--;
        // 设置父节点关系
        if (k > 0) tree[stk[k-1]].r = i;
        if (k < top) tree[i].l = stk[k];
        stk[top = k++] = i; // 压入栈
    }
}
平衡树 【8】
  • 特点:保持树平衡,保证操作效率
  • 类型:AVL、Treap、Splay等
cpp 复制代码
// Treap实现(树堆)
struct Node {
    int key, pri, size; // key为键值,pri为随机优先级
    int l, r; // 左右子节点
} nodes[MAX];
int root, cnt; // 根节点和节点计数

// 更新节点大小
void update(int p) {
    nodes[p].size = nodes[nodes[p].l].size + nodes[nodes[p].r].size + 1;
}

// 按key分裂
void split(int p, int key, int &x, int &y) {
    if (!p) { x = y = 0; return; }
    if (nodes[p].key <= key) {
        x = p;
        split(nodes[p].r, key, nodes[p].r, y);
    } else {
        y = p;
        split(nodes[p].l, key, x, nodes[p].l);
    }
    update(p);
}

// 合并两个Treap
int merge(int x, int y) {
    if (!x || !y) return x | y;
    // 按优先级合并
    if (nodes[x].pri > nodes[y].pri) {
        nodes[x].r = merge(nodes[x].r, y);
        update(x);
        return x;
    } else {
        nodes[y].l = merge(x, nodes[y].l);
        update(y);
        return y;
    }
}

4. 常见图

稀疏图 【5】
  • 特点:边数远少于完全图
  • 表示:邻接表更节省空间
cpp 复制代码
int head[MAX], to[MAX], nxt[MAX], cnt = 1; // 邻接表

void addEdge(int u, int v) {
    to[cnt] = v;
    nxt[cnt] = head[u]; // 头插法
    head[u] = cnt++;
}
偶图(二分图) 【6】
  • 特点:顶点可分为两个不相交集合
  • 判定:二分图染色法
cpp 复制代码
int color[MAX]; // 0和1表示两种颜色

bool isBipartite(int u) {
    for (int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        if (color[v] == -1) {
            color[v] = color[u] ^ 1; // 染相反颜色
            if (!isBipartite(v)) return false;
        } else if (color[v] == color[u]) {
            return false; // 颜色冲突
        }
    }
    return true;
}
欧拉图 【6】
  • 特点:包含欧拉回路或欧拉路径
  • 判定
    • 欧拉回路:所有顶点度数为偶
    • 欧拉路径:恰好两个顶点度数为奇
cpp 复制代码
int degree[MAX]; // 记录每个顶点的度数

bool isEulerian() {
    int odd = 0;
    for (int i = 1; i <= n; i++)
        if (degree[i] % 2) odd++;
    return odd == 0 || odd == 2;
}
有向无环图 【6】
  • 特点:无环,可拓扑排序
  • 应用:任务调度、依赖关系
cpp 复制代码
// 拓扑排序 (见后续实现)
连通图与强连通图 【7】
  • 强连通分量:任意两点互相可达的最大子图
  • 算法:Kosaraju、Tarjan
cpp 复制代码
// Kosaraju算法
int vis[MAX], scc[MAX], scnt; // scc记录强连通分量编号

void dfs1(int u) {
    vis[u] = 1;
    for (int i = head[u]; i; i = nxt[i])
        if (!vis[to[i]]) dfs1(to[i]);
    stk[++top] = u; // 按结束时间入栈
}

void dfs2(int u) {
    scc[u] = scnt; // 标记强连通分量
    for (int i = rhead[u]; i; i = rnxt[i])
        if (!scc[rto[i]]) dfs2(rto[i]);
}

void kosaraju() {
    // 第一次DFS确定结束顺序
    for (int i = 1; i <= n; i++)
        if (!vis[i]) dfs1(i);
    // 第二次DFS在逆图上处理
    while (top) {
        int u = stk[top--];
        if (!scc[u]) { scnt++; dfs2(u); }
    }
}
双连通图 【7】
  • 割点:删除后图连通分量增加
  • 算法:Tarjan算法
cpp 复制代码
int dfn[MAX], low[MAX], idx;
bool cut[MAX]; // 标记割点

void tarjan(int u, int fa) {
    dfn[u] = low[u] = ++idx;
    int child = 0;
    for (int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        if (!dfn[v]) {
            child++;
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            // 判断割点
            if (low[v] >= dfn[u]) cut[u] = true;
        } else if (v != fa) {
            low[u] = min(low[u], dfn[v]);
        }
    }
    // 根节点特殊处理
    if (fa == 0 && child == 1) cut[u] = false;
}

5. 哈希表

数值哈希函数构造 【5】
  • 常用方法
    • 除法哈希: h ( k ) = k m o d    m h(k) = k \mod m h(k)=kmodm
    • 乘法哈希: h ( k ) = ⌊ m ( k A m o d    1 ) ⌋ h(k) = \lfloor m(kA \mod 1) \rfloor h(k)=⌊m(kAmod1)⌋, A ≈ ( 5 − 1 ) / 2 A \approx (\sqrt{5}-1)/2 A≈(5 −1)/2
cpp 复制代码
int hash(int key) {
    return (key % MOD + MOD) % MOD; // 处理负数
}
字符串哈希函数构造 【6】
  • 多项式哈希

H ( S ) = ∑ i = 0 n − 1 s [ i ] ⋅ p i m o d    m H(S) = \sum_{i=0}^{n-1} s[i] \cdot p^i \mod m H(S)=i=0∑n−1s[i]⋅pimodm

cpp 复制代码
#define BASE 131
#define MOD 1000000007
char s[MAX];
int h[MAX], p[MAX]; // h存储哈希值,p存储BASE的幂

void init() {
    p[0] = 1;
    h[0] = s[0];
    for (int i = 1; s[i]; i++) {
        p[i] = (long long)p[i-1] * BASE % MOD;
        h[i] = ((long long)h[i-1] * BASE + s[i]) % MOD;
    }
}

int getHash(int l, int r) {
    if (l == 0) return h[r];
    // 计算子串哈希
    return ((h[r] - (long long)h[l-1] * p[r-l+1] % MOD) + MOD) % MOD;
}
哈希冲突的常用处理方法 【6】
  1. 链地址法:冲突元素组成链表
  2. 开放定址法:线性探测、平方探测等
cpp 复制代码
// 链地址法
struct Node {
    int key, val;
    Node *next;
} *hashTable[MOD];

void insert(int key, int val) {
    int idx = hash(key);
    Node *p = new Node{key, val, hashTable[idx]};
    hashTable[idx] = p;
}

int find(int key) {
    int idx = hash(key);
    for (Node *p = hashTable[idx]; p; p = p->next)
        if (p->key == key) return p->val;
    return -1;
}

// 开放定址法
int hashTable[MOD];

int find(int key) {
    int idx = hash(key);
    while (hashTable[idx] != -1 && hashTable[idx] != key)
        idx = (idx + 1) % MOD; // 线性探测
    return idx;
}

复杂度总结

数据结构 查询 插入 删除 空间
树状数组 O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn) - O ( n ) O(n) O(n)
线段树 O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn) O ( n ) O(n) O(n)
并查集 O ( α ( n ) ) O(\alpha(n)) O(α(n)) O ( α ( n ) ) O(\alpha(n)) O(α(n)) O ( α ( n ) ) O(\alpha(n)) O(α(n)) O ( n ) O(n) O(n)
哈希表 O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1) O ( n ) O(n) O(n)
平衡树 O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn) O ( n ) O(n) O(n)

注: α ( n ) \alpha(n) α(n)为反阿克曼函数,通常小于5

CSP-S 提高组初赛复习大纲

更新时间:2025 年 9 月 18 日 23:21:08

相关推荐
Roc-xb5 小时前
VSCode c/c++头文件函数点击无法跳转问题
c语言·c++·vscode
磨十三5 小时前
C++ 中的 static 关键字:类成员、局部变量与单例模式
开发语言·c++·单例模式
小欣加油6 小时前
leetcode 206 反转链表
数据结构·c++·算法·leetcode·链表·职场和发展
野犬寒鸦6 小时前
力扣hot100:环形链表II(哈希算法与快慢指针法思路讲解)
java·数据结构·算法·leetcode·链表·哈希算法
zstar-_6 小时前
C++真的比Python更快吗?
开发语言·c++·python
j_xxx404_7 小时前
C++:入门基础(2)
开发语言·c++
yuec7 小时前
iOS 系统获取 C++ 崩溃堆栈 - 撒花完结篇
c++·ios
yolo_guo7 小时前
glog使用: 07-错误信号处理(Failure Signal Handler)
linux·c++·glog
林文韬3277 小时前
C、C++、Java 和 Python:四大编程语言的对比分析
java·c语言·c++·python