Compare commits
7 Commits
9bdb691602
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92e696e6bc | ||
|
|
4f44ab5228 | ||
|
|
1e1468f5b1 | ||
|
|
2f969e27d1 | ||
|
|
3d393f0ed0 | ||
|
|
3c5d314e48 | ||
|
|
ec53ee7cab |
109
Algorithm/DivideOnTree/staticDivide.cpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// 建议使用 N 定义数组,比 vector 稍快且容易管理
|
||||||
|
const int N = 100005;
|
||||||
|
vector<pair<int, int>> adj[N];
|
||||||
|
bool vis[N]; // 分治标记:记录哪些点已经作为重心被“删掉”了
|
||||||
|
int treesize[N];
|
||||||
|
int root, min_maxpart, K;
|
||||||
|
long long ans = 0;
|
||||||
|
|
||||||
|
// 存储当前子树所有节点到重心的距离
|
||||||
|
vector<int> dists;
|
||||||
|
|
||||||
|
// 1. 找重心:注意这里不再需要 visited 数组,用 fa 限制方向即可
|
||||||
|
void get_root(int u, int fa, int currsize) {
|
||||||
|
treesize[u] = 1;
|
||||||
|
int maxpart = 0;
|
||||||
|
for (auto& edge : adj[u]) {
|
||||||
|
int v = edge.first;
|
||||||
|
if (v == fa || vis[v]) continue; // 关键:不能走已经“删掉”的点
|
||||||
|
get_root(v, u, currsize);
|
||||||
|
treesize[u] += treesize[v];
|
||||||
|
maxpart = max(maxpart, treesize[v]);
|
||||||
|
}
|
||||||
|
maxpart = max(maxpart, currsize - treesize[u]);
|
||||||
|
if (maxpart < min_maxpart) {
|
||||||
|
min_maxpart = maxpart;
|
||||||
|
root = u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 收集距离:把所有节点到重心的距离存入 dists 数组
|
||||||
|
void get_dists(int u, int fa, int d) {
|
||||||
|
dists.push_back(d);
|
||||||
|
for (auto& edge : adj[u]) {
|
||||||
|
int v = edge.first;
|
||||||
|
int w = edge.second;
|
||||||
|
if (v == fa || vis[v]) continue;
|
||||||
|
get_dists(v, u, d + w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 计算贡献:双指针法统计满足 dist1 + dist2 <= K 的对数
|
||||||
|
long long calc(int u, int init_dist) {
|
||||||
|
dists.clear();
|
||||||
|
get_dists(u, -1, init_dist);
|
||||||
|
sort(dists.begin(), dists.end());
|
||||||
|
|
||||||
|
long long count = 0;
|
||||||
|
int l = 0, r = dists.size() - 1;
|
||||||
|
while (l < r) {
|
||||||
|
if (dists[l] + dists[r] <= K) {
|
||||||
|
count += (r - l);
|
||||||
|
l++;
|
||||||
|
} else {
|
||||||
|
r--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 点分治主函数
|
||||||
|
void divide(int u, int currsize) {
|
||||||
|
// 每次进入先找当前连通块的重心
|
||||||
|
min_maxpart = currsize + 7; // 初始化为一个大值
|
||||||
|
get_root(u, -1, currsize);
|
||||||
|
|
||||||
|
int r = root; // 锁定重心
|
||||||
|
vis[r] = true; // 【关键】逻辑切断重心
|
||||||
|
|
||||||
|
// 统计经过重心的路径
|
||||||
|
ans += calc(r, 0);
|
||||||
|
|
||||||
|
// 递归子树
|
||||||
|
for (auto& edge : adj[r]) {
|
||||||
|
int v = edge.first;
|
||||||
|
int w = edge.second;
|
||||||
|
if (vis[v]) continue;
|
||||||
|
|
||||||
|
// 【关键】去重:减去在同一个儿子子树内提前满足条件的路径
|
||||||
|
ans -= calc(v, w);
|
||||||
|
|
||||||
|
// 这里的 currsize 要更新为子树的实际大小
|
||||||
|
// 注意:由于已经跑过 get_root,treesize[v] 此时就是正确的
|
||||||
|
// 但如果 v 是 r 的“上方”节点,size 应该是 currsize - treesize[r]
|
||||||
|
int next_size = (treesize[v] < treesize[r]) ? treesize[v] : (currsize - treesize[r]);
|
||||||
|
divide(v, next_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int n;
|
||||||
|
if (!(cin >> n >> K)) return 0;
|
||||||
|
for (int i = 0; i < n - 1; i++) {
|
||||||
|
int u, v, w;
|
||||||
|
cin >> u >> v >> w;
|
||||||
|
adj[u].push_back({v, w});
|
||||||
|
adj[v].push_back({u, w});
|
||||||
|
}
|
||||||
|
|
||||||
|
divide(1, n); // 从节点1开始分治,总大小为 n
|
||||||
|
|
||||||
|
cout << ans << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
121
Algorithm/FFT/readme.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# FFT快速傅里叶变换原理
|
||||||
|
|
||||||
|
## - 函数理论分析
|
||||||
|
|
||||||
|
对于一个多项式,设该多项式为:
|
||||||
|
$$A(x) = \Sigma_{i=0}^{n-1}a_ix_i = a_0 + a_1x + a_2x^2 + \mathellipsis$$
|
||||||
|
|
||||||
|
按A(x)的下标奇偶性分类:
|
||||||
|
$$A(x) = (a_0 + a_2x^{2} + \mathellipsis + a_{n-2}x^{n-2}) + (a_1 + a_3x^3 + \mathellipsis + a_{n-1}x^{n-1})$$ $$= (a_0 + a_2x^2 + \mathellipsis + a_{n-2}x^{n-2}) + x(a_1 + a_3x^2 + \mathellipsis + a_{n-1}x^{n-2})$$
|
||||||
|
|
||||||
|
观察式子两边发现极为相似,设两个函数$A_e$, $A_o$,令
|
||||||
|
$$A_e(x) = a_0 + a_2x^{\frac{2}{2}} + \mathellipsis + a_{n-2}x^\frac{n-2}{2} = a_0 + a_2x^{1} + \mathellipsis + a_{n-2}x^{\frac{n}{2}-1} $$ $$A_o(x) = a_1 + a_3x^\frac{2}{2} + \mathellipsis + a_{n-1}x^\frac{n-2}{2} = a_1 + a_3x^1 + \mathellipsis + a_{n-1}x^{\frac{n}{2}-1}$$
|
||||||
|
显然有
|
||||||
|
$$A(x) = A_e(x^2) + xA_o(x^2)$$ $$A(-x) = A_e(x^2) - xA_o(x^2)$$
|
||||||
|
|
||||||
|
> 请注意上式右侧的平方
|
||||||
|
|
||||||
|
带入$x=\omega_n^k$
|
||||||
|
$$A(\omega_n^k) = A_e(\omega_n^{2k}) + \omega_n^k A_o(\omega_n^{2k})$$ $$=A_e(\omega_\frac{n}{2}^{k}) + \omega_n^k A_o(\omega_\frac{n}{2}^{k})$$ $$A(-\omega_n^k) = A(\omega_n^{k+\frac{n}{2}}) = A_e(\omega_n^{2k+n}) - \omega_n^k A_o(\omega_n^{2k+n})$$ $$=A_e(\omega_n^{2k}) - \omega_n^k A_o(\omega_n^{2k})$$ $$=A_e(\omega_\frac{n}{2}^{k}) - \omega_n^k A_o(\omega_\frac{n}{2}^{k})$$
|
||||||
|
|
||||||
|
>利用性质如下
|
||||||
|
> - $-\omega_n^k = \omega_n^{k+\frac{n}{2}}$
|
||||||
|
> - $\omega_n^{2k} = \omega_\frac{n}{2}^k$
|
||||||
|
|
||||||
|
不难发现,如果我们知道了$A_e(\omega_\frac{n}{2}^k), A_o(\omega_\frac{n}{2}^k)$ ,就可以知道$A(\omega_n^k)$
|
||||||
|
|
||||||
|
> 这一步叫做Butterfly(蝴蝶操作)
|
||||||
|
> $$u = A_e(\mathellipsis)$$ $$v=\omega_n^k A_o(\mathellipsis)$$
|
||||||
|
> 则有
|
||||||
|
> $$y_k = u + v$$ $$y_{k+\frac{n}{2}} = u - v$$
|
||||||
|
> 即可以自下而上的,从 $A_{\mathellipsis}(\omega_{\mathellipsis}^k)$ 推导出 $A(x)$
|
||||||
|
> 同时当我们知道$A(-x)$的时候,我们也可以知道$A(x)$
|
||||||
|
|
||||||
|
有下面的树(以$n=4, x = \omega_{16}^k$为例)
|
||||||
|
> $A(\omega_{16}^k)$
|
||||||
|
> $Ae(\omega_8^k); Ao(\omega_8^k)$
|
||||||
|
> $Aee(\omega_4^k), Aeo(\omega_4^k); Aoe(\omega_4^k), Aoo(\omega_4^k)$
|
||||||
|
> $Aeee(\omega_2^k), Aeeo(\omega_2^k) ,, Aeoe(\omega_2^k), Aeoo(\omega_2^k);$
|
||||||
|
> $Aoee(\omega_2^k), Aoeo(\omega_2^k) ,, Aeoe(\omega_2^k), Aeoo(\omega_2^k)$
|
||||||
|
> 最底层为$A...(\omega_1^k) = a_i$(系数 | 常数多项式)
|
||||||
|
|
||||||
|
**已知最下层的所有数值(的一半),一次往上带入即可**
|
||||||
|
|
||||||
|
---
|
||||||
|
## - 示例
|
||||||
|
|
||||||
|
以下两个式子为例:
|
||||||
|
$$f_1 = x^3 + x^2 + 8x + 1 $$ $$ f_2 = x^2 - 5x - 2 $$
|
||||||
|
|
||||||
|
### 一、确定次数以及补零
|
||||||
|
- 次数:$2 + 3 = 5$, 需要 $5 + 1 = 6$ 个点值
|
||||||
|
- 确定n:$2^3=8$,$n = 3$
|
||||||
|
|
||||||
|
得到两个系数数组
|
||||||
|
- $A = [1,8,1,1,0,0,0,0]$
|
||||||
|
- $B = [-2, -5,1,0,0,0,0,0]$
|
||||||
|
**(从低次到高次排序,补0至8位)**
|
||||||
|
|
||||||
|
### 二、正向FFT
|
||||||
|
**计算$f_1(\omega_8^k)$,$f_2(\omega_8^k)$,其中$k = 0, 1, \mathellipsis , 7$,单位根$\omega_8^k = e^{i\frac{2\pi k}{8}} = \cos(\frac{2k\pi}{8}) + i\sin(\frac{2k\pi}{8})$**
|
||||||
|
|
||||||
|
|
||||||
|
#### 1. 初始多项式定义
|
||||||
|
设两个多项式:
|
||||||
|
$$f_1(x) = x^3 + x^2 + 8x + 1$$
|
||||||
|
$$f_2(x) = x^2 - 5x - 2$$
|
||||||
|
|
||||||
|
为了进行 $n=8$ 的 FFT,补零后的系数向量为:
|
||||||
|
$$A = [a_0, a_1, a_2, a_3, a_4, a_5, a_6, a_7] = [1, 8, 1, 1, 0, 0, 0, 0]$$
|
||||||
|
$$B = [b_0, b_1, b_2, b_3, b_4, b_5, b_6, b_7] = [-2, -5, 1, 0, 0, 0, 0, 0]$$
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2. 递归拆分逻辑 (以 A 为例)
|
||||||
|
|
||||||
|
**第一层拆分 ($n=8 \to n=4$):**
|
||||||
|
$$A_e(x) = a_0 + a_2x + a_4x^2 + a_6x^3 = 1 + 1x$$
|
||||||
|
$$A_o(x) = a_1 + a_3x + a_5x^2 + a_7x^3 = 8 + 1x$$
|
||||||
|
|
||||||
|
**第二层拆分 ($n=4 \to n=2$):**
|
||||||
|
对于 $A_e$:
|
||||||
|
$$A_{ee}(x) = a_0 + a_4x = 1$$
|
||||||
|
$$A_{eo}(x) = a_2 + a_6x = 1$$
|
||||||
|
对于 $A_o$:
|
||||||
|
$$A_{oe}(x) = a_1 + a_5x = 8$$
|
||||||
|
$$A_{oo}(x) = a_3 + a_7x = 1$$
|
||||||
|
|
||||||
|
**第三层拆分 ($n=2 \to n=1$):**
|
||||||
|
这是递归终点,系数即为点值:
|
||||||
|
$$A_{eee}=1, A_{eeo}=0, A_{eoe}=1, A_{eeo}=0 \dots$$
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3. 向上回溯(蝴蝶变换公式)
|
||||||
|
利用核心公式:
|
||||||
|
$$A(\omega_n^k) = A_e(\omega_{n/2}^k) + \omega_n^k A_o(\omega_{n/2}^k)$$
|
||||||
|
$$A(\omega_n^{k+n/2}) = A_e(\omega_{n/2}^k) - \omega_n^k A_o(\omega_{n/2}^k)$$
|
||||||
|
|
||||||
|
**从 $n=2$ 合并到 $n=4$ 的例子 (计算 $A_e$ 的点值):**
|
||||||
|
已知 $A_{ee}$ 的点值在 $\omega_2^0$ 处为 $1$, $A_{eo}$ 的点值也为 $1$。
|
||||||
|
则在 $n=4$ 层:
|
||||||
|
$$A_e(\omega_4^0) = A_{ee}(\omega_2^0) + \omega_4^0 A_{eo}(\omega_2^0) = 1 + 1(1) = 2$$
|
||||||
|
$$A_e(\omega_4^1) = A_{ee}(\omega_2^1) + \omega_4^1 A_{eo}(\omega_2^1) = 1 + i(1) = 1+i$$
|
||||||
|
$$A_e(\omega_4^2) = A_{ee}(\omega_2^0) - \omega_4^0 A_{eo}(\omega_2^0) = 1 - 1 = 0$$
|
||||||
|
$$A_e(\omega_4^3) = A_{ee}(\omega_2^1) - \omega_4^1 A_{eo}(\omega_2^1) = 1 - i$$
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4. 点值乘法与逆变换 (IFFT)
|
||||||
|
得到 $A(\omega_8^k)$ 和 $B(\omega_8^k)$ 后,进行点值乘法:
|
||||||
|
$$C(\omega_8^k) = A(\omega_8^k) \cdot B(\omega_8^k)$$
|
||||||
|
|
||||||
|
最后进行 IFFT,公式为:
|
||||||
|
$$c_j = \frac{1}{n} \sum_{k=0}^{n-1} C(\omega_n^k) (\omega_n^{-j})^k$$
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 5. 最终系数结果
|
||||||
|
$$C(x) = f_1(x) \cdot f_2(x) = x^5 - 4x^4 + x^3 - 41x^2 - 21x - 2$$
|
||||||
|
系数向量为:
|
||||||
|
$$[-2, -21, -41, 1, -4, 1, 0, 0]$$
|
||||||
103
Algorithm/FFT/template-iterative.cpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <complex>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// 使用复数类,complex<double> 自带加减乘运算
|
||||||
|
typedef complex<double> cd;
|
||||||
|
const double PI = acos(-1.0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FFT 核心函数
|
||||||
|
* @param a 待转换的系数向量
|
||||||
|
* @param invert 是否为逆变换 (IFFT)。false 为 DFT,true 为 IDFT
|
||||||
|
*/
|
||||||
|
void fft(vector<cd>& a, bool invert) {
|
||||||
|
int n = a.size();
|
||||||
|
|
||||||
|
// 1. 位逆序置换 (Bit-reversal permutation)
|
||||||
|
// 将原数组的下标进行二进制翻转,使得最底层的系数排列在正确位置
|
||||||
|
for (int i = 1, j = 0; i < n; i++) {
|
||||||
|
int bit = n >> 1;
|
||||||
|
for (; j & bit; bit >>= 1)
|
||||||
|
j ^= bit;
|
||||||
|
j ^= bit;
|
||||||
|
if (i < j) swap(a[i], a[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 自底向上合并 (迭代实现蝴蝶变换)
|
||||||
|
// len 是当前合并区间的长度,从 2, 4, 8 ... 一直合并到 n
|
||||||
|
for (int len = 2; len <= n; len <<= 1) {
|
||||||
|
double ang = 2 * PI / len * (invert ? -1 : 1);
|
||||||
|
cd wlen(cos(ang), sin(ang)); // 单位根 w_n^1
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i += len) {
|
||||||
|
cd w(1); // 初始 w_n^0 = 1
|
||||||
|
for (int j = 0; j < len / 2; j++) {
|
||||||
|
// 蝴蝶变换核心公式:
|
||||||
|
// u = A_e, v = w * A_o
|
||||||
|
cd u = a[i + j], v = a[i + j + len / 2] * w;
|
||||||
|
a[i + j] = u + v; // A(w^k) = A_e + w*A_o
|
||||||
|
a[i + j + len / 2] = u - v; // A(w^{k+n/2}) = A_e - w*A_o
|
||||||
|
w *= wlen; // 迭代单位根
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 如果是逆变换,最后需要除以 n
|
||||||
|
if (invert) {
|
||||||
|
for (cd & x : a)
|
||||||
|
x /= n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多项式乘法
|
||||||
|
*/
|
||||||
|
vector<long long> multiply(vector<int> const& a, vector<int> const& b) {
|
||||||
|
vector<cd> fa(a.begin(), a.end()), fb(b.begin(), b.end());
|
||||||
|
|
||||||
|
// 补零到 2 的幂次
|
||||||
|
int n = 1;
|
||||||
|
while (n < a.size() + b.size())
|
||||||
|
n <<= 1;
|
||||||
|
fa.resize(n);
|
||||||
|
fb.resize(n);
|
||||||
|
|
||||||
|
// 1. DFT: 系数 -> 点值
|
||||||
|
fft(fa, false);
|
||||||
|
fft(fb, false);
|
||||||
|
|
||||||
|
// 2. Pointwise Multiplication: 点值直接相乘
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
fa[i] *= fb[i];
|
||||||
|
|
||||||
|
// 3. IDFT: 点值 -> 系数
|
||||||
|
fft(fa, true);
|
||||||
|
|
||||||
|
// 4. 取实部并四舍五入
|
||||||
|
vector<long long> result(n);
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
result[i] = round(fa[i].real());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// 你的例子:
|
||||||
|
// f1 = 1 + 8x + 1x^2 + 1x^3 -> [1, 8, 1, 1]
|
||||||
|
// f2 = -2 - 5x + 1x^2 -> [-2, -5, 1]
|
||||||
|
vector<int> f1 = {1, 8, 1, 1};
|
||||||
|
vector<int> f2 = {-2, -5, 1};
|
||||||
|
|
||||||
|
vector<long long> res = multiply(f1, f2);
|
||||||
|
|
||||||
|
cout << "Result coefficients: ";
|
||||||
|
for (int i = 0; i < f1.size() + f2.size() - 1; i++) {
|
||||||
|
cout << res[i] << " ";
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
105
Algorithm/FFT/template-recursive.cpp
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <complex>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
typedef complex<double> cd;
|
||||||
|
const double PI = acos(-1.0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归版 FFT
|
||||||
|
* @param a 传入的系数向量
|
||||||
|
* @param invert 是否为逆变换(FFT or IFFT)
|
||||||
|
*/
|
||||||
|
void fft_recursive(vector<cd>& a, bool invert) {
|
||||||
|
int n = a.size();
|
||||||
|
|
||||||
|
// 递归终点:当多项式只有一个常数项时,直接返回
|
||||||
|
if (n == 1) return;
|
||||||
|
|
||||||
|
// 1. 拆分阶段 (Divide)
|
||||||
|
// 按照下标奇偶性将 a 拆分成 a_e 和 a_o
|
||||||
|
vector<cd> a0(n / 2), a1(n / 2);
|
||||||
|
for (int i = 0; 2 * i < n; i++) {
|
||||||
|
a0[i] = a[2 * i]; // 偶数项
|
||||||
|
a1[i] = a[2 * i + 1]; // 奇数项
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 递归阶段 (Conquer)
|
||||||
|
fft_recursive(a0, invert);
|
||||||
|
fft_recursive(a1, invert);
|
||||||
|
|
||||||
|
// 3. 合并阶段 (Combine / Butterfly Operation)
|
||||||
|
// 计算单位根 w_n^k
|
||||||
|
double ang = 2 * PI / n * (invert ? -1 : 1);
|
||||||
|
cd w(1); // 初始 w = w_n^0 = 1
|
||||||
|
cd wn(cos(ang), sin(ang)); // 单位步进 w_n^1
|
||||||
|
|
||||||
|
for (int k = 0; k < n / 2; k++) {
|
||||||
|
// 利用蝴蝶变换公式:
|
||||||
|
// A(w^k) = Ae(w^k) + w * Ao(w^k)
|
||||||
|
// A(w^{k+n/2}) = Ae(w^k) - w * Ao(w^k)
|
||||||
|
|
||||||
|
cd t = w * a1[k]; // 旋转因子与奇数项乘积
|
||||||
|
a[k] = a0[k] + t;
|
||||||
|
a[k + n / 2] = a0[k] - t;
|
||||||
|
|
||||||
|
if (invert) {
|
||||||
|
// 如果是逆变换,顺便除以 2(递归层层除以 2,最终相当于除以 n)
|
||||||
|
a[k] /= 2;
|
||||||
|
a[k + n / 2] /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
w *= wn; // 移动到下一个单位根 w_n^{k+1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多项式乘法封装
|
||||||
|
*/
|
||||||
|
vector<long long> multiply(vector<int> const& f1, vector<int> const& f2) {
|
||||||
|
vector<cd> fa(f1.begin(), f1.end()), fb(f2.begin(), f2.end());
|
||||||
|
|
||||||
|
// 补零到 2 的幂
|
||||||
|
int n = 1;
|
||||||
|
while (n < f1.size() + f2.size()) n <<= 1;
|
||||||
|
fa.resize(n);
|
||||||
|
fb.resize(n);
|
||||||
|
|
||||||
|
// 变换到点值表示
|
||||||
|
fft_recursive(fa, false);
|
||||||
|
fft_recursive(fb, false);
|
||||||
|
|
||||||
|
// 点值相乘
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
fa[i] *= fb[i];
|
||||||
|
|
||||||
|
// 变换回系数表示
|
||||||
|
fft_recursive(fa, true);
|
||||||
|
|
||||||
|
// 结果取整
|
||||||
|
vector<long long> result;
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
result.push_back(round(fa[i].real()));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// A(x) = 1x^3 + 1x^2 + 8x + 1
|
||||||
|
// B(x) = 1x^2 - 5x - 2
|
||||||
|
vector<int> a = {1, 8, 1, 1}; // 注意低位在前
|
||||||
|
vector<int> b = {-2, -5, 1};
|
||||||
|
|
||||||
|
vector<long long> res = multiply(a, b);
|
||||||
|
|
||||||
|
cout << "Final Coefficients: ";
|
||||||
|
for (int i = 0; i < a.size() + b.size() - 1; i++) {
|
||||||
|
cout << res[i] << " ";
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
153
Algorithm/积累/基础算法/二分/p1824进击的母牛.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# P1824 [USACO05FEB] 进击的奶牛 Aggressive Cows G
|
||||||
|
|
||||||
|
## 题目描述
|
||||||
|
|
||||||
|
农夫约翰建造了一座有 $n$ 间牛舍的小屋,牛舍排在一条直线上,第 $i$ 间牛舍在 $x_i$ 的位置,但是约翰的 $m$ 头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其它牛尽可能远的牛舍。也就是要最大化最近的两头牛之间的距离。
|
||||||
|
|
||||||
|
牛们并不喜欢这种布局,而且几头牛放在一个隔间里,它们就要发生争斗。为了不让牛互相伤害。约翰决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是多少呢?
|
||||||
|
|
||||||
|
## 输入格式
|
||||||
|
|
||||||
|
第一行用空格分隔的两个整数 $n$ 和 $m$;
|
||||||
|
|
||||||
|
下面 $n$ 行为 $n$ 个用空格隔开的整数,表示位置 $x_i$。
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
|
||||||
|
一行一个整数,表示最大的最小距离值。
|
||||||
|
|
||||||
|
## 输入输出样例 #1
|
||||||
|
|
||||||
|
### 输入 #1
|
||||||
|
|
||||||
|
```
|
||||||
|
5 3
|
||||||
|
1
|
||||||
|
2
|
||||||
|
8
|
||||||
|
4
|
||||||
|
9
|
||||||
|
```
|
||||||
|
|
||||||
|
### 输出 #1
|
||||||
|
|
||||||
|
```
|
||||||
|
3
|
||||||
|
```
|
||||||
|
|
||||||
|
## 说明/提示
|
||||||
|
|
||||||
|
【样例解析】把牛放在 $1$,$4$,$8$ 这三个位置,距离是 $3$。容易证明最小距离已经最大。
|
||||||
|
|
||||||
|
【数据范围】对于 $100\%$ 的数据,$2 \le n \le 10^5$,$0 \le x_i \le 10^9$,$2 \le m \le n$。不保证 $x$ 数组单调递增。
|
||||||
|
|
||||||
|
|
||||||
|
哈哈,这种反应太正常了!初次接触这种题,谁都会觉得这种“反着来”的思路简直是脑洞大开。
|
||||||
|
|
||||||
|
这种思维方式在算法竞赛里有一个专门的套路,叫 **“二分答案”**。其实它不是靠灵光一现,而是有迹可循的“职业习惯”。
|
||||||
|
|
||||||
|
我们可以把这种思维过程拆解为三个“直觉点”,以后你看到类似的题,也能秒懂。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. 关键词暗示:看到“最大化最小值”
|
||||||
|
|
||||||
|
当你看到题目要求:
|
||||||
|
|
||||||
|
* **最大化** 某个 **最小值**
|
||||||
|
* 或者 **最小化** 某个 **最大值**
|
||||||
|
|
||||||
|
这在算法界几乎就是**明示**:请用二分搜索!
|
||||||
|
|
||||||
|
**为什么?** 因为直接计算“最优布局”非常困难,要考虑无数种组合。但如果我问你:“间距为 行不行?”你只需要从左往右摆摆看(贪心),一眼就能看出行不行。把“寻找题”变成“判断题”,难度瞬间降维。
|
||||||
|
|
||||||
|
### 2. 利用“单调性”:答案像个开关
|
||||||
|
|
||||||
|
这是最关键的直觉。你可以这样想:
|
||||||
|
|
||||||
|
* 如果间距是1 米,肯定能放下 头牛(太简单了)。
|
||||||
|
* 如果间距是1000000 米,肯定放不下(太远了)。
|
||||||
|
* 在这中间,一定存在一个**临界点**。在临界点左边,全是“行”;在临界点右边,全是“不行”。
|
||||||
|
|
||||||
|
既然有这种**单调性**(一半行,一半不行),我们就不需要一个个试,直接用**二分查找**去撞那个临界点。
|
||||||
|
|
||||||
|
### 3. 程序员的“偷懒”思维
|
||||||
|
|
||||||
|
正向思考(直接算位置)是“创造者思维”,很难;
|
||||||
|
反向思考(猜一个答案去验证)是“检查者思维”,很简单。
|
||||||
|
|
||||||
|
**计算机最擅长做简单的重复劳动。** 既然验证一次只需要 ,二分查找只需要 次,总共也就跑 次验证。与其想破脑袋去算最优解,不如直接“暴力”地猜几十次。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 总结一下这个“坑”是怎么跳进去的:
|
||||||
|
|
||||||
|
1. **第一步:** 看到“最大化最小间距”,心里默念:“二分答案预警”。
|
||||||
|
2. **第二步:** 确定答案范围(比如 到 )。
|
||||||
|
3. **第三步:** 写一个 `check` 函数。这一步通常用**贪心**。
|
||||||
|
* *贪心逻辑:* 既然要间距大,我就把第一头牛放在最左边,后面每一头牛都紧贴着“当前位置 + 间距”能放下的第一个点放。
|
||||||
|
|
||||||
|
|
||||||
|
4. **第四步:** 套二分模板。
|
||||||
|
|
||||||
|
> **这种感觉就像是:** 你去买衣服,不知道自己穿多大码。你不是拿尺子把自己全身量一遍再算缩水率(正向计算),而是直接拿 L 码试一下。大了?换 M。还大?换 S。这就是二分答案。
|
||||||
|
|
||||||
|
**你要不要试着写一下这个 `check` 函数的伪代码?我可以帮你看看逻辑对不对。**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
``` cpp
|
||||||
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
using namespace std;
|
||||||
|
//int ax[] = {1, 2, 4, 8, 9};
|
||||||
|
|
||||||
|
//按照分布,检查是否能够排列即可
|
||||||
|
/*
|
||||||
|
1、读入arr, mid
|
||||||
|
2、按照分布排列,找到离mid格最近的槽位插入
|
||||||
|
*/
|
||||||
|
int check(const vector<int>& arr, int mid){
|
||||||
|
if(arr.empty()) return 0;
|
||||||
|
|
||||||
|
int cnt = 1;
|
||||||
|
int n = arr.size();
|
||||||
|
int dist = 0;
|
||||||
|
for(int i = 1; i < n; ++i){
|
||||||
|
dist += arr[i] - arr[i - 1];
|
||||||
|
if(dist >= mid){
|
||||||
|
cnt++;
|
||||||
|
dist = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
int n = 5, c = 3;
|
||||||
|
cin >> n >> c;
|
||||||
|
vector<int> arr(n);
|
||||||
|
for(int i = 0; i < n; i++){
|
||||||
|
cin >> arr[i];
|
||||||
|
//arr[i] = ax[i];
|
||||||
|
}
|
||||||
|
sort(arr.begin(), arr.end());
|
||||||
|
int smin = 0, smax = arr.back() - arr.front();
|
||||||
|
//range [smin, smax], do mid to this range
|
||||||
|
int l = smin, r = smax;
|
||||||
|
int ans = 0;
|
||||||
|
while(l <= r){
|
||||||
|
int mid = (l+r)/2;
|
||||||
|
int cc = check(arr, mid);
|
||||||
|
if (cc >= c) {
|
||||||
|
ans = mid;
|
||||||
|
l = mid + 1;
|
||||||
|
} else r = mid - 1;
|
||||||
|
}
|
||||||
|
cout << ans << endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
294
C/Ccompile.md
@@ -1,294 +0,0 @@
|
|||||||
好,这个问题问得**非常本质**,而且一看就是你已经开始从“会写代码”往“懂工具链 / 懂底层”走了 👍
|
|
||||||
我分三层给你捋清楚:**编译器有哪些 → 编译/汇编/链接的指令 → C 与 C++ 在编译层面的真实差异**。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、主流 C / C++ 编译器版图(你必须认识的)
|
|
||||||
|
|
||||||
### 1️⃣ GCC / G++
|
|
||||||
|
|
||||||
* **全名**:GNU Compiler Collection
|
|
||||||
* **支持语言**:C / C++ / Fortran / Go / Obj-C / …
|
|
||||||
* **命令**:
|
|
||||||
|
|
||||||
* `gcc` → 主要用来编 C
|
|
||||||
* `g++` → 主要用来编 C++
|
|
||||||
* **平台**:Linux / WSL / macOS / *nix
|
|
||||||
* **特点**:
|
|
||||||
|
|
||||||
* 工业级事实标准
|
|
||||||
* 参数多、控制力极强
|
|
||||||
* 对 ABI、链接、底层非常“真实”
|
|
||||||
|
|
||||||
📌 **重点**:
|
|
||||||
`gcc` 和 `g++` **底层是同一个编译器**,区别在:
|
|
||||||
|
|
||||||
* 默认语言不同
|
|
||||||
* 是否自动链接 `libstdc++`
|
|
||||||
* 是否启用 C++ 语义(name mangling、异常等)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2️⃣ Clang / Clang++
|
|
||||||
|
|
||||||
* **LLVM 系列**
|
|
||||||
* **命令**:
|
|
||||||
|
|
||||||
* `clang`
|
|
||||||
* `clang++`
|
|
||||||
* **平台**:Linux / macOS / Windows
|
|
||||||
* **特点**:
|
|
||||||
|
|
||||||
* 错误提示极其友好(新手 + 老手都爱)
|
|
||||||
* 编译速度快
|
|
||||||
* AST、静态分析能力强
|
|
||||||
|
|
||||||
📌 **现实情况**:
|
|
||||||
|
|
||||||
* macOS 上的 `gcc` 实际就是 `clang`
|
|
||||||
* 现代 C++ 项目大量使用 clang
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3️⃣ MSVC(Windows 专属)
|
|
||||||
|
|
||||||
* **微软官方编译器**
|
|
||||||
* **命令**:
|
|
||||||
|
|
||||||
* `cl.exe`
|
|
||||||
* **特点**:
|
|
||||||
|
|
||||||
* Windows ABI 官方实现
|
|
||||||
* C++ 标准支持偏慢但稳定
|
|
||||||
* 和 Visual Studio 深度绑定
|
|
||||||
|
|
||||||
📌 **注意**:
|
|
||||||
|
|
||||||
* MSVC ≠ GCC / Clang
|
|
||||||
* 一些 GCC 的参数 **在 MSVC 上完全不存在**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4️⃣ 其他你只需“知道存在”的
|
|
||||||
|
|
||||||
| 编译器 | 用途 |
|
|
||||||
| --------------- | -------------- |
|
|
||||||
| TinyCC (tcc) | 极快、教学 |
|
|
||||||
| Intel ICC / ICX | 性能优化 |
|
|
||||||
| ARM GCC | 嵌入式 |
|
|
||||||
| MinGW | Windows 上的 GCC |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、C / C++ 编译的**完整流程**(不是一句 gcc)
|
|
||||||
|
|
||||||
### 🧠 真相:**编译 = 4 个阶段**
|
|
||||||
|
|
||||||
```text
|
|
||||||
源代码
|
|
||||||
↓
|
|
||||||
预处理(cpp)
|
|
||||||
↓
|
|
||||||
编译(cc1 / clang)
|
|
||||||
↓
|
|
||||||
汇编(as)
|
|
||||||
↓
|
|
||||||
链接(ld)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、你问的重点①:**“各种指令”是哪些?**
|
|
||||||
|
|
||||||
### 1️⃣ 预处理指令(只和文本有关)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gcc -E a.c
|
|
||||||
```
|
|
||||||
|
|
||||||
| 指令 | 作用 |
|
|
||||||
| ---------------- | ---- |
|
|
||||||
| `#include` | 文本展开 |
|
|
||||||
| `#define` | 宏替换 |
|
|
||||||
| `#ifdef` / `#if` | 条件编译 |
|
|
||||||
|
|
||||||
📌 **关键点**:
|
|
||||||
|
|
||||||
* C 和 C++ **完全一样**
|
|
||||||
* 预处理器不知道“类型”“函数”“类”
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2️⃣ 编译阶段指令(最核心)
|
|
||||||
|
|
||||||
#### 基础用法
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gcc -c a.c # 只编译,不链接
|
|
||||||
g++ -c a.cpp
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 常用编译参数
|
|
||||||
|
|
||||||
| 参数 | 含义 |
|
|
||||||
| -------------------------------- | ---- |
|
|
||||||
| `-std=c11 / c17 / c++17 / c++20` | 标准 |
|
|
||||||
| `-Wall -Wextra` | 警告 |
|
|
||||||
| `-O0 -O2 -O3` | 优化 |
|
|
||||||
| `-g` | 调试信息 |
|
|
||||||
| `-fno-omit-frame-pointer` | 调试友好 |
|
|
||||||
| `-fsanitize=address` | ASan |
|
|
||||||
|
|
||||||
📌 **重点差异**:
|
|
||||||
|
|
||||||
* C++ 会启用:
|
|
||||||
|
|
||||||
* **函数重载**
|
|
||||||
* **模板实例化**
|
|
||||||
* **名字修饰(name mangling)**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3️⃣ 汇编层(你开始接触底层时很重要)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gcc -S a.c
|
|
||||||
g++ -S a.cpp
|
|
||||||
```
|
|
||||||
|
|
||||||
📌 差异点:
|
|
||||||
|
|
||||||
* C++ 生成的汇编:
|
|
||||||
|
|
||||||
* 函数名会被改写(`_Z3addii`)
|
|
||||||
* 有构造 / 析构 / 异常展开
|
|
||||||
* C:
|
|
||||||
|
|
||||||
* 函数名基本原样
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4️⃣ 链接阶段(很多“玄学错误”的源头)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gcc a.o b.o -o app
|
|
||||||
g++ a.o b.o -o app
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **最重要的差异**
|
|
||||||
|
|
||||||
| 行为 | gcc | g++ |
|
|
||||||
| ----------------- | --- | --- |
|
|
||||||
| 自动链接 libstdc++ | ❌ | ✅ |
|
|
||||||
| 支持 new / iostream | ❌ | ✅ |
|
|
||||||
|
|
||||||
📌 经典坑:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gcc main.cpp
|
|
||||||
# undefined reference to std::cout
|
|
||||||
```
|
|
||||||
|
|
||||||
因为 **gcc 不自动链接 C++ 标准库**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、你问的重点②:**C 与 C++ 在“编译器层面”的差异**
|
|
||||||
|
|
||||||
### 1️⃣ 语言前端不同
|
|
||||||
|
|
||||||
| 项目 | C | C++ |
|
|
||||||
| ---- | -- | --- |
|
|
||||||
| 类型系统 | 简单 | 极复杂 |
|
|
||||||
| 模板 | ❌ | ✅ |
|
|
||||||
| 函数重载 | ❌ | ✅ |
|
|
||||||
| 名字修饰 | ❌ | ✅ |
|
|
||||||
|
|
||||||
📌 本质:
|
|
||||||
|
|
||||||
> **同一个编译器,不同“语言前端”**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2️⃣ Name Mangling(超级重要)
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
int add(int a, int b);
|
|
||||||
```
|
|
||||||
|
|
||||||
| 语言 | 符号名 |
|
|
||||||
| --- | ---------- |
|
|
||||||
| C | `add` |
|
|
||||||
| C++ | `_Z3addii` |
|
|
||||||
|
|
||||||
所以你见过:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
extern "C" {
|
|
||||||
void foo();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
👉 **是为了让 C++ 用 C 的符号规则**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3️⃣ 运行时支持完全不同
|
|
||||||
|
|
||||||
| 项目 | C | C++ |
|
|
||||||
| ----- | ---- | --------- |
|
|
||||||
| 构造/析构 | ❌ | ✅ |
|
|
||||||
| RTTI | ❌ | ✅ |
|
|
||||||
| 异常 | ❌ | ✅ |
|
|
||||||
| 标准库 | libc | libstdc++ |
|
|
||||||
|
|
||||||
📌 所以:
|
|
||||||
|
|
||||||
* C 程序 = **非常薄**
|
|
||||||
* C++ 程序 = **编译器 + 运行时体系**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4️⃣ ABI 差异(工程级别)
|
|
||||||
|
|
||||||
* C ABI:**几乎永远稳定**
|
|
||||||
* C++ ABI:
|
|
||||||
|
|
||||||
* 不同编译器、不同版本可能不兼容
|
|
||||||
* 这就是为什么库常用 **C 接口**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 五、给你一个“认知升级总结”
|
|
||||||
|
|
||||||
> 🔥 **C 是“能跑就行的语言”**
|
|
||||||
> 🔥 **C++ 是“需要编译器深度参与的语言”**
|
|
||||||
|
|
||||||
所以你会发现:
|
|
||||||
|
|
||||||
* 学 C → 多学 **内存、指针、汇编**
|
|
||||||
* 学 C++ → 多学 **编译模型、ABI、链接、对象模型**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 六、下一步我可以怎么继续帮你?
|
|
||||||
|
|
||||||
你现在这个问题,**已经明显在向系统级程序员靠拢了**。
|
|
||||||
我可以继续给你:
|
|
||||||
|
|
||||||
1️⃣ 用 **同一段代码** 对比
|
|
||||||
|
|
||||||
* gcc / g++ / clang / msvc
|
|
||||||
* 汇编输出差异
|
|
||||||
|
|
||||||
2️⃣ 系统讲一遍
|
|
||||||
|
|
||||||
* **C++ 对象在内存里的真实布局**
|
|
||||||
* vtable / 构造顺序 / 析构顺序
|
|
||||||
|
|
||||||
3️⃣ 专门讲
|
|
||||||
|
|
||||||
* **链接错误为什么那么玄学**
|
|
||||||
* undefined reference 背后的逻辑
|
|
||||||
|
|
||||||
你选一个,我直接给你干到“明白为止”。
|
|
||||||
@@ -1,376 +0,0 @@
|
|||||||
### 1. GCC 常用指令大表
|
|
||||||
|
|
||||||
| 指令 | 全称/含义 | 作用说明 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| **`-c`** | Compile | 只编译生成目标文件 (`.o`),**不进行链接**。 |
|
|
||||||
| **`-o <file>`** | Output | 指定输出文件的名称(可执行文件、库文件等)。 |
|
|
||||||
| **`-g`** | Debug | 生成调试信息,方便使用 `gdb` 进行断点调试。 |
|
|
||||||
| **`-O0/1/2/3`** | Optimize | 设置优化等级。`-O2` 是最常用的平衡选项,`-O3` 追求极致速度。 |
|
|
||||||
| **`-I <dir>`** | Include | 添加**头文件**搜索路径。 |
|
|
||||||
| **`-L <dir>`** | Library Path | 添加**库文件**搜索路径。 |
|
|
||||||
| **`-l <name>`** | link library | 链接具体的库(如 `-lm` 链接 `libmath`)。 |
|
|
||||||
| **`-D <macro>`** | Define | 定义宏(等同于代码里的 `#define`)。 |
|
|
||||||
| **`-Wall`** | Warnings all | 开启几乎所有的常用警告,强烈建议永远加上。 |
|
|
||||||
| **`-fPIC`** | Position Independent Code | 生成位置无关代码,**制作 `.so` 动态库必带**。 |
|
|
||||||
| **`-shared`** | Shared | 告诉编译器生成一个动态链接库。 |
|
|
||||||
| **`-std=c++17`** | Standard | 指定使用的 C++ 标准版本(如 c++11, c++14, c++20)。 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 什么是 `-Wl`?
|
|
||||||
|
|
||||||
**`-Wl` (Warn linker)** 的意思是:**“喂,GCC,把后面跟着的参数直接传给底层的链接器(ld)。”**
|
|
||||||
|
|
||||||
因为 GCC 本身只是一个“前端”驱动程序,它负责调用预处理器、编译器、汇编器和链接器。有时候你需要设置一些链接器特有的高级选项,而 GCC 并不直接支持这些选项。
|
|
||||||
|
|
||||||
* **格式**:`-Wl,<option1>,<option2>`
|
|
||||||
* **注意**:参数之间是用**逗号**隔开的,不能有空格。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 为什么有 `--out-implib,xxx.a`?
|
|
||||||
|
|
||||||
这通常出现在 **Windows (MinGW)** 环境下开发 DLL 时。这是一个非常关键的概念:**“导入库” (Import Library)**。
|
|
||||||
|
|
||||||
#### 为什么需要它?
|
|
||||||
|
|
||||||
在 Windows 上,如果你编译了一个 `my.dll`,主程序运行的时候需要它,但**编译主程序的时候**,链接器也需要知道这个 DLL 里到底有哪些函数。
|
|
||||||
|
|
||||||
* **`.dll`**:是运行时用的(包含代码逻辑)。
|
|
||||||
* **`.a` 或 `.lib` (导入库)**:是编译链接时用的(包含函数地址列表)。
|
|
||||||
|
|
||||||
#### 指令解析:
|
|
||||||
|
|
||||||
`g++ -shared -o my.dll -Wl,--out-implib,libmy.dll.a`
|
|
||||||
|
|
||||||
1. **`-shared -o my.dll`**:生成动态链接库主体。
|
|
||||||
2. **`-Wl,`**:接下来的参数是给链接器看的。
|
|
||||||
3. **`--out-implib,libmy.dll.a`**:链接器,请帮我顺便生成一个名为 `libmy.dll.a` 的**导入库**。
|
|
||||||
|
|
||||||
**这样做的流程是:**
|
|
||||||
|
|
||||||
* **第一步**:你编译 DLL,得到了 `my.dll` 和 `libmy.dll.a`。
|
|
||||||
* **第二步**:你编译主程序,链接 `libmy.dll.a`(使用 `-L. -lmy`)。
|
|
||||||
* **第三步**:运行程序,Windows 会根据之前链接的信息去加载 `my.dll`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. 总结:`-Wl` 后的常用参数
|
|
||||||
|
|
||||||
除了 `--out-implib`,你可能还会见到这些:
|
|
||||||
|
|
||||||
* **`-Wl,-rpath,<path>`**:指定运行时查找 `.so` 的路径(Linux 常用,解决找不到库的问题)。
|
|
||||||
* **`-Wl,--stack,<size>`**:设置程序栈的大小。
|
|
||||||
* **`-Wl,--as-needed`**:只链接那些代码中真正用到的库,减小体积。
|
|
||||||
|
|
||||||
> **小贴士**:如果你在 Linux 下开发,通常不需要 `--out-implib`,因为 Linux 的 `.so` 文件既是运行时库,也可以直接作为链接时的导入信息。
|
|
||||||
|
|
||||||
你现在是在尝试把现有的 C++ 库打包成 DLL 给其他语言(比如 Python 或 Java)调用吗?如果是的话,我们可以聊聊 `extern "C"` 的深层必要性。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
哈哈,这正是 C/C++ 的“黑魔法”所在:它是现代编程世界的**基石**。
|
|
||||||
|
|
||||||
不管是 Python 的 `numpy`、Node.js 的原生模块,还是 Android (Java/Kotlin) 的底层驱动,核心全是这么干的。这种技术统称为 **FFI (Foreign Function Interface,外部函数接口)**。
|
|
||||||
|
|
||||||
要实现这个,主要靠这两根支柱:
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 1. 核心秘诀:`extern "C"`
|
|
||||||
|
|
||||||
这是最关键的一步。
|
|
||||||
|
|
||||||
C++ 支持**函数重载**,所以编译器会偷偷修改函数名(这叫 **Name Mangling**)。比如 `add(int, int)` 在 C++ 内部可能变成了 `_Z3addii`。
|
|
||||||
其他语言(比如 Python)去 DLL 里找 `add` 时,根本找不到这个怪名字。
|
|
||||||
|
|
||||||
**解决方法:**
|
|
||||||
在 C++ 头文件里加上 `extern "C"`,告诉编译器:“请按 C 语言那种简单粗暴的方式命名,叫 `add` 就给我留成 `add`。”
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// mylib.h
|
|
||||||
extern "C" {
|
|
||||||
__declspec(dllexport) int add(int a, int b) {
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 不同语言是怎么“吃”掉这个库的?
|
|
||||||
|
|
||||||
我给你举两个最常用的例子:
|
|
||||||
|
|
||||||
#### A. Python 调用(最简单)
|
|
||||||
|
|
||||||
Python 有个内置库叫 `ctypes`,专门用来“白嫖” C 库。
|
|
||||||
|
|
||||||
```python
|
|
||||||
import ctypes
|
|
||||||
|
|
||||||
# 1. 加载库
|
|
||||||
# Windows 用 .dll, Linux 用 .so
|
|
||||||
lib = ctypes.CDLL("./mylib.dll")
|
|
||||||
|
|
||||||
# 2. 直接调用
|
|
||||||
result = lib.add(10, 20)
|
|
||||||
print(f"结果是: {result}")
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### B. Java 调用 (JNI)
|
|
||||||
|
|
||||||
Java 稍微复杂点,它需要一个“中介”。
|
|
||||||
|
|
||||||
1. 在 Java 里写个 `native` 声明。
|
|
||||||
2. 用 `javac -h` 生成一个 C 标头。
|
|
||||||
3. 在 C 里实现这个标头,编译成 DLL/so。
|
|
||||||
4. Java 用 `System.loadLibrary("mylib")` 加载。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 为什么大家都爱把 C 库包给别人用?
|
|
||||||
|
|
||||||
1. **性能炸裂**:Python 算循环很慢,但 C 算循环极快。把计算量大的逻辑写成 C 库,Python 只负责调,速度瞬间提升百倍。
|
|
||||||
2. **保护代码**:`.py` 文件发给别人一眼看穿,但编译后的 `.dll` 或 `.so` 是二进制,逆向工程难度直接拉满。
|
|
||||||
3. **跨语言共享**:写一份核心逻辑(比如图像处理算法),然后同时给 Web (Node.js)、桌面 (C#)、手机 (Java) 一起用。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. 跨语言调用的“深水区”
|
|
||||||
|
|
||||||
虽然 `int` 和 `float` 这种简单类型可以直接传,但当涉及到**字符串**、**结构体**或者**指针**时,事情就开始刺激了:
|
|
||||||
|
|
||||||
* **字符串**:C 语言的字符串是 `char*`(以 `\0` 结尾),而 Python 是对象。你需要手动转换编码。
|
|
||||||
* **内存管理**:谁申请的内存谁负责释放。如果 C 申请了一块内存传给 Python,Python 用完不告诉 C,就会导致**内存泄漏**。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 你想玩个更高端的吗?
|
|
||||||
|
|
||||||
有一种东西叫 **SWIG** 或者 **pybind11**。
|
|
||||||
|
|
||||||
如果你觉得手动写 `ctypes` 太麻烦,这些工具可以**自动生成**包装代码。你只需要给它一个 C++ 头文件,它能直接给你吐出一个可以在 Python 里 `import` 的模块。
|
|
||||||
|
|
||||||
你想让我演示一下如何用 **Python** 真正调用你刚刚写好的那个 `.dll` 吗?(我可以带你跑一遍完整的避坑流程)
|
|
||||||
|
|
||||||
这种“我操牛逼”的感觉,就是程序员第一次打通**底层逻辑**和**上层应用**时的快感。
|
|
||||||
|
|
||||||
既然你已经感受到了这种力量,我得给你泼点冷水(或者说教你几招保命符),因为跨语言调用虽然爽,但**内存安全**和**数据格式**是两个巨大的坑。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 1. 为什么一定要用 `extern "C"`?(可视化对比)
|
|
||||||
|
|
||||||
如果不加 `extern "C"`,C++ 编译器会进行“名字粉碎”(Name Mangling)。
|
|
||||||
|
|
||||||
* **你的代码:** `void hello()`
|
|
||||||
* **C++ 编译器眼里的它:** `__Z5hello_v`(因为它要区分同名但参数不同的重载函数)
|
|
||||||
* **其他语言眼里的它:** “对不起,我只找 `hello`,找不到那个乱码。”
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 跨语言调用的“生存法则”
|
|
||||||
|
|
||||||
当你准备把 C/C++ 库交给 Python、Java 或 Go 调用时,请务必记住这三条:
|
|
||||||
|
|
||||||
#### ① 永远不要在 C++ 库里分配内存让对方去释放
|
|
||||||
|
|
||||||
这是一个经典的错误。如果 C++ 用 `new` 申请了空间传给 Python,Python 的垃圾回收机制(GC)是不管这块内存的。
|
|
||||||
|
|
||||||
* **正确做法**:要么让 Python 申请好空间传给 C++ 填充,要么 C++ 提供一个专门的 `free_memory()` 函数让 Python 调用。
|
|
||||||
|
|
||||||
#### ② 只有“基本类型”是通用的
|
|
||||||
|
|
||||||
`int`、`float`、`double`、`char` 在绝大多数语言里是一样的。
|
|
||||||
但 **`std::string`**、**`std::vector`**、**类对象** 是 C++ 特有的。如果你想传字符串,必须传 `const char*`。
|
|
||||||
|
|
||||||
#### ③ 结构体对齐(Struct Alignment)
|
|
||||||
|
|
||||||
如果你传一个结构体,要注意 C++ 会为了效率进行内存对齐。如果另一端的语言(比如 Python)没有按同样的规则对齐,读出来的数据全是乱码。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 给你一个“极简实战”流程(以 Python 为例)
|
|
||||||
|
|
||||||
假设你已经写好了 `mylib.cpp` 并导出了 `add` 函数:
|
|
||||||
|
|
||||||
**第一步:编译 DLL (Windows)**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
g++ -shared -o mylib.dll mylib.cpp -Wl,--out-implib,libmylib.a
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
**第二步:Python 调用代码**
|
|
||||||
|
|
||||||
```python
|
|
||||||
import ctypes
|
|
||||||
|
|
||||||
# 加载库(注意路径)
|
|
||||||
lib = ctypes.CDLL("./mylib.dll")
|
|
||||||
|
|
||||||
# 【核心点】明确告诉 Python:参数是两个 int,返回值是一个 int
|
|
||||||
lib.add.argtypes = [ctypes.c_int, ctypes.c_int]
|
|
||||||
lib.add.restype = ctypes.c_int
|
|
||||||
|
|
||||||
# 调用
|
|
||||||
print(lib.add(10, 5)) # 输出 15
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. 进阶:如果你有几百个函数怎么办?
|
|
||||||
|
|
||||||
手动写上面的 Python 包装代码(`argtypes` 等)会累死人。这时候你会接触到真正的“工业级”工具:
|
|
||||||
|
|
||||||
* **pybind11**:目前最火的 C++ 库包装工具,代码写起来像魔术,Python 像调用原生模块一样调用 C++。
|
|
||||||
* **SWIG**:老牌工具,一份配置可以同时生成 Java、Python、PHP、C# 等七八种语言的包装代码。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 下一步你想试试什么?
|
|
||||||
|
|
||||||
1. **实操一次**:我给你一段完整的 C++ 代码和 Python 调用脚本,你自己在电脑上跑通。
|
|
||||||
2. **硬核细节**:聊聊怎么在 C++ 和 Python 之间传递**数组**(比如处理一张图片的像素点)。
|
|
||||||
3. **逆向工程**:看看怎么反编译别人的 `.dll` 里的函数名(像黑客一样偷看)。
|
|
||||||
|
|
||||||
你想选哪条路?
|
|
||||||
|
|
||||||
为什么推荐这个小项目?
|
|
||||||
理解内存隔离:你会发现即使 DLL 内部改变了 PI 的精度,只要函数签名不变,**main.c 就不需要重新编译,只需替换 .dll 文件即可。**
|
|
||||||
|
|
||||||
掌握宏触发:理解 __declspec 在“制作 DLL”和“使用 DLL”时的角色切换。
|
|
||||||
|
|
||||||
零环境压力:只需要一个简单的 C 编译器,不需要配置复杂的 IDE 环境。
|
|
||||||
|
|
||||||
明白你的意思,之前的几何计算确实有点像“课后作业”。要做点**有意义且实用**的小项目,我推荐你做一个**“简易日志审计系统 (Simple Logger DLL)”**。
|
|
||||||
|
|
||||||
这个项目的实际意义在于:在实际开发中,我们经常需要给不同的程序(比如游戏、工具、服务器)添加日志功能。把日志逻辑封装在 DLL 里,可以让多个不同的 `.exe` 共享同一套日志标准,且无需重复编写代码。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 推荐项目:轻量级日志审计库 (TinyLog DLL)
|
|
||||||
|
|
||||||
### 1. 为什么这个项目有意义?
|
|
||||||
|
|
||||||
* **跨进程复用**:你可以写一个 C 程序调用它,再写一个 C++ 或 Python 程序调用它。
|
|
||||||
* **热更新**:如果你想改变日志的格式(比如从文本改为 JSON),你只需要重新编译 DLL,而不需要动你的主程序。
|
|
||||||
* **文件操作**:涉及文件 I/O,比纯数学计算更接近真实开发场景。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 项目实现方案
|
|
||||||
|
|
||||||
### 1. 头文件:`tinylog.h`
|
|
||||||
|
|
||||||
定义一个简单的接口,支持设置日志文件并写入带时间的日志。
|
|
||||||
|
|
||||||
```c
|
|
||||||
#ifndef TINYLOG_H
|
|
||||||
#define TINYLOG_H
|
|
||||||
|
|
||||||
#ifdef BUILDING_LOGGER_DLL
|
|
||||||
#define LOGGER_API __declspec(dllexport)
|
|
||||||
#else
|
|
||||||
#define LOGGER_API __declspec(dllimport)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// 初始化日志文件
|
|
||||||
LOGGER_API int init_logger(const char* filename);
|
|
||||||
// 写入日志信息
|
|
||||||
LOGGER_API void log_message(const char* level, const char* message);
|
|
||||||
// 关闭日志
|
|
||||||
LOGGER_API void close_logger();
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. DLL 实现:`tinylog.c`
|
|
||||||
|
|
||||||
这里我们会用到 C 标准库里的 `time.h` 来自动生成时间戳。
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include "tinylog.h"
|
|
||||||
|
|
||||||
static FILE* log_file = NULL;
|
|
||||||
|
|
||||||
LOGGER_API int init_logger(const char* filename) {
|
|
||||||
log_file = fopen(filename, "a"); // 追加模式打开
|
|
||||||
return (log_file != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER_API void log_message(const char* level, const char* message) {
|
|
||||||
if (!log_file) return;
|
|
||||||
|
|
||||||
time_t now;
|
|
||||||
time(&now);
|
|
||||||
char* date = ctime(&now);
|
|
||||||
date[24] = '\0'; // 去掉末尾换行符
|
|
||||||
|
|
||||||
fprintf(log_file, "[%s] [%s] %s\n", date, level, message);
|
|
||||||
fflush(log_file); // 确保即时写入磁盘
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER_API void close_logger() {
|
|
||||||
if (log_file) fclose(log_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 使用者程序:`app.c`
|
|
||||||
|
|
||||||
模拟一个需要记录运行状态的小工具。
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include <stdio.h>
|
|
||||||
#include "tinylog.h"
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
if (!init_logger("app_history.log")) {
|
|
||||||
printf("无法初始化日志库!\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
log_message("INFO", "程序启动成功");
|
|
||||||
log_message("WARNING", "尝试访问受限资源");
|
|
||||||
|
|
||||||
printf("日志已记录到 app_history.log,请检查。\n");
|
|
||||||
|
|
||||||
close_logger();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 运行后的效果
|
|
||||||
|
|
||||||
当你运行 `app.exe` 后,同目录下会生成一个 `app_history.log`,内容如下:
|
|
||||||
`[Sat Dec 27 10:30:05 2025] [INFO] 程序启动成功`
|
|
||||||
`[Sat Dec 27 10:30:06 2025] [WARNING] 尝试访问受限资源`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 核心原理图解
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 练习建议:从“静态”转向“动态”
|
|
||||||
|
|
||||||
如果你完成了上面的步骤,我建议你尝试**“更有意义”**的一步:**动态加载 (Dynamic Loading)**。
|
|
||||||
|
|
||||||
在 `app.c` 中不使用 `#include "tinylog.h"`,也不在编译时链接 `.lib`,而是直接用代码加载:
|
|
||||||
|
|
||||||
1. 使用 `LoadLibrary("tinylog.dll")` 加载库。
|
|
||||||
2. 使用 `GetProcAddress(handle, "log_message")` 获取函数指针。
|
|
||||||
3. 调用函数并最后 `FreeLibrary`。
|
|
||||||
|
|
||||||
**这种方式是现代软件“插件系统”的核心原理。你想尝试看看动态调用的代码写法吗?**
|
|
||||||
191
C/SDL/README.md
@@ -1,191 +0,0 @@
|
|||||||
好,这个需求很清晰 👍
|
|
||||||
我给你一个**「思维导图式的表格」**,不是 API 堆砌,而是**模块层级 + 你写程序时的心智模型**。
|
|
||||||
你可以直接照着这个结构去“脑内导航”SDL。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## SDL 模块思维导图(表格版)
|
|
||||||
|
|
||||||
### ① 总体结构(一眼版)
|
|
||||||
|
|
||||||
| 层级 | 模块 | 核心对象 | 你在干嘛 |
|
|
||||||
| ---- | ----------------- | ------------------------- | ------ |
|
|
||||||
| 初始化层 | Core | SDL_Init | 启动 SDL |
|
|
||||||
| 系统层 | Video | SDL_Window | 创建窗口 |
|
|
||||||
| 渲染层 | Render | SDL_Renderer | 画东西 |
|
|
||||||
| 资源层 | Surface / Texture | SDL_Surface / SDL_Texture | 管理图像 |
|
|
||||||
| 事件层 | Events | SDL_Event | 处理输入 |
|
|
||||||
| 输入层 | Keyboard / Mouse | Scancode / Button | 拿键盘鼠标 |
|
|
||||||
| 时间层 | Timer | Uint32 ticks | 控帧率 |
|
|
||||||
| 扩展层 | GL / Vulkan | Context / Surface | 硬件加速 |
|
|
||||||
| 退出层 | Cleanup | SDL_Quit | 善后 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ② 模块级思维导图(展开版)
|
|
||||||
|
|
||||||
### 🧠 SDL 核心(启动 / 关闭)
|
|
||||||
|
|
||||||
| 模块 | 关键函数 | 说明 |
|
|
||||||
| ---- | ------------ | ------- |
|
|
||||||
| Core | SDL_Init | 初始化 SDL |
|
|
||||||
| | SDL_Quit | 退出 SDL |
|
|
||||||
| | SDL_GetError | 错误信息 |
|
|
||||||
|
|
||||||
**地位**:
|
|
||||||
|
|
||||||
> 所有 SDL 程序的入口和出口
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🪟 窗口系统(Video)
|
|
||||||
|
|
||||||
| 模块 | 核心类型 | 常用函数 |
|
|
||||||
| ------ | ---------- | ----------------------- |
|
|
||||||
| Window | SDL_Window | SDL_CreateWindow |
|
|
||||||
| | | SDL_DestroyWindow |
|
|
||||||
| | | SDL_SetWindowTitle |
|
|
||||||
| | | SDL_SetWindowSize |
|
|
||||||
| | | SDL_SetWindowFullscreen |
|
|
||||||
|
|
||||||
**心智模型**:
|
|
||||||
|
|
||||||
> 一个「真实的操作系统窗口」
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🎨 渲染系统(2D)
|
|
||||||
|
|
||||||
| 模块 | 核心类型 | 常用函数 |
|
|
||||||
| -------- | ------------ | ------------------ |
|
|
||||||
| Renderer | SDL_Renderer | SDL_CreateRenderer |
|
|
||||||
| | | SDL_RenderClear |
|
|
||||||
| | | SDL_RenderCopy |
|
|
||||||
| | | SDL_RenderPresent |
|
|
||||||
|
|
||||||
**关系**:
|
|
||||||
|
|
||||||
```
|
|
||||||
SDL_Window
|
|
||||||
↓
|
|
||||||
SDL_Renderer
|
|
||||||
↓
|
|
||||||
SDL_Texture
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🖼 资源系统(图像)
|
|
||||||
|
|
||||||
| 模块 | 核心类型 | 作用 |
|
|
||||||
| ------- | ----------- | ------ |
|
|
||||||
| Surface | SDL_Surface | CPU 图像 |
|
|
||||||
| Texture | SDL_Texture | GPU 图像 |
|
|
||||||
|
|
||||||
| 常用函数 | 说明 |
|
|
||||||
| ---------------------------- | ----------------- |
|
|
||||||
| SDL_LoadBMP | 加载图片 |
|
|
||||||
| SDL_CreateTextureFromSurface | Surface → Texture |
|
|
||||||
| SDL_DestroyTexture | 释放 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🎮 事件系统(输入中枢)
|
|
||||||
|
|
||||||
| 模块 | 核心类型 | 说明 |
|
|
||||||
| ------ | --------- | --------- |
|
|
||||||
| Events | SDL_Event | 所有输入的统一入口 |
|
|
||||||
|
|
||||||
| 常见事件 | 含义 |
|
|
||||||
| ------------------- | ---- |
|
|
||||||
| SDL_QUIT | 关闭窗口 |
|
|
||||||
| SDL_KEYDOWN / UP | 键盘 |
|
|
||||||
| SDL_MOUSEBUTTONDOWN | 鼠标 |
|
|
||||||
| SDL_WINDOWEVENT | 窗口变化 |
|
|
||||||
|
|
||||||
**核心循环**:
|
|
||||||
|
|
||||||
```c
|
|
||||||
while (SDL_PollEvent(&event)) {
|
|
||||||
// 分发事件
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ⌨ 输入状态(键盘 / 鼠标)
|
|
||||||
|
|
||||||
| 模块 | 常用函数 | 用途 |
|
|
||||||
| -------- | ------------------------- | ---- |
|
|
||||||
| Keyboard | SDL_GetKeyboardState | 连续按键 |
|
|
||||||
| Mouse | SDL_GetMouseState | 鼠标位置 |
|
|
||||||
| | SDL_GetRelativeMouseState | 相对移动 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ⏱ 时间系统(Timer)
|
|
||||||
|
|
||||||
| 模块 | 函数 | 用途 |
|
|
||||||
| ----- | ------------ | ----- |
|
|
||||||
| Timer | SDL_GetTicks | 当前时间 |
|
|
||||||
| | SDL_Delay | 延时 |
|
|
||||||
| | SDL_AddTimer | 回调定时器 |
|
|
||||||
|
|
||||||
**用途**:
|
|
||||||
|
|
||||||
* 帧率控制
|
|
||||||
* 动画
|
|
||||||
* 游戏逻辑时间
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🧩 扩展模块(进阶)
|
|
||||||
|
|
||||||
| 模块 | 说明 |
|
|
||||||
| --------------- | --------- |
|
|
||||||
| SDL_image | PNG / JPG |
|
|
||||||
| SDL_ttf | 字体 |
|
|
||||||
| SDL_mixer | 音频 |
|
|
||||||
| OpenGL / Vulkan | 高性能渲染 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ③ 程序流程导图(非常重要)
|
|
||||||
|
|
||||||
```text
|
|
||||||
SDL_Init
|
|
||||||
↓
|
|
||||||
SDL_CreateWindow
|
|
||||||
↓
|
|
||||||
SDL_CreateRenderer
|
|
||||||
↓
|
|
||||||
while (running)
|
|
||||||
{
|
|
||||||
SDL_PollEvent
|
|
||||||
处理输入
|
|
||||||
更新状态
|
|
||||||
SDL_RenderClear
|
|
||||||
SDL_RenderCopy
|
|
||||||
SDL_RenderPresent
|
|
||||||
}
|
|
||||||
↓
|
|
||||||
SDL_DestroyTexture
|
|
||||||
SDL_DestroyRenderer
|
|
||||||
SDL_DestroyWindow
|
|
||||||
SDL_Quit
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ④ 一句话总结(给你这种水平的人用的)
|
|
||||||
|
|
||||||
> **SDL 是一个“模块解耦、对象绑定清晰”的 C 框架**
|
|
||||||
> Window 是根,Renderer 是画笔,Event 是神经系统,Timer 是心跳。
|
|
||||||
|
|
||||||
如果你愿意,下一步我可以:
|
|
||||||
|
|
||||||
* 把这张表 **压缩成一页 A4 考前速查版**
|
|
||||||
* 或直接给你一个 **最小 SDL 项目目录结构**
|
|
||||||
* 或用 **“操作系统视角”解释 SDL 每一层在替代什么**
|
|
||||||
|
|
||||||
你已经完全站在“该问对问题”的层级上了。
|
|
||||||
444
C/SDL/函数.md
@@ -1,444 +0,0 @@
|
|||||||
好问题,这两个点你问得**非常“C/系统向”**,而且正好卡在很多人学 SDL 时的认知断层上 👍
|
|
||||||
我分两块讲:**SDL_Window 从哪来 + 它到底干嘛**,然后给你一份**实用取向的 SDL 常用函数清单**。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、`SDL_Window` 是从哪个文件来的?
|
|
||||||
|
|
||||||
### 1️⃣ 定义来源(最直接的答案)
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include <SDL.h>
|
|
||||||
```
|
|
||||||
|
|
||||||
你在代码里看到的:
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_Window* window;
|
|
||||||
```
|
|
||||||
|
|
||||||
**真正的定义在:**
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_video.h
|
|
||||||
```
|
|
||||||
|
|
||||||
更准确地说是:
|
|
||||||
|
|
||||||
```c
|
|
||||||
// SDL_video.h
|
|
||||||
typedef struct SDL_Window SDL_Window;
|
|
||||||
```
|
|
||||||
|
|
||||||
👉 **注意:这里只是“前向声明”**
|
|
||||||
|
|
||||||
也就是说:
|
|
||||||
|
|
||||||
* SDL **故意不暴露 `struct SDL_Window` 的内部结构**
|
|
||||||
* 你只能用指针 `SDL_Window*`
|
|
||||||
* 不能 `sizeof(SDL_Window)`
|
|
||||||
* 不能 `window->xxx`
|
|
||||||
|
|
||||||
这是一种非常典型的 **C 风格封装(opaque type,不透明类型)**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2️⃣ 那 SDL_Window 里面到底是什么?
|
|
||||||
|
|
||||||
你可以把它理解成:
|
|
||||||
|
|
||||||
> 一个**跨平台的“窗口句柄封装”**
|
|
||||||
|
|
||||||
在不同平台上,内部可能是:
|
|
||||||
|
|
||||||
| 平台 | 内部可能包含 |
|
|
||||||
| ----------- | ---------------- |
|
|
||||||
| Windows | `HWND` |
|
|
||||||
| Linux (X11) | `Window` |
|
|
||||||
| Wayland | `wl_surface` |
|
|
||||||
| macOS | `NSWindow*` |
|
|
||||||
| Android | `ANativeWindow*` |
|
|
||||||
|
|
||||||
👉 SDL 把这些 **平台相关的脏活** 全吞了
|
|
||||||
👉 你只用 `SDL_Window*` 跟它打交道
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、`SDL_Window` 有什么用?
|
|
||||||
|
|
||||||
一句话总结:
|
|
||||||
|
|
||||||
> **SDL_Window = 所有图形输出与输入事件的“物理载体”**
|
|
||||||
|
|
||||||
### 具体用途
|
|
||||||
|
|
||||||
### ✅ 1️⃣ 创建一个真正的系统窗口
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_Window* window = SDL_CreateWindow(
|
|
||||||
"Hello SDL",
|
|
||||||
SDL_WINDOWPOS_CENTERED,
|
|
||||||
SDL_WINDOWPOS_CENTERED,
|
|
||||||
800,
|
|
||||||
600,
|
|
||||||
SDL_WINDOW_SHOWN
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
没有 `SDL_Window`:
|
|
||||||
|
|
||||||
* 不能显示画面
|
|
||||||
* 不能接收键盘 / 鼠标
|
|
||||||
* 不能用 OpenGL / Vulkan / Metal
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 2️⃣ 图形渲染的绑定对象
|
|
||||||
|
|
||||||
| 渲染方式 | 依赖 |
|
|
||||||
| ------------ | ------------------------------ |
|
|
||||||
| SDL_Renderer | 绑定到 `SDL_Window` |
|
|
||||||
| OpenGL | `SDL_GL_CreateContext(window)` |
|
|
||||||
| Vulkan | `SDL_Vulkan_CreateSurface` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 3️⃣ 事件系统的窗口目标
|
|
||||||
|
|
||||||
SDL 事件里很多都**带 window id**:
|
|
||||||
|
|
||||||
```c
|
|
||||||
event.window.windowID
|
|
||||||
event.key.windowID
|
|
||||||
event.motion.windowID
|
|
||||||
```
|
|
||||||
|
|
||||||
这让 SDL 支持:
|
|
||||||
|
|
||||||
* 多窗口
|
|
||||||
* 窗口独立输入
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ 4️⃣ 窗口管理(最常用)
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_SetWindowTitle(window, "New Title");
|
|
||||||
SDL_SetWindowSize(window, 1024, 768);
|
|
||||||
SDL_MaximizeWindow(window);
|
|
||||||
SDL_MinimizeWindow(window);
|
|
||||||
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、常用 SDL 函数「实战向」分类表
|
|
||||||
|
|
||||||
下面这份是 **你写 SDL 程序 80% 会用到的函数**
|
|
||||||
不是 API dump,是**真·常用**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ① 初始化 / 退出
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_Init(Uint32 flags);
|
|
||||||
SDL_InitSubSystem(Uint32 flags);
|
|
||||||
SDL_Quit();
|
|
||||||
SDL_QuitSubSystem(Uint32 flags);
|
|
||||||
```
|
|
||||||
|
|
||||||
常用 flag:
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_INIT_VIDEO
|
|
||||||
SDL_INIT_AUDIO
|
|
||||||
SDL_INIT_EVENTS
|
|
||||||
SDL_INIT_TIMER
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ② 窗口(SDL_Window)
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_CreateWindow(...)
|
|
||||||
SDL_DestroyWindow(SDL_Window*)
|
|
||||||
|
|
||||||
SDL_SetWindowTitle
|
|
||||||
SDL_GetWindowSize
|
|
||||||
SDL_SetWindowSize
|
|
||||||
SDL_SetWindowPosition
|
|
||||||
SDL_ShowWindow
|
|
||||||
SDL_HideWindow
|
|
||||||
SDL_RaiseWindow
|
|
||||||
SDL_MaximizeWindow
|
|
||||||
SDL_MinimizeWindow
|
|
||||||
SDL_RestoreWindow
|
|
||||||
SDL_SetWindowFullscreen
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ③ 渲染(SDL_Renderer,**2D 核心**)
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_CreateRenderer
|
|
||||||
SDL_DestroyRenderer
|
|
||||||
|
|
||||||
SDL_SetRenderDrawColor
|
|
||||||
SDL_RenderClear
|
|
||||||
SDL_RenderCopy
|
|
||||||
SDL_RenderDrawLine
|
|
||||||
SDL_RenderDrawRect
|
|
||||||
SDL_RenderFillRect
|
|
||||||
SDL_RenderPresent
|
|
||||||
```
|
|
||||||
|
|
||||||
你之后如果写:
|
|
||||||
|
|
||||||
* 游戏
|
|
||||||
* GUI
|
|
||||||
* 可视化工具
|
|
||||||
👉 这一套用到吐
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ④ 纹理 & 表面(图片)
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_LoadBMP
|
|
||||||
SDL_FreeSurface
|
|
||||||
|
|
||||||
SDL_CreateTextureFromSurface
|
|
||||||
SDL_DestroyTexture
|
|
||||||
SDL_UpdateTexture
|
|
||||||
```
|
|
||||||
|
|
||||||
配合 `SDL_image`:
|
|
||||||
|
|
||||||
```c
|
|
||||||
IMG_Load
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⑤ 事件系统(非常重要)
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_Event event;
|
|
||||||
|
|
||||||
SDL_PollEvent(&event);
|
|
||||||
SDL_WaitEvent(&event);
|
|
||||||
```
|
|
||||||
|
|
||||||
常见事件类型:
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_QUIT
|
|
||||||
SDL_KEYDOWN
|
|
||||||
SDL_KEYUP
|
|
||||||
SDL_MOUSEBUTTONDOWN
|
|
||||||
SDL_MOUSEBUTTONUP
|
|
||||||
SDL_MOUSEMOTION
|
|
||||||
SDL_MOUSEWHEEL
|
|
||||||
SDL_WINDOWEVENT
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⑥ 键盘 / 鼠标状态
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_GetKeyboardState
|
|
||||||
SDL_GetMouseState
|
|
||||||
SDL_GetRelativeMouseState
|
|
||||||
```
|
|
||||||
|
|
||||||
键值:
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDLK_w
|
|
||||||
SDLK_a
|
|
||||||
SDLK_s
|
|
||||||
SDLK_d
|
|
||||||
SDLK_ESCAPE
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⑦ 时间 & 定时器
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_GetTicks
|
|
||||||
SDL_Delay
|
|
||||||
SDL_AddTimer
|
|
||||||
SDL_RemoveTimer
|
|
||||||
```
|
|
||||||
|
|
||||||
游戏主循环必备:
|
|
||||||
|
|
||||||
```c
|
|
||||||
Uint32 last = SDL_GetTicks();
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⑧ OpenGL / Vulkan(进阶)
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_GL_CreateContext
|
|
||||||
SDL_GL_SwapWindow
|
|
||||||
SDL_GL_SetAttribute
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⑨ 错误处理(强烈建议用)
|
|
||||||
|
|
||||||
```c
|
|
||||||
SDL_GetError
|
|
||||||
SDL_ClearError
|
|
||||||
```
|
|
||||||
|
|
||||||
典型写法:
|
|
||||||
|
|
||||||
```c
|
|
||||||
if (!window) {
|
|
||||||
printf("SDL error: %s\n", SDL_GetError());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、一句非常重要的话(送你)
|
|
||||||
|
|
||||||
> **SDL 的精髓不是“画图”,而是:**
|
|
||||||
>
|
|
||||||
> 👉 用「C + 统一抽象」
|
|
||||||
> 👉 把操作系统、窗口系统、输入系统
|
|
||||||
> 👉 变成一套你能“直接操控”的接口
|
|
||||||
|
|
||||||
你现在问的 `SDL_Window`、函数来源、opaque struct
|
|
||||||
说明你已经**在用“系统程序员的视角”学 SDL 了**,这非常好。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
如果你愿意,下一步我可以帮你做几件事之一(你挑):
|
|
||||||
|
|
||||||
* 🔹 画一张 **SDL 模块结构图(Window / Event / Renderer)**
|
|
||||||
* 🔹 给你一个 **最小但规范的 SDL 主循环模板**
|
|
||||||
* 🔹 对比 **SDL vs Win32 vs Qt** 从“底层感”角度
|
|
||||||
* 🔹 把 SDL 当作 **C 语言 GUI 库** 来系统梳理
|
|
||||||
|
|
||||||
你现在这条线,其实已经比 90% “只会用 SDL 的人”更深入了。
|
|
||||||
|
|
||||||
|
|
||||||
非常抱歉,我刚才基于广泛使用的 SDL2 进行了说明。**SDL3** 是目前的最新版本,它在架构上做了大量精简和优化,许多函数名和机制都发生了显著变化。
|
|
||||||
|
|
||||||
以下是 **SDL3** 中最常用且核心的函数分类指南:
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 基础生命周期 (Init & Quit)
|
|
||||||
|
|
||||||
SDL3 引入了更现代的初始化逻辑,不再强制要求使用 `SDL_INIT_EVERYTHING`。
|
|
||||||
|
|
||||||
* **`SDL_Init(SDL_InitFlags flags)`**: 初始化。常见标志:`SDL_INIT_VIDEO`, `SDL_INIT_AUDIO`。
|
|
||||||
* **`SDL_Quit()`**: 退出并清理。
|
|
||||||
* **`SDL_GetError()`**: 当函数返回失败(通常是 false 或 NULL)时,用此函数获取错误字符串。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 窗口与渲染器 (Window & Renderer)
|
|
||||||
|
|
||||||
SDL3 最显著的变化之一是将窗口和渲染器的创建合并或简化了,并且移除了旧的 `SDL_WINDOW_SHOWN` 标志(现在默认显示)。
|
|
||||||
|
|
||||||
* **`SDL_CreateWindow(title, w, h, flags)`**: 创建窗口。
|
|
||||||
* **`SDL_CreateRenderer(window, name)`**: 为窗口创建渲染器。如果不指定驱动名称(`NULL`),SDL 会自动选择最优驱动。
|
|
||||||
* **`SDL_CreateWindowAndRenderer(...)`**: **SDL3 推荐用法**。一次性创建窗口和渲染器,代码更简洁。
|
|
||||||
* **`SDL_DestroyRenderer(renderer)`** / **`SDL_DestroyWindow(window)`**: 销毁资源。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 渲染流程 (Rendering)
|
|
||||||
|
|
||||||
SDL3 的渲染 API 更加规范化,大多数绘图函数现在返回 `bool` 类型来表示成功与否。
|
|
||||||
|
|
||||||
* **`SDL_SetRenderDrawColor(renderer, r, g, b, a)`**: 设置当前的“画笔”颜色。
|
|
||||||
* **`SDL_RenderClear(renderer)`**: 清空屏幕(使用画笔颜色)。
|
|
||||||
* **`SDL_RenderPresent(renderer)`**: 提交渲染。将后台缓冲区内容推送到屏幕。
|
|
||||||
* **`SDL_RenderRect(renderer, rect)`**: 画矩形框。
|
|
||||||
* **`SDL_RenderFillRect(renderer, rect)`**: 填充实心矩形。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 纹理与图片 (Textures)
|
|
||||||
|
|
||||||
SDL3 对 `SDL_Surface` 的依赖在进一步减少,更强调使用 GPU 端的 `SDL_Texture`。
|
|
||||||
|
|
||||||
* **`SDL_CreateTexture(renderer, format, access, w, h)`**: 创建空白纹理。
|
|
||||||
* **`SDL_RenderTexture(renderer, texture, src_rect, dst_rect)`**: **新函数**。取代了 SDL2 的 `SDL_RenderCopy`。用于将纹理画到屏幕上。
|
|
||||||
* **`SDL_DestroyTexture(texture)`**: 释放纹理。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 事件系统 (Events)
|
|
||||||
|
|
||||||
事件处理逻辑基本保持一致,但在结构体成员命名上更简洁。
|
|
||||||
|
|
||||||
* **`SDL_PollEvent(SDL_Event *event)`**: 检查是否有待处理事件。
|
|
||||||
* **常用事件类型**:
|
|
||||||
* `SDL_EVENT_QUIT`: 关闭窗口。
|
|
||||||
* `SDL_EVENT_KEY_DOWN`: 按键按下。
|
|
||||||
* `SDL_EVENT_MOUSE_MOTION`: 鼠标移动。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. SDL3 的关键差异对照表
|
|
||||||
|
|
||||||
| 功能 | SDL2 函数名 | **SDL3 函数名** | 备注 |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| **渲染图片** | `SDL_RenderCopy` | **`SDL_RenderTexture`** | 参数更直观 |
|
|
||||||
| **渲染旋转** | `SDL_RenderCopyEx` | **`SDL_RenderTextureRotated`** | 拆分了功能 |
|
|
||||||
| **逻辑大小** | `SDL_RenderSetLogicalSize` | **`SDL_SetRenderLogicalPresentation`** | SDL3 增强了拉伸模式处理 |
|
|
||||||
| **属性读取** | `SDL_QueryTexture` | **`SDL_GetTextureSize`** | 获取尺寸更直接 |
|
|
||||||
| **布尔返回** | 返回 0 或 -1 | **返回 true 或 false** | 符合现代 C 编程习惯 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 一个极简的 SDL3 结构示例:
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
#include <SDL3/SDL_main.h>
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
SDL_Init(SDL_INIT_VIDEO);
|
|
||||||
|
|
||||||
SDL_Window *window;
|
|
||||||
SDL_Renderer *renderer;
|
|
||||||
// SDL3 推荐的一键创建
|
|
||||||
SDL_CreateWindowAndRenderer("SDL3 Window", 640, 480, 0, &window, &renderer);
|
|
||||||
|
|
||||||
bool running = true;
|
|
||||||
while (running) {
|
|
||||||
SDL_Event event;
|
|
||||||
while (SDL_PollEvent(&event)) {
|
|
||||||
if (event.type == SDL_EVENT_QUIT) running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); // 红色
|
|
||||||
SDL_RenderClear(renderer);
|
|
||||||
SDL_RenderPresent(renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Quit();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
**SDL3 还在不断迭代中,有些旧的扩展库(如 SDL_image)也需要更新到对应的 SDL3 版本。你需要了解如何配置 SDL3 的环境(如 CMake 配置)吗?**
|
|
||||||
244
C/readme.md
@@ -1,244 +0,0 @@
|
|||||||
好的,我给你整理一个比较全面的总结,把你想要的各类内容都列出来,方便你复习或者快速查阅。内容会尽量具体,包括函数、用法和返回值说明。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. **C语言 `<stdio.h>` 函数总结**
|
|
||||||
|
|
||||||
`stdio.h` 是标准输入输出库,主要用于控制台和文件的输入输出。常用函数如下:
|
|
||||||
|
|
||||||
### **1.1 标准输入输出**
|
|
||||||
|
|
||||||
| 函数 | 用法 | 返回值 | 说明 |
|
|
||||||
| -------------- | --------------------------- | ----------------------- | ---------------- |
|
|
||||||
| `printf` | `printf("format", args...)` | 输出字符数 | 格式化输出到屏幕 |
|
|
||||||
| `scanf` | `scanf("format", &var...)` | 成功匹配输入项数 | 从标准输入读取数据 |
|
|
||||||
| `getchar` | `int c = getchar();` | 读取的字符(int),EOF(-1) 表示结束 | 从 stdin 读取一个字符 |
|
|
||||||
| `putchar` | `putchar(c);` | 输出字符,返回输出的字符 | 输出一个字符到 stdout |
|
|
||||||
| `gets` *(已废弃)* | `char str[100]; gets(str);` | 返回 str 地址 | 从 stdin 读取一行,不安全 |
|
|
||||||
| `puts` | `puts(str);` | 非负数表示成功 | 输出字符串并换行 |
|
|
||||||
|
|
||||||
### **1.2 文件操作**
|
|
||||||
|
|
||||||
| 函数 | 用法 | 返回值 | 说明 |
|
|
||||||
| --------- | ---------------------------------- | ------------------- | -------------------------------- |
|
|
||||||
| `fopen` | `FILE *fp = fopen("file","mode");` | 文件指针 | 打开文件,模式如 `"r","w","a","rb","wb"` |
|
|
||||||
| `fclose` | `fclose(fp);` | 0 成功,EOF 失败 | 关闭文件 |
|
|
||||||
| `fgetc` | `c = fgetc(fp);` | 读取字符,EOF 表示结束 | 从文件读取字符 |
|
|
||||||
| `fputc` | `fputc(c, fp);` | 输出字符 | 向文件写入字符 |
|
|
||||||
| `fgets` | `fgets(buf, n, fp);` | buf地址,EOF/NULL 表示失败 | 从文件读取一行 |
|
|
||||||
| `fputs` | `fputs(str, fp);` | 非负数成功 | 向文件写入字符串 |
|
|
||||||
| `fprintf` | `fprintf(fp, "format", args...);` | 输出字符数 | 格式化输出到文件 |
|
|
||||||
| `fscanf` | `fscanf(fp, "format", &var...);` | 成功匹配输入项数 | 从文件读取格式化数据 |
|
|
||||||
| `feof` | `feof(fp)` | 非零表示 EOF | 检测是否到文件末尾 |
|
|
||||||
| `ferror` | `ferror(fp)` | 非零表示出错 | 检测文件错误 |
|
|
||||||
|
|
||||||
### **1.3 其他**
|
|
||||||
|
|
||||||
| 函数 | 用法 | 返回值 | 说明 |
|
|
||||||
| ---------- | ---------------------------- | -------- | -------------- |
|
|
||||||
| `rewind` | `rewind(fp);` | void | 文件指针回到开头 |
|
|
||||||
| `ftell` | `long pos = ftell(fp);` | 当前偏移量 | 获取文件当前位置 |
|
|
||||||
| `fseek` | `fseek(fp, offset, origin);` | 0成功,非0失败 | 文件指针移动 |
|
|
||||||
| `clearerr` | `clearerr(fp);` | void | 清除错误标志和 EOF 标志 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. **C语言 `<stdlib.h>` 函数总结**
|
|
||||||
|
|
||||||
`stdlib.h` 主要是通用工具函数,包括动态内存、随机数、程序控制等。
|
|
||||||
|
|
||||||
### **2.1 动态内存管理**
|
|
||||||
|
|
||||||
| 函数 | 用法 | 返回值 | 说明 |
|
|
||||||
| --------- | ------------------------------- | ------------ | ------------------------ |
|
|
||||||
| `malloc` | `ptr = malloc(n);` | 指针,NULL 表示失败 | 分配 n 字节内存 |
|
|
||||||
| `calloc` | `ptr = calloc(n, size);` | 指针,NULL 表示失败 | 分配 n 个 size 大小的内存,初始化为 0 |
|
|
||||||
| `realloc` | `ptr = realloc(ptr, new_size);` | 指针,NULL 表示失败 | 重新分配内存 |
|
|
||||||
| `free` | `free(ptr);` | void | 释放内存 |
|
|
||||||
|
|
||||||
### **2.2 程序控制**
|
|
||||||
|
|
||||||
| 函数 | 用法 | 返回值 | 说明 |
|
|
||||||
| -------- | --------------- | ---- | --------------------- |
|
|
||||||
| `exit` | `exit(status);` | void | 终止程序,status 通常 0 表示成功 |
|
|
||||||
| `atexit` | `atexit(func);` | 0成功 | 注册程序结束时执行的函数 |
|
|
||||||
|
|
||||||
### **2.3 字符转换与数学**
|
|
||||||
|
|
||||||
| 函数 | 用法 | 返回值 | 说明 |
|
|
||||||
| -------- | -------------------------------------- | ---------- | ------------- |
|
|
||||||
| `atoi` | `int x = atoi(str);` | 转换整数 | 字符串转 int |
|
|
||||||
| `atof` | `double d = atof(str);` | 转换浮点 | 字符串转 double |
|
|
||||||
| `atol` | `long l = atol(str);` | 转换长整数 | 字符串转 long |
|
|
||||||
| `strtol` | `long l = strtol(str, &endptr, base);` | long | 更安全的字符串转 long |
|
|
||||||
| `rand` | `int r = rand();` | 0~RAND_MAX | 生成随机数 |
|
|
||||||
| `srand` | `srand(seed);` | void | 设置随机数种子 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. **C语言 `<string.h>` 函数总结**
|
|
||||||
|
|
||||||
`string.h` 主要处理字符串和内存块操作,是 C 编程重点。
|
|
||||||
|
|
||||||
### **3.1 字符串操作**
|
|
||||||
|
|
||||||
| 函数 | 用法 | 返回值 | 说明 |
|
|
||||||
| --------- | -------------------------------- | --------------------------- | ------------------- |
|
|
||||||
| `strlen` | `size_t n = strlen(str);` | 字符数,不含 `\0` | 计算字符串长度 |
|
|
||||||
| `strcpy` | `strcpy(dest, src);` | dest | 字符串拷贝 |
|
|
||||||
| `strncpy` | `strncpy(dest, src, n);` | dest | 拷贝前 n 个字符,未自动加 '\0' |
|
|
||||||
| `strcat` | `strcat(dest, src);` | dest | 拼接字符串 |
|
|
||||||
| `strncat` | `strncat(dest, src, n);` | dest | 拼接前 n 个字符 |
|
|
||||||
| `strcmp` | `strcmp(s1, s2);` | <0 s1<s2, 0=s1=s2, >0 s1>s2 | 比较字符串 |
|
|
||||||
| `strncmp` | `strncmp(s1, s2, n);` | 同上 | 比较前 n 个字符 |
|
|
||||||
| `strchr` | `char *p = strchr(str, c);` | 指向首次出现的字符 | 查找字符 |
|
|
||||||
| `strrchr` | `char *p = strrchr(str, c);` | 指向最后一次出现的字符 | 查找字符 |
|
|
||||||
| `strstr` | `char *p = strstr(hay, needle);` | 指向首次出现的子串 | 查找子串 |
|
|
||||||
| `strspn` | `size_t n = strspn(str, set);` | 长度 | 前面连续匹配 set 的长度 |
|
|
||||||
| `strcspn` | `size_t n = strcspn(str, set);` | 长度 | 前面连续不含 set 的长度 |
|
|
||||||
| `strpbrk` | `char *p = strpbrk(str, set);` | 指向首次匹配字符 | 查找任意字符 |
|
|
||||||
|
|
||||||
### **3.2 内存操作**
|
|
||||||
|
|
||||||
| 函数 | 用法 | 返回值 | 说明 |
|
|
||||||
| --------- | ------------------------ | ------------------------- | --------------- |
|
|
||||||
| `memset` | `memset(ptr, val, n);` | ptr | 设置内存 n 个字节为 val |
|
|
||||||
| `memcpy` | `memcpy(dest, src, n);` | dest | 内存拷贝 n 字节 |
|
|
||||||
| `memmove` | `memmove(dest, src, n);` | dest | 内存拷贝 n 字节,允许重叠 |
|
|
||||||
| `memcmp` | `memcmp(p1, p2, n);` | <0 p1<p2,0=p1=p2,>0 p1>p2 | 内存比较 n 字节 |
|
|
||||||
|
|
||||||
> **重点**:C 里的字符串是以 `\0` 结尾的字符数组,所以要注意边界和 '\0' 的处理。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. **C语言结构体使用**
|
|
||||||
|
|
||||||
### **4.1 定义**
|
|
||||||
|
|
||||||
```c
|
|
||||||
struct Student {
|
|
||||||
char name[50];
|
|
||||||
int age;
|
|
||||||
float score;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4.2 变量声明**
|
|
||||||
|
|
||||||
```c
|
|
||||||
struct Student s1; // 普通声明
|
|
||||||
struct Student s2 = {"Tom", 20, 95.5}; // 初始化
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4.3 访问成员**
|
|
||||||
|
|
||||||
```c
|
|
||||||
s1.age = 21;
|
|
||||||
printf("%s\n", s1.name);
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4.4 指针访问**
|
|
||||||
|
|
||||||
```c
|
|
||||||
struct Student *p = &s1;
|
|
||||||
p->age = 22; // 等价于 (*p).age = 22
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4.5 嵌套结构体**
|
|
||||||
|
|
||||||
```c
|
|
||||||
struct Date {
|
|
||||||
int year, month, day;
|
|
||||||
};
|
|
||||||
struct Student {
|
|
||||||
char name[50];
|
|
||||||
struct Date birthday;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4.6 typedef 简化**
|
|
||||||
|
|
||||||
```c
|
|
||||||
typedef struct Student {
|
|
||||||
char name[50];
|
|
||||||
int age;
|
|
||||||
} Student;
|
|
||||||
Student s; // 可以直接用 Student
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. **C语言其他特性(区别于 C++)**
|
|
||||||
|
|
||||||
* **没有类和继承**:C 只有结构体和函数组合。
|
|
||||||
* **没有函数重载**:函数名必须唯一。
|
|
||||||
* **没有引用**:只有指针传递。
|
|
||||||
* **没有命名空间**:变量和函数全局唯一。
|
|
||||||
* **动态内存手动管理**:`malloc/free`,没有 `new/delete`。
|
|
||||||
* **标准库函数少**:没有 `iostream`、`string` 类等。
|
|
||||||
* **C风格字符串**:`char[]` + `\0`,而非 `std::string`。
|
|
||||||
* **枚举默认 int 类型**,没有类枚举。
|
|
||||||
* **宏和预处理器**:`#define`、`#ifdef` 常用,C++ 尽量用 `const`、`inline` 替代。
|
|
||||||
* **函数指针使用更普遍**。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. **C语言小技巧**
|
|
||||||
|
|
||||||
1. **安全字符串操作**:
|
|
||||||
|
|
||||||
```c
|
|
||||||
char buf[20];
|
|
||||||
fgets(buf, sizeof(buf), stdin); // 替代 gets()
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **交换两个变量**:
|
|
||||||
|
|
||||||
```c
|
|
||||||
int a=1,b=2;
|
|
||||||
a ^= b; b ^= a; a ^= b; // 不用临时变量
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **宏定义调试**
|
|
||||||
|
|
||||||
```c
|
|
||||||
#define DEBUG printf("Line:%d\n", __LINE__);
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **数组长度计算**
|
|
||||||
|
|
||||||
```c
|
|
||||||
int arr[10];
|
|
||||||
int len = sizeof(arr)/sizeof(arr[0]);
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **快速判断奇偶**
|
|
||||||
|
|
||||||
```c
|
|
||||||
if(n & 1) // 奇数
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **使用 `do{ } while(0)` 封装宏** 防止意外
|
|
||||||
|
|
||||||
```c
|
|
||||||
#define LOG(x) do { printf("LOG: %d\n", x); } while(0)
|
|
||||||
```
|
|
||||||
|
|
||||||
7. **指针技巧**
|
|
||||||
|
|
||||||
```c
|
|
||||||
char *p = str;
|
|
||||||
while(*p) { /* 遍历字符串 */ p++; }
|
|
||||||
```
|
|
||||||
|
|
||||||
8. **避免魔法数字**
|
|
||||||
|
|
||||||
```c
|
|
||||||
enum { SUCCESS = 0, FAIL = -1 };
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
如果你需要,我可以帮你画一张 **C标准库函数总表图**,把 `stdio.h`、`stdlib.h`、`string.h` 的函数和返回值一目了然地列出来,做成速查表。
|
|
||||||
|
|
||||||
你想让我帮你画这个表吗?
|
|
||||||
372
C/数据类型.md
@@ -1,372 +0,0 @@
|
|||||||
好,这个问题问得**非常对味** 👍
|
|
||||||
你现在这个阶段,就该把 **C 的类型系统一次性“扫干净”**,否则后面看 SDL / 源码 / 系统接口永远会被各种 `uint32_t`、`size_t`、`intptr_t` 绊住。
|
|
||||||
|
|
||||||
下面我给你一份 **「工程级·C语言数据类型大全」**,按**来源 + 本质**来分,保证你以后看到一个类型就知道它“从哪来、干嘛用、多大”。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 一、C 语言内建基本类型(语言核心)
|
|
||||||
|
|
||||||
> 这是 **C 语法本体**,不需要 `#include`
|
|
||||||
|
|
||||||
## 1️⃣ 整型(Integer types)
|
|
||||||
|
|
||||||
### (1)字符整型
|
|
||||||
|
|
||||||
```c
|
|
||||||
char // 至少 8 位(实现定义)
|
|
||||||
signed char // 至少 8 位
|
|
||||||
unsigned char // 至少 8 位
|
|
||||||
```
|
|
||||||
|
|
||||||
⚠️ 注意:
|
|
||||||
|
|
||||||
* `char` ≠ `signed char` ≠ `unsigned char`
|
|
||||||
* `char` 是否有符号是**实现定义**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### (2)短整型
|
|
||||||
|
|
||||||
```c
|
|
||||||
short
|
|
||||||
short int
|
|
||||||
signed short
|
|
||||||
signed short int
|
|
||||||
|
|
||||||
unsigned short
|
|
||||||
unsigned short int
|
|
||||||
```
|
|
||||||
|
|
||||||
* **至少 16 位**
|
|
||||||
* 常见:16 位
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### (3)整型(最常用)
|
|
||||||
|
|
||||||
```c
|
|
||||||
int
|
|
||||||
signed int
|
|
||||||
unsigned int
|
|
||||||
```
|
|
||||||
|
|
||||||
* **至少 16 位**
|
|
||||||
* 常见:
|
|
||||||
|
|
||||||
* 32 位(现代系统)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### (4)长整型
|
|
||||||
|
|
||||||
```c
|
|
||||||
long
|
|
||||||
long int
|
|
||||||
signed long
|
|
||||||
unsigned long
|
|
||||||
```
|
|
||||||
|
|
||||||
* **至少 32 位**
|
|
||||||
* 常见:
|
|
||||||
|
|
||||||
* Linux x64:64 位
|
|
||||||
* Windows x64:32 位(LLP64)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### (5)长长整型(C99)
|
|
||||||
|
|
||||||
```c
|
|
||||||
long long
|
|
||||||
long long int
|
|
||||||
signed long long
|
|
||||||
unsigned long long
|
|
||||||
```
|
|
||||||
|
|
||||||
* **至少 64 位**
|
|
||||||
* 基本稳定为 64 位
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2️⃣ 浮点类型
|
|
||||||
|
|
||||||
```c
|
|
||||||
float // 单精度
|
|
||||||
double // 双精度
|
|
||||||
long double // 扩展精度(实现定义)
|
|
||||||
```
|
|
||||||
|
|
||||||
常见:
|
|
||||||
|
|
||||||
| 类型 | 位数 |
|
|
||||||
| ----------- | ------------- |
|
|
||||||
| float | 32 |
|
|
||||||
| double | 64 |
|
|
||||||
| long double | 80 / 128 / 64 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3️⃣ 布尔类型(C99)
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
bool // 实际是 _Bool
|
|
||||||
_Bool
|
|
||||||
```
|
|
||||||
|
|
||||||
* `_Bool` 是内建类型
|
|
||||||
* `bool` 是宏别名
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4️⃣ 空类型
|
|
||||||
|
|
||||||
```c
|
|
||||||
void
|
|
||||||
```
|
|
||||||
|
|
||||||
用途:
|
|
||||||
|
|
||||||
* 无返回值
|
|
||||||
* 泛型指针 `void*`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 二、修饰符(类型“调味料”)
|
|
||||||
|
|
||||||
这些**不是独立类型**,而是修饰已有类型:
|
|
||||||
|
|
||||||
```c
|
|
||||||
signed
|
|
||||||
unsigned
|
|
||||||
short
|
|
||||||
long
|
|
||||||
```
|
|
||||||
|
|
||||||
合法组合例子:
|
|
||||||
|
|
||||||
```c
|
|
||||||
unsigned long long int
|
|
||||||
signed short
|
|
||||||
long double
|
|
||||||
```
|
|
||||||
|
|
||||||
非法组合:
|
|
||||||
|
|
||||||
```c
|
|
||||||
unsigned float ❌
|
|
||||||
short double ❌
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 三、固定宽度整数(`stdint.h`,**强烈推荐**)
|
|
||||||
|
|
||||||
> 你看到的 `int64_t`、`uint32_t` 全在这
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include <stdint.h>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1️⃣ 精确宽度(一定存在才定义)
|
|
||||||
|
|
||||||
```c
|
|
||||||
int8_t uint8_t
|
|
||||||
int16_t uint16_t
|
|
||||||
int32_t uint32_t
|
|
||||||
int64_t uint64_t
|
|
||||||
```
|
|
||||||
|
|
||||||
✔ **最常用、最安全**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2️⃣ 至少 N 位
|
|
||||||
|
|
||||||
```c
|
|
||||||
int_least8_t
|
|
||||||
int_least16_t
|
|
||||||
int_least32_t
|
|
||||||
int_least64_t
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3️⃣ 最快类型
|
|
||||||
|
|
||||||
```c
|
|
||||||
int_fast8_t
|
|
||||||
int_fast16_t
|
|
||||||
int_fast32_t
|
|
||||||
int_fast64_t
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4️⃣ 指针等宽整数(系统编程必备)
|
|
||||||
|
|
||||||
```c
|
|
||||||
intptr_t
|
|
||||||
uintptr_t
|
|
||||||
```
|
|
||||||
|
|
||||||
👉 能安全存放指针的整数
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5️⃣ 最大整数类型
|
|
||||||
|
|
||||||
```c
|
|
||||||
intmax_t
|
|
||||||
uintmax_t
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 四、常见 typedef(标准库里“看起来像关键字的东西”)
|
|
||||||
|
|
||||||
## 1️⃣ `stddef.h`
|
|
||||||
|
|
||||||
```c
|
|
||||||
size_t // sizeof 的结果类型(无符号)
|
|
||||||
ptrdiff_t // 指针差值(有符号)
|
|
||||||
nullptr_t // C23
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2️⃣ `limits.h` / `stdint.h`
|
|
||||||
|
|
||||||
```c
|
|
||||||
CHAR_BIT // 一个字节多少 bit(通常 8)
|
|
||||||
INT_MAX
|
|
||||||
UINT_MAX
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3️⃣ `sys/types.h`(POSIX)
|
|
||||||
|
|
||||||
```c
|
|
||||||
ssize_t // size_t 的有符号版
|
|
||||||
off_t // 文件偏移
|
|
||||||
pid_t // 进程 ID
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 五、字符 & 文本相关类型
|
|
||||||
|
|
||||||
```c
|
|
||||||
char // 字节 / UTF-8
|
|
||||||
wchar_t // 宽字符(平台相关)
|
|
||||||
```
|
|
||||||
|
|
||||||
C11 还支持:
|
|
||||||
|
|
||||||
```c
|
|
||||||
char16_t
|
|
||||||
char32_t
|
|
||||||
```
|
|
||||||
|
|
||||||
(但在 C 里用得不多)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 六、非标准但“到处都是”的别名(你问的 uint / uchar)
|
|
||||||
|
|
||||||
⚠️ **这些不是 C 标准!**
|
|
||||||
|
|
||||||
常见于:
|
|
||||||
|
|
||||||
* Windows
|
|
||||||
* 嵌入式
|
|
||||||
* 老代码
|
|
||||||
* 游戏 / 引擎
|
|
||||||
|
|
||||||
```c
|
|
||||||
typedef unsigned int uint;
|
|
||||||
typedef unsigned char uchar;
|
|
||||||
typedef unsigned short ushort;
|
|
||||||
typedef unsigned long ulong;
|
|
||||||
typedef long long int64;
|
|
||||||
typedef unsigned long long uint64;
|
|
||||||
```
|
|
||||||
|
|
||||||
👉 **完全是项目自定义**
|
|
||||||
👉 **千万别假设它们存在**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 七、复合类型(不是基本类型,但必须知道)
|
|
||||||
|
|
||||||
```c
|
|
||||||
struct
|
|
||||||
union
|
|
||||||
enum
|
|
||||||
```
|
|
||||||
|
|
||||||
以及:
|
|
||||||
|
|
||||||
```c
|
|
||||||
typedef
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 八、函数 / 指针 / 数组“伪类型”
|
|
||||||
|
|
||||||
```c
|
|
||||||
int* p;
|
|
||||||
int (*fp)(int);
|
|
||||||
int arr[10];
|
|
||||||
```
|
|
||||||
|
|
||||||
它们不是新类型,而是**类型构造方式**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 九、一张“终极速查表”(你该背的)
|
|
||||||
|
|
||||||
### ✅ 必背
|
|
||||||
|
|
||||||
* `char`
|
|
||||||
* `int`
|
|
||||||
* `long`
|
|
||||||
* `long long`
|
|
||||||
* `float`
|
|
||||||
* `double`
|
|
||||||
* `void`
|
|
||||||
* `size_t`
|
|
||||||
* `intptr_t`
|
|
||||||
* `int32_t / uint32_t`
|
|
||||||
* `int64_t / uint64_t`
|
|
||||||
* `bool`
|
|
||||||
|
|
||||||
### ❌ 不该依赖
|
|
||||||
|
|
||||||
* `uint`
|
|
||||||
* `uchar`
|
|
||||||
* `DWORD`
|
|
||||||
* `BYTE`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 最后一句工程建议(很重要)
|
|
||||||
|
|
||||||
> **写现代 C:**
|
|
||||||
>
|
|
||||||
> * 用 `stdint.h`
|
|
||||||
> * 用 `size_t`
|
|
||||||
> * 少猜位宽
|
|
||||||
> * 不造轮子 typedef
|
|
||||||
|
|
||||||
如果你愿意,下一步我可以帮你做一件事之一:
|
|
||||||
|
|
||||||
* 🔍 把 **SDL 里所有 typedef 拆给你看**
|
|
||||||
* 🧠 给你出 **“看到类型 → 推断平台行为”训练题**
|
|
||||||
* 🧱 教你 **自己写一个干净的 `types.h`**
|
|
||||||
|
|
||||||
你选一个,我接着往下带你。
|
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <cmath>
|
||||||
|
using namespace std;
|
||||||
|
/*
|
||||||
|
* L Stands for Left Dividing Point, So as R
|
||||||
|
* pos stands for: for each element, which Chunk the element is in
|
||||||
|
* sum stands for the main purpose, what do we want the chunk to calculate
|
||||||
|
*/
|
||||||
|
vector<int> L, R, pos, sum, tag;
|
||||||
|
void build(int n, const vector<int>& arr){
|
||||||
|
int B = sqrt(n);
|
||||||
|
int num = n / B; if(n % B) num++;
|
||||||
|
L.resize(num); R.resize(num); sum.resize(num); tag.resize(num);
|
||||||
|
for(int i = 0; i <= num; ++i) {
|
||||||
|
L[i] = (i-1)*B + 1;
|
||||||
|
R[i] = min(i*B, n); // Remind of The Last dec
|
||||||
|
for(int j = L[i]; j <= R[i]; ++j){
|
||||||
|
pos[j] = i;
|
||||||
|
sum[i] += arr[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(vector<int>& arr, int l, int r, int v){
|
||||||
|
int lchk = pos[l], rchk = pos[r];
|
||||||
|
//A. Only in one chunk
|
||||||
|
if (lchk == rchk) {
|
||||||
|
for(int i = l; i <= r; i++){
|
||||||
|
arr[i] += v;
|
||||||
|
sum[lchk] += v;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//B. Across Chunks
|
||||||
|
//left & right: violent update
|
||||||
|
for(int i = l; i <= R[lchk]; ++i){
|
||||||
|
arr[i] += v;
|
||||||
|
sum[lchk] += v;
|
||||||
|
}
|
||||||
|
for(int i = L[rchk]; i <= r; ++i){
|
||||||
|
arr[i] += v;
|
||||||
|
sum[rchk] += v;
|
||||||
|
}
|
||||||
|
//middle: use tag
|
||||||
|
for(int i = lchk+1; i <= rchk-1; ++i){
|
||||||
|
tag[i] += v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long long query(const vector<int>& arr, int l, int r){
|
||||||
|
int lchk = pos[l], rchk = pos[r];
|
||||||
|
//A. Only in one chunk
|
||||||
|
long long ans = 0;
|
||||||
|
if (lchk == rchk) {
|
||||||
|
for(int i = l; i <= r; i++){
|
||||||
|
ans += arr[i] + tag[lchk];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//B. Across Chunks
|
||||||
|
//left & right: violent update
|
||||||
|
for(int i = l; i <= R[lchk]; ++i){
|
||||||
|
ans += arr[i] + tag[lchk];
|
||||||
|
}
|
||||||
|
for(int i = L[rchk]; i <= r; ++i){
|
||||||
|
ans += arr[i] + tag[rchk];
|
||||||
|
}
|
||||||
|
//middle: use tag
|
||||||
|
for(int i = lchk+1; i <= rchk-1; ++i){
|
||||||
|
ans += sum[i] + tag[i] * (R[i]-L[i] + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
48
Data-Structure/Tree/BinaryTree/BinaryIndexedTree/BIT.cpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
using namespace std;
|
||||||
|
inline int lowbit(int x){
|
||||||
|
return (x & (-x));
|
||||||
|
}
|
||||||
|
class BIT{
|
||||||
|
public:
|
||||||
|
vector<long long> tree; // 用 long long 防溢出
|
||||||
|
int n;
|
||||||
|
|
||||||
|
BIT(int size) : n(size) {
|
||||||
|
tree.resize(n+1, 0); // *BIT 空间通常是 n+1*
|
||||||
|
}
|
||||||
|
|
||||||
|
BIT(vector<int> arr) {
|
||||||
|
n = arr.size();
|
||||||
|
tree.resize(n+1, 0);
|
||||||
|
for(int i = 0; i < n; ++i){
|
||||||
|
int x = i + 1;
|
||||||
|
while(x <= n){
|
||||||
|
tree[x] += arr[i];
|
||||||
|
x += lowbit(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//x: pos; d: alter a[x] to a[x] + d;
|
||||||
|
void update(int x, int d) {
|
||||||
|
if (x <= 0) return;
|
||||||
|
while (x <= n) {
|
||||||
|
tree[x] += d;
|
||||||
|
x += lowbit(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//return sum: a[0] + a[1] + ... + a[x]
|
||||||
|
long long sum(int x) {
|
||||||
|
long long ans = 0;
|
||||||
|
while (x > 0) {
|
||||||
|
ans += tree[x];
|
||||||
|
x -= lowbit(x);
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
|
||||||
|
}
|
||||||
91
Data-Structure/Tree/BinaryTree/K-DTree/2D-KDTree.cpp
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/* ========= 2D KD-Tree ========= */
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
double x, y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
Point p;
|
||||||
|
Node *left, *right;
|
||||||
|
Node(Point _p) : p(_p), left(nullptr), right(nullptr) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
double dist2(const Point& a, const Point& b) {
|
||||||
|
double dx = a.x - b.x;
|
||||||
|
double dy = a.y - b.y;
|
||||||
|
return dx * dx + dy * dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* build(vector<Point>& pts, int l, int r, int depth) {
|
||||||
|
if (l >= r) return nullptr;
|
||||||
|
|
||||||
|
int dim = depth % 2;
|
||||||
|
int mid = (l + r) / 2;
|
||||||
|
|
||||||
|
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
|
||||||
|
[&](const Point& a, const Point& b) {
|
||||||
|
return dim == 0 ? a.x < b.x : a.y < b.y;
|
||||||
|
});
|
||||||
|
|
||||||
|
Node* node = new Node(pts[mid]);
|
||||||
|
node->left = build(pts, l, mid, depth + 1);
|
||||||
|
node->right = build(pts, mid + 1, r, depth + 1);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nearest(Node* node, const Point& target, int depth,
|
||||||
|
Point& best, double& bestDist) {
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
double d = dist2(node->p, target);
|
||||||
|
if (d < bestDist) {
|
||||||
|
bestDist = d;
|
||||||
|
best = node->p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dim = depth % 2;
|
||||||
|
Node *nearChild, *farChild;
|
||||||
|
|
||||||
|
if ((dim == 0 && target.x < node->p.x) ||
|
||||||
|
(dim == 1 && target.y < node->p.y)) {
|
||||||
|
nearChild = node->left;
|
||||||
|
farChild = node->right;
|
||||||
|
} else {
|
||||||
|
nearChild = node->right;
|
||||||
|
farChild = node->left;
|
||||||
|
}
|
||||||
|
|
||||||
|
nearest(nearChild, target, depth + 1, best, bestDist);
|
||||||
|
|
||||||
|
double diff = (dim == 0)
|
||||||
|
? target.x - node->p.x
|
||||||
|
: target.y - node->p.y;
|
||||||
|
|
||||||
|
if (diff * diff < bestDist) {
|
||||||
|
nearest(farChild, target, depth + 1, best, bestDist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
vector<Point> points = {
|
||||||
|
{2,3}, {5,4}, {9,6},
|
||||||
|
{4,7}, {8,1}, {7,2}
|
||||||
|
};
|
||||||
|
|
||||||
|
Node* root = build(points, 0, points.size(), 0);
|
||||||
|
|
||||||
|
Point target{9, 2};
|
||||||
|
Point best;
|
||||||
|
double bestDist = 1e18;
|
||||||
|
|
||||||
|
nearest(root, target, 0, best, bestDist);
|
||||||
|
|
||||||
|
cout << "Nearest point: (" << best.x << ", " << best.y << ")\n";
|
||||||
|
cout << "Squared distance: " << bestDist << "\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
99
Data-Structure/Tree/BinaryTree/K-DTree/3D-KDTree.cpp
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/* ========= 3D KD-Tree ========= */
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
double x, y, z;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
Point p;
|
||||||
|
Node *left, *right;
|
||||||
|
Node(Point _p) : p(_p), left(nullptr), right(nullptr) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
double dist2(const Point& a, const Point& b) {
|
||||||
|
double dx = a.x - b.x;
|
||||||
|
double dy = a.y - b.y;
|
||||||
|
double dz = a.z - b.z;
|
||||||
|
return dx * dx + dy * dy + dz * dz;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* build(vector<Point>& pts, int l, int r, int depth) {
|
||||||
|
if (l >= r) return nullptr;
|
||||||
|
|
||||||
|
int dim = depth % 3;
|
||||||
|
int mid = (l + r) / 2;
|
||||||
|
|
||||||
|
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
|
||||||
|
[&](const Point& a, const Point& b) {
|
||||||
|
if (dim == 0) return a.x < b.x;
|
||||||
|
if (dim == 1) return a.y < b.y;
|
||||||
|
return a.z < b.z;
|
||||||
|
});
|
||||||
|
|
||||||
|
Node* node = new Node(pts[mid]);
|
||||||
|
node->left = build(pts, l, mid, depth + 1);
|
||||||
|
node->right = build(pts, mid + 1, r, depth + 1);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nearest(Node* node, const Point& target, int depth,
|
||||||
|
Point& best, double& bestDist) {
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
double d = dist2(node->p, target);
|
||||||
|
if (d < bestDist) {
|
||||||
|
bestDist = d;
|
||||||
|
best = node->p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dim = depth % 3;
|
||||||
|
Node *nearChild, *farChild;
|
||||||
|
|
||||||
|
if ((dim == 0 && target.x < node->p.x) ||
|
||||||
|
(dim == 1 && target.y < node->p.y) ||
|
||||||
|
(dim == 2 && target.z < node->p.z)) {
|
||||||
|
nearChild = node->left;
|
||||||
|
farChild = node->right;
|
||||||
|
} else {
|
||||||
|
nearChild = node->right;
|
||||||
|
farChild = node->left;
|
||||||
|
}
|
||||||
|
|
||||||
|
nearest(nearChild, target, depth + 1, best, bestDist);
|
||||||
|
|
||||||
|
double diff = (dim == 0) ? target.x - node->p.x
|
||||||
|
: (dim == 1) ? target.y - node->p.y
|
||||||
|
: target.z - node->p.z;
|
||||||
|
|
||||||
|
if (diff * diff < bestDist) {
|
||||||
|
nearest(farChild, target, depth + 1, best, bestDist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
vector<Point> points = {
|
||||||
|
{2,3,4}, {5,4,2}, {9,6,7},
|
||||||
|
{4,7,9}, {8,1,5}, {7,2,6}
|
||||||
|
};
|
||||||
|
|
||||||
|
Node* root = build(points, 0, points.size(), 0);
|
||||||
|
|
||||||
|
Point target{9, 2, 6};
|
||||||
|
Point best;
|
||||||
|
double bestDist = 1e18;
|
||||||
|
|
||||||
|
nearest(root, target, 0, best, bestDist);
|
||||||
|
|
||||||
|
cout << "Nearest point: ("
|
||||||
|
<< best.x << ", "
|
||||||
|
<< best.y << ", "
|
||||||
|
<< best.z << ")\n";
|
||||||
|
|
||||||
|
cout << "Squared distance: " << bestDist << "\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
325
Data-Structure/Tree/BinaryTree/K-DTree/Readme.md
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
# 1️⃣ What is a K-D Tree (clean & precise)
|
||||||
|
|
||||||
|
A **K-D Tree (k-dimensional tree)** is a **binary tree for organizing points in k-dimensional space**.
|
||||||
|
|
||||||
|
Each node:
|
||||||
|
|
||||||
|
* Stores **one point** `(x₀, x₁, …, xₖ₋₁)`
|
||||||
|
* Splits space using **one dimension**
|
||||||
|
* Left/right child represent **two half-spaces**
|
||||||
|
|
||||||
|
### Core rule (the soul of KD-Tree)
|
||||||
|
|
||||||
|
At depth `d`:
|
||||||
|
|
||||||
|
```
|
||||||
|
split_dimension = d % k
|
||||||
|
```
|
||||||
|
|
||||||
|
* Left subtree: points with smaller coordinate on that dimension
|
||||||
|
* Right subtree: points with larger coordinate on that dimension
|
||||||
|
|
||||||
|
So the split dimension **cycles**:
|
||||||
|
|
||||||
|
```
|
||||||
|
2D: x → y → x → y → ...
|
||||||
|
3D: x → y → z → x → ...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why KD-Tree exists (not just “cool BST”)
|
||||||
|
|
||||||
|
KD-Tree is designed for **spatial queries**, not sorting.
|
||||||
|
|
||||||
|
It excels at:
|
||||||
|
|
||||||
|
* Nearest Neighbor (NN)
|
||||||
|
* k-Nearest Neighbor (kNN)
|
||||||
|
* Range / box queries
|
||||||
|
|
||||||
|
Why?
|
||||||
|
➡️ Because it **partitions space**, allowing **whole subtrees to be pruned**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Balanced construction (important)
|
||||||
|
|
||||||
|
Given all points beforehand:
|
||||||
|
|
||||||
|
1. Choose split dimension
|
||||||
|
2. Sort points by that dimension
|
||||||
|
3. Take **median** as root
|
||||||
|
4. Recurse on left / right
|
||||||
|
|
||||||
|
This gives:
|
||||||
|
|
||||||
|
* Height ≈ `O(log n)`
|
||||||
|
* Good pruning behavior
|
||||||
|
|
||||||
|
This is the version we’ll implement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Geometry intuition (very important)
|
||||||
|
|
||||||
|
* Each node represents a **hyperplane**
|
||||||
|
* Each subtree represents a **region of space**
|
||||||
|
* Distance queries:
|
||||||
|
|
||||||
|
* First go to the “likely” side
|
||||||
|
* Then check if the other side **can possibly contain a closer point**
|
||||||
|
|
||||||
|
That’s the entire NN trick.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2️⃣ 2D KD-Tree (C++ Template)
|
||||||
|
|
||||||
|
### Supported:
|
||||||
|
|
||||||
|
* Build from points
|
||||||
|
* Nearest Neighbor query
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Data structures
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
double x, y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
Point p;
|
||||||
|
Node *left, *right;
|
||||||
|
|
||||||
|
Node(Point _p) : p(_p), left(nullptr), right(nullptr) {}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Utility functions
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
double dist2(const Point& a, const Point& b) {
|
||||||
|
double dx = a.x - b.x;
|
||||||
|
double dy = a.y - b.y;
|
||||||
|
return dx * dx + dy * dy;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Build KD-Tree (2D)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* build(vector<Point>& pts, int l, int r, int depth) {
|
||||||
|
if (l >= r) return nullptr;
|
||||||
|
|
||||||
|
int dim = depth % 2;
|
||||||
|
int mid = (l + r) / 2;
|
||||||
|
|
||||||
|
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
|
||||||
|
[&](const Point& a, const Point& b) {
|
||||||
|
return dim == 0 ? a.x < b.x : a.y < b.y;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Node* node = new Node(pts[mid]);
|
||||||
|
node->left = build(pts, l, mid, depth + 1);
|
||||||
|
node->right = build(pts, mid + 1, r, depth + 1);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
📌 `nth_element` gives **median in linear time** (better than full sort).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Nearest Neighbor Search (2D)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void nearest(Node* node, const Point& target, int depth,
|
||||||
|
Point& best, double& bestDist) {
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
double d = dist2(node->p, target);
|
||||||
|
if (d < bestDist) {
|
||||||
|
bestDist = d;
|
||||||
|
best = node->p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dim = depth % 2;
|
||||||
|
|
||||||
|
Node *nearChild, *farChild;
|
||||||
|
if ((dim == 0 && target.x < node->p.x) ||
|
||||||
|
(dim == 1 && target.y < node->p.y)) {
|
||||||
|
nearChild = node->left;
|
||||||
|
farChild = node->right;
|
||||||
|
} else {
|
||||||
|
nearChild = node->right;
|
||||||
|
farChild = node->left;
|
||||||
|
}
|
||||||
|
|
||||||
|
nearest(nearChild, target, depth + 1, best, bestDist);
|
||||||
|
|
||||||
|
double diff = (dim == 0)
|
||||||
|
? target.x - node->p.x
|
||||||
|
: target.y - node->p.y;
|
||||||
|
|
||||||
|
if (diff * diff < bestDist) {
|
||||||
|
nearest(farChild, target, depth + 1, best, bestDist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
💡 **Key idea**:
|
||||||
|
Only search the “other side” if the splitting line is close enough.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Usage example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int main() {
|
||||||
|
vector<Point> pts = {{2,3}, {5,4}, {9,6}, {4,7}, {8,1}, {7,2}};
|
||||||
|
Node* root = build(pts, 0, pts.size(), 0);
|
||||||
|
|
||||||
|
Point target{9, 2};
|
||||||
|
Point best;
|
||||||
|
double bestDist = 1e18;
|
||||||
|
|
||||||
|
nearest(root, target, 0, best, bestDist);
|
||||||
|
|
||||||
|
cout << best.x << " " << best.y << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3️⃣ 3D KD-Tree (C++ Template)
|
||||||
|
|
||||||
|
### Changes from 2D:
|
||||||
|
|
||||||
|
* Add `z`
|
||||||
|
* `depth % 3`
|
||||||
|
* Distance formula extends naturally
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Structures
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Point3 {
|
||||||
|
double x, y, z;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Node3 {
|
||||||
|
Point3 p;
|
||||||
|
Node3 *left, *right;
|
||||||
|
|
||||||
|
Node3(Point3 _p) : p(_p), left(nullptr), right(nullptr) {}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Distance
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
double dist2(const Point3& a, const Point3& b) {
|
||||||
|
double dx = a.x - b.x;
|
||||||
|
double dy = a.y - b.y;
|
||||||
|
double dz = a.z - b.z;
|
||||||
|
return dx*dx + dy*dy + dz*dz;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Build (3D)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node3* build3(vector<Point3>& pts, int l, int r, int depth) {
|
||||||
|
if (l >= r) return nullptr;
|
||||||
|
|
||||||
|
int dim = depth % 3;
|
||||||
|
int mid = (l + r) / 2;
|
||||||
|
|
||||||
|
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
|
||||||
|
[&](const Point3& a, const Point3& b) {
|
||||||
|
if (dim == 0) return a.x < b.x;
|
||||||
|
if (dim == 1) return a.y < b.y;
|
||||||
|
return a.z < b.z;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Node3* node = new Node3(pts[mid]);
|
||||||
|
node->left = build3(pts, l, mid, depth + 1);
|
||||||
|
node->right = build3(pts, mid + 1, r, depth + 1);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Nearest Neighbor (3D)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void nearest3(Node3* node, const Point3& target, int depth,
|
||||||
|
Point3& best, double& bestDist) {
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
double d = dist2(node->p, target);
|
||||||
|
if (d < bestDist) {
|
||||||
|
bestDist = d;
|
||||||
|
best = node->p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dim = depth % 3;
|
||||||
|
|
||||||
|
double diff;
|
||||||
|
Node3 *nearChild, *farChild;
|
||||||
|
|
||||||
|
if ((dim == 0 && target.x < node->p.x) ||
|
||||||
|
(dim == 1 && target.y < node->p.y) ||
|
||||||
|
(dim == 2 && target.z < node->p.z)) {
|
||||||
|
nearChild = node->left;
|
||||||
|
farChild = node->right;
|
||||||
|
} else {
|
||||||
|
nearChild = node->right;
|
||||||
|
farChild = node->left;
|
||||||
|
}
|
||||||
|
|
||||||
|
nearest3(nearChild, target, depth + 1, best, bestDist);
|
||||||
|
|
||||||
|
diff = (dim == 0) ? target.x - node->p.x
|
||||||
|
: (dim == 1) ? target.y - node->p.y
|
||||||
|
: target.z - node->p.z;
|
||||||
|
|
||||||
|
if (diff * diff < bestDist) {
|
||||||
|
nearest3(farChild, target, depth + 1, best, bestDist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Final mental model (keep this)
|
||||||
|
|
||||||
|
> **KD-Tree = BST where comparison dimension rotates, and pruning is geometric.**
|
||||||
|
|
||||||
|
Once you understand:
|
||||||
|
|
||||||
|
* `depth % k`
|
||||||
|
* median build
|
||||||
|
* “check other side only if needed”
|
||||||
|
|
||||||
|
You’ve mastered KD-Trees.
|
||||||
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
184
Data-Structure/Tree/LCT/Readme.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
LCT(Link-Cut Tree)是由 Robert Tarjan 等人在 1982 年开发的一种用于维护**动态树**(Dynamic Tree)关系的数据结构。
|
||||||
|
|
||||||
|
如果说 **树状数组** 是为了动态维护前缀和,**线段树** 是为了动态维护区间,那么 **LCT** 就是为了维护一个**森林中节点的连通性以及路径信息**(比如两点间的距离、最大值等),且这个森林的结构是会随时发生变化的(断开某条边、连上某条边)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 核心概念:实边与虚边
|
||||||
|
|
||||||
|
LCT 的核心思想是将一棵树拆分成若干条**实路径(Preferred Paths)**。
|
||||||
|
|
||||||
|
* **实边(Preferred Edge):** 指向“首选儿子”的边。
|
||||||
|
* **虚边(Light Edge):** 树中除了实边以外的边。
|
||||||
|
* **实路径(Preferred Path):** 由一系列实边首尾相连构成的路径。
|
||||||
|
|
||||||
|
**关键点:** 每个节点**最多只能有一条**实边连向它的儿子。这意味着整棵树被完美地剖分成了若干条互不相交的实路径,覆盖了所有的节点。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. “首选路径”是怎么规定的?
|
||||||
|
|
||||||
|
你问到了 LCT 的灵魂——**首选路径(Preferred Path)**。
|
||||||
|
|
||||||
|
在 LCT 中,首选路径并不是一成不变的(不像树链剖分是根据子树大小定义的“重链”),它是**动态变化**的。
|
||||||
|
|
||||||
|
### 规定逻辑:
|
||||||
|
|
||||||
|
首选路径的定义取决于**最后一次访问(Access)操作**。
|
||||||
|
|
||||||
|
1. **访问定义:** 当我们对节点 执行 `access(u)` 时,目标是创造出一条从当前树的**根节点**到 **** 的实路径。
|
||||||
|
2. **排他性:** 一旦一个节点 决定将它与儿子 的边变为“实边”,那么 之前与之相连的其他实边(通往其他儿子的)必须立即变回“虚边”。
|
||||||
|
3. **结果:** `access(u)` 执行完后, 所在的实路径会一直延伸到根,且 是这条路径上深度最大的节点(它没有实边连向儿子了)。
|
||||||
|
|
||||||
|
> **直白点说:** “首选”代表的是“最近被关照过”。你想操作哪个节点,你就把从根到它的路全都刷成“实边”,原本在这些路上的岔路(旧实边)就会被自动抛弃变成虚边。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. LCT 的结构:辅助树 (Splay Tree)
|
||||||
|
|
||||||
|
LCT 并不是直接维护原树,而是为每一条**实路径**维护一棵 **Splay Tree**(称为辅助树)。
|
||||||
|
|
||||||
|
* **辅助树的性质:**
|
||||||
|
* 一棵 Splay 维护且仅维护原树中的一条**实路径**。
|
||||||
|
* Splay 的中序遍历,严格对应原树路径中节点**从上到下**(深度由浅到深)的顺序。
|
||||||
|
* **虚边如何表示?** 辅助树的根节点的 `parent` 指针指向这条实路径在原树中上一个节点的 Splay,但那个节点并不认这个儿子(即:子不认父,父不认子)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. LCT 的基本操作
|
||||||
|
|
||||||
|
要实现 LCT,最基础的几个大招如下:
|
||||||
|
|
||||||
|
| 操作 | 功能 | 核心逻辑 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **Access(x)** | **打通路径** | 将根节点到 的路径全部变为实边,并放入同一棵 Splay 中。 |
|
||||||
|
| **MakeRoot(x)** | **换根** | 让 成为原树的根。先 `access(x)`,再 `splay(x)`,最后翻转 Splay。 |
|
||||||
|
| **FindRoot(x)** | **找根** | 判断连通性。`access(x)` 后找 Splay 中最左边的节点。 |
|
||||||
|
| **Link(x, y)** | **连边** | 让 所在的树连接到 。通常先 `makeRoot(x)`,然后将 的父指针指向 。 |
|
||||||
|
| **Cut(x, y)** | **断边** | 断开 之间的边。通过 `makeRoot` 和 `access` 将其分离。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 为什么 LCT 这么强?
|
||||||
|
|
||||||
|
LCT 的复杂度是**均摊 **。
|
||||||
|
|
||||||
|
它的强大在于:
|
||||||
|
|
||||||
|
* **处理动态性:** 传统的树链剖分(Heavy-Light Decomposition)无法处理加边、删边,因为重儿子是根据子树大小算的,一旦结构变了,重链就全乱了。
|
||||||
|
* **灵活性:** 只要你能用 Splay 维护的信息(区间和、区间最大、区间翻转),LCT 几乎都能在动态环境下维护。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**你想深入了解 `access` 操作的具体代码实现逻辑,还是想看看 LCT 如何处理具体的题目(比如动态维护森林连通性)?**
|
||||||
|
|
||||||
|
为了让你直观理解 LCT 的核心逻辑,我们通过一个具体的**“修路”场景**来演示。
|
||||||
|
|
||||||
|
假设你现在有一棵树,我们要对其中的节点执行操作。请记住:**实路径(Preferred Path)就是你最近“访问”过的那条线。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 场景:我们要访问节点
|
||||||
|
|
||||||
|
假设原树的结构如下(括号内为深度):
|
||||||
|
|
||||||
|
* 根节点是
|
||||||
|
* 有两个儿子 和
|
||||||
|
* 有两个儿子 和
|
||||||
|
* 有一个儿子
|
||||||
|
|
||||||
|
### 1. 初始状态:全是虚边
|
||||||
|
|
||||||
|
在没有任何操作前,所有边默认都是**虚边**(或者你可以理解为每条实路径只有一个节点)。
|
||||||
|
|
||||||
|
### 2. 执行 `access(G)`:打通去往 的路
|
||||||
|
|
||||||
|
这个操作的目的是:**从根节点 到 连出一条连贯的实边路径。**
|
||||||
|
|
||||||
|
1. **第一步:** 访问 。 现在自己是一条实路径。
|
||||||
|
2. **第二步:** 向上找到 的父亲 。原本 可能连着别的儿子(比如 还有个儿子 ),但为了 ,** 必须断开与 的实边,改与 建立实边连接**。
|
||||||
|
3. **第三步:** 向上找到 的父亲 。同理, 断开与 的实边,改与 建立实边。
|
||||||
|
4. **第四步:** 向上找到 的父亲 。 断开与 的实边,改与 建立实边。
|
||||||
|
|
||||||
|
**结果:** 产生了一条实路径:。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 这里的“首选”体现在哪?
|
||||||
|
|
||||||
|
当你执行完 `access(G)` 后:
|
||||||
|
|
||||||
|
* **它是唯一的:** 在原树中,从根到 的路径是唯一的,LCT 将这条路径上的所有边“高亮”为实边。
|
||||||
|
* **它是排他的:** 原本指向 的边变成了虚边。这意味着在“辅助树(Splay)”里, 不再和 在同一棵 Splay 里了。
|
||||||
|
* **它是为了维护顺序:** 此时,这棵包含 的 Splay Tree,其中序遍历结果严格为 。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 动态改变:执行 `link(X, Y)`
|
||||||
|
|
||||||
|
假设现在有另一棵树,根节点是 。你想把 连到 上(`link(X, G)`)。
|
||||||
|
|
||||||
|
1. **MakeRoot(X):** 先把 变成它所在那棵树的根(通过旋转和翻转)。
|
||||||
|
2. **连边:** 直接把 的父指针(虚边形式)指向 。
|
||||||
|
|
||||||
|
此时,如果你再执行 `access(X)`,原本那条 的实路径就会立刻延伸,变成 。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 总结:首选路径的“潜规则”
|
||||||
|
|
||||||
|
你可以把 LCT 想象成一个**交通管制系统**:
|
||||||
|
|
||||||
|
* **实路径**就是“绿灯区”,是一条高速公路(Splay Tree),里面的节点可以快速互相访问。
|
||||||
|
* **首选路径的规定**就是:**谁最后被呼叫(access),谁就拥有从根部直达自己的“全线绿灯”。**
|
||||||
|
* 为了保证你是这条路的终点,你之后的所有子孙节点都会被挤成“虚边”连接(红灯停发)。
|
||||||
|
|
||||||
|
**这个例子能帮你理解为什么它是“动态”的了吗?如果你愿意,我们可以看看 `access` 那个著名的 `while` 循环代码是怎么实现的。**
|
||||||
|
|
||||||
|
没错,你的理解**完全正确**!这正是 LCT 能够高效处理动态树问题的精髓。
|
||||||
|
|
||||||
|
当你的目光从 转向 时,LCT 会进行一次“路径切换”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 变化过程详解
|
||||||
|
|
||||||
|
假设刚才的状态是实路径 。现在你执行 `access(C)`:
|
||||||
|
|
||||||
|
1. **A 的选择变了:** 节点 发现你要访问 ,由于每个节点只能有一个“首选儿子”, 必须在 和 之间做出选择。
|
||||||
|
2. **A-C 变实:** 到 的边由虚变实。
|
||||||
|
3. **A-B 变虚:** 原本 指向 的实边被迫断开,降级为虚边。
|
||||||
|
4. **下游保持不变:** * 虽然 断开了,但 下方的路径 ** 依然是实边连接的**。
|
||||||
|
* 它们只是整体从以 为首的实路径中“掉”了出来,形成了一条独立的实路径(对应一棵独立的辅助树)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 现在的格局:
|
||||||
|
|
||||||
|
* **实路径 1:** (在新的 Splay 中)
|
||||||
|
* **实路径 2:** (在另一棵 Splay 中,但这棵 Splay 的根的父指针指向 )
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 为什么“其余边不变”很重要?
|
||||||
|
|
||||||
|
这种“懒惰”的更新方式保证了效率:
|
||||||
|
|
||||||
|
* **局部性:** `access(C)` 只会影响从 到根节点路径上的实虚关系。
|
||||||
|
* **复杂度控制:** 只有路径上的边会发生实虚切换。虽然看起来改动很多,但通过 **Splay Tree** 的势能分析(Amortized Analysis)可以证明,长期来看,所有操作的平均时间复杂度依然是 。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 总结:首选路径的“渣男”法则
|
||||||
|
|
||||||
|
你可以把每个节点想象成一个只能谈一个对象的“渣男/渣女”:
|
||||||
|
|
||||||
|
* ** 原本的对象是 **( 是实边)。
|
||||||
|
* 一旦 ** 被 Access 了**,且 也是 的孩子。
|
||||||
|
* 会立刻抛弃 ( 变虚),转而和 在一起( 变实)。
|
||||||
|
* 至于 之前谈的对象( 的实儿子 ), 管不着, 和 依然保持实边关系。
|
||||||
|
|
||||||
|
**这就是 LCT 维护动态连通性的奥秘。你想看看 `access` 那个短小精悍的 C++ 代码模板吗?它能帮你彻底看清这个“变心”的过程。**
|
||||||
|
|
||||||
344
Data-Structure/Tree/ScapeGoatTree/Readme.md
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
# Scapegoat Tree (α-Scapegoat Tree)
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
|
||||||
|
A **Scapegoat Tree** is a type of **self-balancing Binary Search Tree (BST)** proposed by *Igal Galperin and Ronald L. Rivest (1993)*.
|
||||||
|
Unlike AVL Trees or Red-Black Trees, it **does not store balance information** (such as heights or colors) inside nodes. Instead, it maintains balance by **occasionally rebuilding subtrees** when imbalance is detected.
|
||||||
|
|
||||||
|
The key idea is:
|
||||||
|
|
||||||
|
> *Allow the tree to become temporarily unbalanced, but detect structural violations and rebuild only when necessary.*
|
||||||
|
|
||||||
|
This makes the Scapegoat Tree conceptually simple while still guaranteeing good asymptotic performance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Balance Criterion
|
||||||
|
|
||||||
|
The tree is parameterized by a real constant:
|
||||||
|
|
||||||
|
[
|
||||||
|
\alpha \in \left(\frac{1}{2}, 1\right)
|
||||||
|
]
|
||||||
|
|
||||||
|
A subtree rooted at node `x` is **α-weight-balanced** if:
|
||||||
|
|
||||||
|
[
|
||||||
|
\max(|\text{left}(x)|, |\text{right}(x)|) \le \alpha \cdot |\text{subtree}(x)|
|
||||||
|
]
|
||||||
|
|
||||||
|
If this condition is violated, `x` is called a **scapegoat**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Height Guarantee
|
||||||
|
|
||||||
|
Let `n` be the number of nodes in the tree.
|
||||||
|
|
||||||
|
The height of a Scapegoat Tree is guaranteed to be:
|
||||||
|
|
||||||
|
[
|
||||||
|
h \le \log_{1/\alpha}(n)
|
||||||
|
]
|
||||||
|
|
||||||
|
Thus:
|
||||||
|
|
||||||
|
* Search: **O(log n)** worst-case
|
||||||
|
* Insert: **O(log n)** amortized
|
||||||
|
* Delete: **O(log n)** amortized
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Insertion Strategy
|
||||||
|
|
||||||
|
Insertion proceeds in two phases:
|
||||||
|
|
||||||
|
### 4.1 Normal BST Insertion
|
||||||
|
|
||||||
|
* Insert the key as in a standard BST.
|
||||||
|
* Track the depth `d` of the inserted node.
|
||||||
|
|
||||||
|
### 4.2 Violation Detection
|
||||||
|
|
||||||
|
If:
|
||||||
|
[
|
||||||
|
d > \log_{1/\alpha}(n)
|
||||||
|
]
|
||||||
|
then the tree may be unbalanced.
|
||||||
|
|
||||||
|
### 4.3 Scapegoat Identification
|
||||||
|
|
||||||
|
* Traverse upward from the inserted node.
|
||||||
|
* Find the **first ancestor** where the α-balance condition is violated.
|
||||||
|
* This node is the **scapegoat**.
|
||||||
|
|
||||||
|
### 4.4 Rebuilding
|
||||||
|
|
||||||
|
* Rebuild the entire subtree rooted at the scapegoat into a perfectly balanced BST.
|
||||||
|
* This restores global balance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Deletion Strategy
|
||||||
|
|
||||||
|
Deletion is handled lazily:
|
||||||
|
|
||||||
|
1. Perform standard BST deletion.
|
||||||
|
2. Maintain:
|
||||||
|
|
||||||
|
* `n`: current node count
|
||||||
|
* `max_n`: maximum node count ever reached
|
||||||
|
3. If:
|
||||||
|
[
|
||||||
|
n < \alpha \cdot \text{max_n}
|
||||||
|
]
|
||||||
|
then:
|
||||||
|
|
||||||
|
* Rebuild the **entire tree**
|
||||||
|
* Set `max_n = n`
|
||||||
|
|
||||||
|
This avoids frequent rebalancing during small deletions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Rebuilding a Subtree
|
||||||
|
|
||||||
|
Rebuilding consists of two steps:
|
||||||
|
|
||||||
|
1. **Flatten**
|
||||||
|
|
||||||
|
* Perform an inorder traversal of the subtree
|
||||||
|
* Store nodes in a sorted array
|
||||||
|
|
||||||
|
2. **Rebuild**
|
||||||
|
|
||||||
|
* Recursively construct a perfectly balanced BST from the array
|
||||||
|
|
||||||
|
This operation takes **O(k)** time for a subtree of size `k`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Comparison with Other BSTs
|
||||||
|
|
||||||
|
| Tree Type | Balance Info | Rebalancing | Worst-case Height |
|
||||||
|
| ------------- | ------------ | --------------- | ------------------ |
|
||||||
|
| AVL | Height | Rotations | O(log n) |
|
||||||
|
| Red-Black | Color | Rotations | O(log n) |
|
||||||
|
| Splay | None | Access-based | Amortized O(log n) |
|
||||||
|
| **Scapegoat** | None | Subtree rebuild | O(log n) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Advantages and Disadvantages
|
||||||
|
|
||||||
|
### Advantages
|
||||||
|
|
||||||
|
* Simple node structure
|
||||||
|
* No rotations
|
||||||
|
* Deterministic height bound
|
||||||
|
* Easy to implement correctly
|
||||||
|
|
||||||
|
### Disadvantages
|
||||||
|
|
||||||
|
* Rebuild cost can be high
|
||||||
|
* Worse constant factors than AVL/RB trees
|
||||||
|
* Not ideal for real-time systems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Typical Use Cases
|
||||||
|
|
||||||
|
* Educational purposes
|
||||||
|
* Systems where simplicity > constant factors
|
||||||
|
* Situations where rotations are undesirable
|
||||||
|
* Batch-heavy insert/delete workloads
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# C++ Template: Scapegoat Tree
|
||||||
|
|
||||||
|
Below is a **minimal, clean, academic-style template**, suitable for learning and extension.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Node Definition
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Node {
|
||||||
|
int key;
|
||||||
|
Node *left, *right, *parent;
|
||||||
|
int size;
|
||||||
|
|
||||||
|
Node(int k)
|
||||||
|
: key(k), left(nullptr), right(nullptr), parent(nullptr), size(1) {}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Core Class Skeleton
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class ScapegoatTree {
|
||||||
|
private:
|
||||||
|
const double alpha = 0.75;
|
||||||
|
|
||||||
|
Node* root = nullptr;
|
||||||
|
int n = 0;
|
||||||
|
int max_n = 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Utility Functions
|
||||||
|
|
||||||
|
### 3.1 Subtree Size Maintenance
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int getSize(Node* x) {
|
||||||
|
return x ? x->size : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(Node* x) {
|
||||||
|
if (x) {
|
||||||
|
x->size = getSize(x->left) + getSize(x->right) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 Inorder Flatten
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void flatten(Node* x, std::vector<Node*>& arr) {
|
||||||
|
if (!x) return;
|
||||||
|
flatten(x->left, arr);
|
||||||
|
arr.push_back(x);
|
||||||
|
flatten(x->right, arr);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 Build Balanced Tree
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* build(std::vector<Node*>& arr, int l, int r, Node* parent) {
|
||||||
|
if (l > r) return nullptr;
|
||||||
|
int m = (l + r) / 2;
|
||||||
|
|
||||||
|
Node* x = arr[m];
|
||||||
|
x->parent = parent;
|
||||||
|
x->left = build(arr, l, m - 1, x);
|
||||||
|
x->right = build(arr, m + 1, r, x);
|
||||||
|
|
||||||
|
update(x);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Rebuild Subtree
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void rebuild(Node* x) {
|
||||||
|
std::vector<Node*> nodes;
|
||||||
|
flatten(x, nodes);
|
||||||
|
|
||||||
|
Node* parent = x->parent;
|
||||||
|
Node* newSub = build(nodes, 0, nodes.size() - 1, parent);
|
||||||
|
|
||||||
|
if (!parent) {
|
||||||
|
root = newSub;
|
||||||
|
} else if (parent->left == x) {
|
||||||
|
parent->left = newSub;
|
||||||
|
} else {
|
||||||
|
parent->right = newSub;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Balance Check
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool isBalanced(Node* x) {
|
||||||
|
return getSize(x->left) <= alpha * x->size &&
|
||||||
|
getSize(x->right) <= alpha * x->size;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Insertion
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void insert(int key) {
|
||||||
|
if (!root) {
|
||||||
|
root = new Node(key);
|
||||||
|
n = max_n = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* cur = root;
|
||||||
|
Node* parent = nullptr;
|
||||||
|
int depth = 0;
|
||||||
|
|
||||||
|
while (cur) {
|
||||||
|
parent = cur;
|
||||||
|
cur->size++;
|
||||||
|
if (key < cur->key)
|
||||||
|
cur = cur->left;
|
||||||
|
else
|
||||||
|
cur = cur->right;
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* x = new Node(key);
|
||||||
|
x->parent = parent;
|
||||||
|
|
||||||
|
if (key < parent->key)
|
||||||
|
parent->left = x;
|
||||||
|
else
|
||||||
|
parent->right = x;
|
||||||
|
|
||||||
|
n++;
|
||||||
|
max_n = std::max(max_n, n);
|
||||||
|
|
||||||
|
if (depth > std::log(n) / std::log(1.0 / alpha)) {
|
||||||
|
Node* y = x->parent;
|
||||||
|
while (y && isBalanced(y)) {
|
||||||
|
y = y->parent;
|
||||||
|
}
|
||||||
|
if (y) rebuild(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Search (Standard BST)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* find(int key) {
|
||||||
|
Node* cur = root;
|
||||||
|
while (cur) {
|
||||||
|
if (key == cur->key) return cur;
|
||||||
|
if (key < cur->key) cur = cur->left;
|
||||||
|
else cur = cur->right;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Notes for Extension
|
||||||
|
|
||||||
|
* Add deletion with global rebuild
|
||||||
|
* Support order-statistics (`k`-th element)
|
||||||
|
* Replace `int key` with templates
|
||||||
|
* Remove parent pointers using recursion
|
||||||
132
Data-Structure/Tree/ScapeGoatTree/template.cpp
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
int key;
|
||||||
|
Node *left, *right, *parent;
|
||||||
|
int size;
|
||||||
|
|
||||||
|
Node(int k)
|
||||||
|
: key(k), left(nullptr), right(nullptr), parent(nullptr), size(1) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScapegoatTree {
|
||||||
|
private:
|
||||||
|
const double alpha = 0.75;
|
||||||
|
Node* root = nullptr;
|
||||||
|
int n = 0; // current node count
|
||||||
|
int max_n = 0; // historical maximum node count
|
||||||
|
|
||||||
|
int getSize(Node* x) {
|
||||||
|
return x ? x->size : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(Node* x) {
|
||||||
|
if (x) x->size = getSize(x->left) + getSize(x->right) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void flatten(Node* x, vector<Node*>& arr) {
|
||||||
|
if (!x) return;
|
||||||
|
flatten(x->left, arr);
|
||||||
|
arr.push_back(x);
|
||||||
|
flatten(x->right, arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* build(vector<Node*>& arr, int l, int r, Node* parent) {
|
||||||
|
if (l > r) return nullptr;
|
||||||
|
int m = (l + r) / 2;
|
||||||
|
Node* x = arr[m];
|
||||||
|
x->parent = parent;
|
||||||
|
x->left = build(arr, l, m - 1, x);
|
||||||
|
x->right = build(arr, m + 1, r, x);
|
||||||
|
update(x);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rebuild(Node* x) {
|
||||||
|
vector<Node*> nodes;
|
||||||
|
flatten(x, nodes);
|
||||||
|
Node* parent = x->parent;
|
||||||
|
Node* newSub = build(nodes, 0, nodes.size() - 1, parent);
|
||||||
|
|
||||||
|
if (!parent) root = newSub;
|
||||||
|
else if (parent->left == x) parent->left = newSub;
|
||||||
|
else parent->right = newSub;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isBalanced(Node* x) {
|
||||||
|
return getSize(x->left) <= alpha * x->size &&
|
||||||
|
getSize(x->right) <= alpha * x->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void insert(int key) {
|
||||||
|
if (!root) {
|
||||||
|
root = new Node(key);
|
||||||
|
n = max_n = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* cur = root;
|
||||||
|
Node* parent = nullptr;
|
||||||
|
int depth = 0;
|
||||||
|
|
||||||
|
while (cur) {
|
||||||
|
parent = cur;
|
||||||
|
cur->size++;
|
||||||
|
if (key < cur->key) cur = cur->left;
|
||||||
|
else cur = cur->right;
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* x = new Node(key);
|
||||||
|
x->parent = parent;
|
||||||
|
if (key < parent->key) parent->left = x;
|
||||||
|
else parent->right = x;
|
||||||
|
|
||||||
|
n++;
|
||||||
|
max_n = max(max_n, n);
|
||||||
|
|
||||||
|
if (depth > log(n) / log(1.0 / alpha)) {
|
||||||
|
Node* y = x->parent;
|
||||||
|
while (y && isBalanced(y)) y = y->parent;
|
||||||
|
if (y) rebuild(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* find(int key) {
|
||||||
|
Node* cur = root;
|
||||||
|
while (cur) {
|
||||||
|
if (key == cur->key) return cur;
|
||||||
|
if (key < cur->key) cur = cur->left;
|
||||||
|
else cur = cur->right;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void inorder(Node* x) {
|
||||||
|
if (!x) return;
|
||||||
|
inorder(x->left);
|
||||||
|
cout << x->key << " ";
|
||||||
|
inorder(x->right);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print() { inorder(root); cout << endl; }
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
ScapegoatTree tree;
|
||||||
|
|
||||||
|
vector<int> keys = {50, 20, 70, 10, 30, 60, 80, 25};
|
||||||
|
for (int k : keys) tree.insert(k);
|
||||||
|
|
||||||
|
cout << "Inorder traversal of Scapegoat Tree: ";
|
||||||
|
tree.print();
|
||||||
|
|
||||||
|
int query = 25;
|
||||||
|
Node* res = tree.find(query);
|
||||||
|
if (res) cout << "Found " << query << " in tree.\n";
|
||||||
|
else cout << query << " not found.\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
72
Data-Structure/Tree/SegmentTree/template.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
using namespace std;
|
||||||
|
const int MAXN = 100005;
|
||||||
|
long long tree[MAXN << 2];
|
||||||
|
long long lazy[MAXN << 2];
|
||||||
|
int a[MAXN];
|
||||||
|
|
||||||
|
//Renew ancestor node
|
||||||
|
void pushUp(int p){
|
||||||
|
//Use *sum* as an example
|
||||||
|
tree[p] = tree[p << 1] + tree[p << 1 | 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Renew children through LazyTag(DirtyBit)
|
||||||
|
void pushDown(int p, int l, int r){
|
||||||
|
if(lazy[p] != 0){
|
||||||
|
int mid = l + (r-l)/2;
|
||||||
|
//Change tree and array
|
||||||
|
//if lazy[p]!0, pass lazy to lazy[kids](*length), tree[kids] + lazy, lazy[p]=0
|
||||||
|
lazy[p << 1] += lazy[p];
|
||||||
|
tree[p << 1] += lazy[p] * (mid-l+1);
|
||||||
|
lazy[p << 1 | 1] += lazy[p];
|
||||||
|
tree[p << 1 | 1] += lazy[p] * (r-mid);
|
||||||
|
/*You *Should Not* Recursively pushDown
|
||||||
|
Controlled by void update() or void ll query()
|
||||||
|
Otherwise: *O(log n) -> O(n)*
|
||||||
|
*/
|
||||||
|
lazy[p] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void build(int p, int l, int r){
|
||||||
|
lazy[p] = 0;
|
||||||
|
if(l == r) {
|
||||||
|
tree[p] = a[l];
|
||||||
|
return;
|
||||||
|
//single node noneed to pushup
|
||||||
|
}
|
||||||
|
int mid = l + (r-l)/2;
|
||||||
|
build(p << 1, l, mid);
|
||||||
|
build(p << 1 | 1, mid+1, r);
|
||||||
|
pushUp(p);
|
||||||
|
}
|
||||||
|
// Update: In [L, R], each add v
|
||||||
|
void update(int L, int R, int v, int l, int r, int p){
|
||||||
|
//Fully covered by [l, r]
|
||||||
|
if (L <= l && r <= R) {
|
||||||
|
tree[p] += (long long)v * (r-l+1);
|
||||||
|
lazy[p] += v;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Unfully covered by [l, r]
|
||||||
|
pushDown(p, l, r);
|
||||||
|
int mid = l + (r-l)/2;
|
||||||
|
//Now Recursively Update
|
||||||
|
if (L <= mid) update(L, R, v, l, mid, p << 1);
|
||||||
|
if (mid > R) update(L, R, v, mid+1, r, p << 1 | 1);
|
||||||
|
pushUp(p);
|
||||||
|
}
|
||||||
|
long long query(int L, int R, int l, int r, int p){
|
||||||
|
if (L <= l && r <= R) return tree[p];
|
||||||
|
pushDown(p, l, r);
|
||||||
|
int mid = (l+r) >> 1;
|
||||||
|
long long res = 0;
|
||||||
|
if(L <= mid) res += query(L, R, l, mid, p << 1);
|
||||||
|
if(R > mid) res += query(L, R, mid+1, r, p << 1 | 1);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
int main(){
|
||||||
|
int n;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
199
Data-Structure/Tree/SplayTree/Readme.md
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# 🌳 Splay Tree — A Self-Adjusting Binary Search Tree
|
||||||
|
|
||||||
|
## 1. What is a Splay Tree?
|
||||||
|
|
||||||
|
A **Splay Tree** is a type of **self-adjusting Binary Search Tree (BST)**.
|
||||||
|
Unlike AVL or Red–Black Trees, it **does not maintain explicit balance rules**.
|
||||||
|
|
||||||
|
Instead, it follows one simple idea:
|
||||||
|
|
||||||
|
> **Every time a node is accessed, move it to the root using rotations.**
|
||||||
|
|
||||||
|
This operation is called **splaying**.
|
||||||
|
|
||||||
|
As a result, the tree dynamically reorganizes itself based on **access patterns**, not height constraints.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Core Idea: “Access → Root”
|
||||||
|
|
||||||
|
In a Splay Tree:
|
||||||
|
|
||||||
|
* Searching for a key
|
||||||
|
* Inserting a node
|
||||||
|
* Deleting a node
|
||||||
|
|
||||||
|
All end with the same operation:
|
||||||
|
|
||||||
|
> 🔁 **Splay the accessed node to the root**
|
||||||
|
|
||||||
|
This is done via a sequence of rotations:
|
||||||
|
|
||||||
|
* **Zig** (single rotation)
|
||||||
|
* **Zig–Zig** (double rotation, same direction)
|
||||||
|
* **Zig–Zag** (double rotation, opposite directions)
|
||||||
|
|
||||||
|
After splaying:
|
||||||
|
|
||||||
|
* The accessed node becomes the **root**
|
||||||
|
* Nodes “related” to it move closer to the top
|
||||||
|
* Frequently accessed nodes stay shallow
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. What Makes Splay Trees Special?
|
||||||
|
|
||||||
|
### 3.1 No Explicit Balance Condition
|
||||||
|
|
||||||
|
Splay Trees:
|
||||||
|
|
||||||
|
* Do **not** store height, color, or priority
|
||||||
|
* Do **not** rebalance on every insert/delete
|
||||||
|
* May look “unbalanced” at any moment
|
||||||
|
|
||||||
|
Yet, surprisingly:
|
||||||
|
|
||||||
|
> ✅ **All operations run in amortized (O(\log n)) time**
|
||||||
|
|
||||||
|
This is proven using amortized analysis (potential method).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 Self-Adjusting Behavior
|
||||||
|
|
||||||
|
Splay Trees adapt automatically to access patterns:
|
||||||
|
|
||||||
|
* Recently accessed nodes become fast to access again
|
||||||
|
* Sequential access becomes extremely efficient
|
||||||
|
* “Hot” keys naturally move near the root
|
||||||
|
|
||||||
|
This gives Splay Trees several strong properties:
|
||||||
|
|
||||||
|
* **Working-set property**
|
||||||
|
* **Static optimality**
|
||||||
|
* **Dynamic finger property**
|
||||||
|
|
||||||
|
These properties are hard (or impossible) to guarantee with strictly balanced trees.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Splay Tree as an “Easy-to-Pivot” Tree
|
||||||
|
|
||||||
|
A very useful way to think about Splay Trees is:
|
||||||
|
|
||||||
|
> 🧠 **Any accessed key can instantly become a pivot of the entire tree**
|
||||||
|
|
||||||
|
Once a node `x` is splayed to the root:
|
||||||
|
|
||||||
|
* All keys `< x` are in the left subtree
|
||||||
|
* All keys `> x` are in the right subtree
|
||||||
|
|
||||||
|
This leads directly to powerful structural operations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Split and Merge — The Real Power
|
||||||
|
|
||||||
|
### 5.1 Split Operation
|
||||||
|
|
||||||
|
**Split** divides a tree into two trees based on a key `k`:
|
||||||
|
|
||||||
|
* `Left`: all keys `≤ k`
|
||||||
|
* `Right`: all keys `> k`
|
||||||
|
|
||||||
|
In a Splay Tree, this is easy:
|
||||||
|
|
||||||
|
1. Search for `k` (or the closest node)
|
||||||
|
2. Splay it to the root
|
||||||
|
3. Detach its left or right subtree
|
||||||
|
|
||||||
|
Because access automatically moves nodes to the root, **split is almost free**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.2 Merge Operation
|
||||||
|
|
||||||
|
**Merge** combines two trees:
|
||||||
|
|
||||||
|
* All keys in `Left` are smaller than those in `Right`
|
||||||
|
|
||||||
|
Method:
|
||||||
|
|
||||||
|
1. Splay the **maximum node** of `Left` to the root
|
||||||
|
2. Attach `Right` as its right child
|
||||||
|
|
||||||
|
Again, splaying makes this simple and efficient.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Interval and Range Manipulation
|
||||||
|
|
||||||
|
By combining **split** and **merge**, Splay Trees can easily isolate any interval `[L, R]`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
T
|
||||||
|
├─ split by R → A , C
|
||||||
|
└─ split A by L-1 → B , Mid
|
||||||
|
```
|
||||||
|
|
||||||
|
Now:
|
||||||
|
|
||||||
|
* `Mid` contains exactly the range `[L, R]`
|
||||||
|
* You can apply operations to `Mid` only
|
||||||
|
* Then merge everything back
|
||||||
|
|
||||||
|
This makes Splay Trees ideal for:
|
||||||
|
|
||||||
|
* Sequence manipulation
|
||||||
|
* Range updates
|
||||||
|
* Subarray operations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Typical Applications
|
||||||
|
|
||||||
|
### 7.1 Text Editors and Ropes
|
||||||
|
|
||||||
|
* Cursor moves locally
|
||||||
|
* Recent edits are reused
|
||||||
|
* Cut / paste / reverse ranges efficiently
|
||||||
|
|
||||||
|
### 7.2 Dynamic Trees (Link–Cut Tree)
|
||||||
|
|
||||||
|
* Splay Tree is the **core data structure**
|
||||||
|
* Supports path queries and updates
|
||||||
|
* AVL / Red–Black Trees cannot replace it here
|
||||||
|
|
||||||
|
### 7.3 Cache-like Access Patterns
|
||||||
|
|
||||||
|
* Frequently accessed keys stay near the root
|
||||||
|
* No need to maintain explicit frequency counters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Comparison with Other BSTs
|
||||||
|
|
||||||
|
| Tree Type | Balance Method | Worst Case | Interval Ops | Adaptivity |
|
||||||
|
| --------- | --------------- | ---------------------- | ------------ | ---------- |
|
||||||
|
| AVL | Height | (O(\log n)) | Hard | ❌ |
|
||||||
|
| Red–Black | Color rules | (O(\log n)) | Hard | ❌ |
|
||||||
|
| Treap | Random priority | (O(\log n)) (expected) | Easy | ❌ |
|
||||||
|
| **Splay** | Access-based | (O(n)) (single op) | **Easy** | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. When NOT to Use Splay Trees
|
||||||
|
|
||||||
|
Splay Trees are **not ideal** when:
|
||||||
|
|
||||||
|
* Strict worst-case latency is required
|
||||||
|
* Access patterns are uniformly random
|
||||||
|
* Predictable structure is more important than adaptivity
|
||||||
|
|
||||||
|
In such cases, AVL or Red–Black Trees may be better.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. One-Sentence Summary
|
||||||
|
|
||||||
|
> **A Splay Tree is a self-adjusting BST where any accessed node becomes the root, making the tree extremely easy to split, merge, and reorganize around chosen keys — with strong amortized performance guarantees.**
|
||||||
125
Data-Structure/Tree/SplayTree/template.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
int key;
|
||||||
|
Node *fa, *ch[2];
|
||||||
|
int sz;
|
||||||
|
|
||||||
|
Node(int k) : key(k), fa(nullptr), sz(1) {
|
||||||
|
ch[0] = ch[1] = nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline int size(Node* x) {
|
||||||
|
return x ? x->sz : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void pull(Node* x) {
|
||||||
|
if (x)
|
||||||
|
x->sz = 1 + size(x->ch[0]) + size(x->ch[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool is_right(Node* x) {
|
||||||
|
return x->fa && x->fa->ch[1] == x;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rotate(Node* x) {
|
||||||
|
Node* p = x->fa;
|
||||||
|
Node* g = p->fa;
|
||||||
|
bool dir = (x == p->ch[1]); // 0 = left, 1 = right
|
||||||
|
|
||||||
|
// connect x's opposite child to p
|
||||||
|
p->ch[dir] = x->ch[dir ^ 1];
|
||||||
|
if (x->ch[dir ^ 1])
|
||||||
|
x->ch[dir ^ 1]->fa = p;
|
||||||
|
|
||||||
|
// move p under x
|
||||||
|
x->ch[dir ^ 1] = p;
|
||||||
|
p->fa = x;
|
||||||
|
|
||||||
|
// connect x to g
|
||||||
|
x->fa = g;
|
||||||
|
if (g) {
|
||||||
|
if (g->ch[0] == p) g->ch[0] = x;
|
||||||
|
else g->ch[1] = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
pull(p);
|
||||||
|
pull(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
void splay(Node* x, Node* goal = nullptr) {
|
||||||
|
while (x->fa != goal) {
|
||||||
|
Node* p = x->fa;
|
||||||
|
Node* g = p->fa;
|
||||||
|
|
||||||
|
if (g != goal) {
|
||||||
|
if ((g->ch[0] == p) == (p->ch[0] == x))
|
||||||
|
rotate(p); // zig-zig
|
||||||
|
else
|
||||||
|
rotate(x); // zig-zag
|
||||||
|
}
|
||||||
|
rotate(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Split by k, result: left <= k, right >= k
|
||||||
|
*/
|
||||||
|
void split(Node* root, int k, Node*& left, Node*& right) {
|
||||||
|
Node* cur = root;
|
||||||
|
Node* last = nullptr;
|
||||||
|
|
||||||
|
while (cur) {
|
||||||
|
last = cur;
|
||||||
|
if (k < cur->key)
|
||||||
|
cur = cur->ch[0];
|
||||||
|
else
|
||||||
|
cur = cur->ch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last)
|
||||||
|
splay(last);
|
||||||
|
|
||||||
|
if (!last) {
|
||||||
|
left = right = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last->key <= k) {
|
||||||
|
left = last;
|
||||||
|
right = last->ch[1];
|
||||||
|
if (right) right->fa = nullptr;
|
||||||
|
left->ch[1] = nullptr;
|
||||||
|
pull(left);
|
||||||
|
} else {
|
||||||
|
right = last;
|
||||||
|
left = last->ch[0];
|
||||||
|
if (left) left->fa = nullptr;
|
||||||
|
right->ch[0] = nullptr;
|
||||||
|
pull(right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* merge(Node* left, Node* right) {
|
||||||
|
if (!left) return right;
|
||||||
|
if (!right) return left;
|
||||||
|
|
||||||
|
// find max of left
|
||||||
|
Node* cur = left;
|
||||||
|
while (cur->ch[1])
|
||||||
|
cur = cur->ch[1];
|
||||||
|
|
||||||
|
splay(cur); // max becomes root
|
||||||
|
cur->ch[1] = right;
|
||||||
|
right->fa = cur;
|
||||||
|
|
||||||
|
pull(cur);
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
67
Data-Structure/Tree/Treap/FHQ.cpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
int key, priority;
|
||||||
|
int left, right;
|
||||||
|
int size;
|
||||||
|
};
|
||||||
|
|
||||||
|
const int MAXN = 200000;
|
||||||
|
Node tr[MAXN];
|
||||||
|
int tot = 0;
|
||||||
|
|
||||||
|
int newNode(int key) {
|
||||||
|
++tot;
|
||||||
|
tr[tot] = {key, rand(), 0, 0, 1};
|
||||||
|
return tot;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSize(int x) {
|
||||||
|
return x ? tr[x].size : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushUp(int x) {
|
||||||
|
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split by key: left <= key, right > key
|
||||||
|
void split(int root, int key, int &x, int &y) {
|
||||||
|
if (!root) {
|
||||||
|
x = y = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tr[root].key <= key) {
|
||||||
|
x = root;
|
||||||
|
split(tr[root].right, key, tr[root].right, y);
|
||||||
|
pushUp(x);
|
||||||
|
} else {
|
||||||
|
y = root;
|
||||||
|
split(tr[root].left, key, x, tr[root].left);
|
||||||
|
pushUp(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int merge(int x, int y) {
|
||||||
|
if (!x || !y) return x | y;
|
||||||
|
if (tr[x].priority > tr[y].priority) {
|
||||||
|
tr[x].right = merge(tr[x].right, y);
|
||||||
|
pushUp(x);
|
||||||
|
return x;
|
||||||
|
} else {
|
||||||
|
tr[y].left = merge(x, tr[y].left);
|
||||||
|
pushUp(y);
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert key into treap
|
||||||
|
void insert(int &root, int key) {
|
||||||
|
int x, y;
|
||||||
|
split(root, key, x, y);
|
||||||
|
root = merge(merge(x, newNode(key)), y);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
283
Data-Structure/Tree/Treap/Readme.md
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
# Treap: A Randomized Balanced Binary Search Tree
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
|
||||||
|
A **Treap** (Tree + Heap) is a **randomized balanced binary search tree** that simultaneously satisfies:
|
||||||
|
|
||||||
|
1. **Binary Search Tree (BST) property** on keys
|
||||||
|
2. **Heap property** on priorities
|
||||||
|
|
||||||
|
By assigning each node a random priority, Treap achieves **expected logarithmic height**, providing efficient dynamic set and sequence operations.
|
||||||
|
|
||||||
|
Treaps are widely used in:
|
||||||
|
|
||||||
|
* Dynamic ordered sets
|
||||||
|
* Sequence maintenance
|
||||||
|
* Range query problems
|
||||||
|
* Competitive programming as a flexible alternative to AVL / Red-Black trees
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Structural Properties
|
||||||
|
|
||||||
|
Each Treap node contains:
|
||||||
|
|
||||||
|
* `key`: used to maintain BST ordering
|
||||||
|
* `priority`: a randomly assigned value
|
||||||
|
* `left`, `right`: child pointers (or indices)
|
||||||
|
* Optional augmented data (e.g., subtree size, sum)
|
||||||
|
|
||||||
|
### 2.1 BST Property
|
||||||
|
|
||||||
|
For any node `u`:
|
||||||
|
|
||||||
|
* All keys in `u.left` are **less than** `u.key`
|
||||||
|
* All keys in `u.right` are **greater than** `u.key`
|
||||||
|
|
||||||
|
### 2.2 Heap Property
|
||||||
|
|
||||||
|
For any node `u`:
|
||||||
|
|
||||||
|
* `priority(u)` is **greater than or equal to** the priorities of its children
|
||||||
|
(Max-heap convention; min-heap also works symmetrically)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Why Randomization Works
|
||||||
|
|
||||||
|
If priorities are independent random variables, then:
|
||||||
|
|
||||||
|
* The expected height of the Treap is **O(log n)**
|
||||||
|
* All standard BST operations run in **expected O(log n)** time
|
||||||
|
|
||||||
|
This avoids the need for strict rebalancing rules, unlike AVL or Red-Black trees.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Implementation Models
|
||||||
|
|
||||||
|
In practice, Treaps are implemented as **pointer-based trees**.
|
||||||
|
However, for efficiency and memory safety, most implementations use:
|
||||||
|
|
||||||
|
> **Array-based node pools with integer indices simulating pointers**
|
||||||
|
|
||||||
|
This approach:
|
||||||
|
|
||||||
|
* Avoids frequent dynamic memory allocation
|
||||||
|
* Improves cache locality
|
||||||
|
* Is standard in algorithmic contexts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Rotating Treap (Classic Treap)
|
||||||
|
|
||||||
|
### 5.1 Core Idea
|
||||||
|
|
||||||
|
Insertion proceeds as in a normal BST by `key`.
|
||||||
|
If the heap property is violated after insertion, **tree rotations** are used to restore it.
|
||||||
|
|
||||||
|
### 5.2 Operations
|
||||||
|
|
||||||
|
* Insert: BST insert + rotations
|
||||||
|
* Delete: rotate node down until removable
|
||||||
|
* Search: standard BST search
|
||||||
|
|
||||||
|
### 5.3 Characteristics
|
||||||
|
|
||||||
|
**Advantages**
|
||||||
|
|
||||||
|
* Conceptually close to AVL / Red-Black trees
|
||||||
|
* Intuitive if rotations are already familiar
|
||||||
|
|
||||||
|
**Disadvantages**
|
||||||
|
|
||||||
|
* Rotation logic can be error-prone
|
||||||
|
* Slightly harder to extend for sequence problems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. FHQ Treap (Split & Merge Treap)
|
||||||
|
|
||||||
|
### 6.1 Core Idea
|
||||||
|
|
||||||
|
The FHQ Treap (named after its proposer) eliminates rotations entirely.
|
||||||
|
|
||||||
|
It relies on two fundamental operations:
|
||||||
|
|
||||||
|
1. **Split**
|
||||||
|
|
||||||
|
* Divide a Treap into two based on a key (or position)
|
||||||
|
2. **Merge**
|
||||||
|
|
||||||
|
* Combine two Treaps assuming all keys in the left are smaller
|
||||||
|
|
||||||
|
All operations are expressed using **split + merge**, preserving both BST and heap properties automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6.2 Why FHQ Treap Is Popular
|
||||||
|
|
||||||
|
* No explicit rotations
|
||||||
|
* Cleaner and more modular code
|
||||||
|
* Ideal for **implicit Treap** (sequence problems)
|
||||||
|
* Easier to augment with lazy propagation
|
||||||
|
|
||||||
|
As a result, FHQ Treap is the **dominant Treap variant** in competitive programming.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Comparison Summary
|
||||||
|
|
||||||
|
| Aspect | Rotating Treap | FHQ Treap |
|
||||||
|
| ---------------- | -------------- | ------------- |
|
||||||
|
| Balancing method | Rotations | Split & Merge |
|
||||||
|
| Code complexity | Medium | Low |
|
||||||
|
| Extensibility | Moderate | Excellent |
|
||||||
|
| Sequence support | Harder | Natural |
|
||||||
|
| Contest usage | Less common | Very common |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Rotating Treap — C++ Template
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
int key, priority;
|
||||||
|
int left, right;
|
||||||
|
int size;
|
||||||
|
};
|
||||||
|
|
||||||
|
const int MAXN = 200000;
|
||||||
|
Node tr[MAXN];
|
||||||
|
int tot = 0;
|
||||||
|
int root = 0;
|
||||||
|
|
||||||
|
int newNode(int key) {
|
||||||
|
++tot;
|
||||||
|
tr[tot] = {key, rand(), 0, 0, 1};
|
||||||
|
return tot;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSize(int x) {
|
||||||
|
return x ? tr[x].size : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushUp(int x) {
|
||||||
|
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rotateLeft(int &x) {
|
||||||
|
int y = tr[x].right;
|
||||||
|
tr[x].right = tr[y].left;
|
||||||
|
tr[y].left = x;
|
||||||
|
pushUp(x);
|
||||||
|
pushUp(y);
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rotateRight(int &x) {
|
||||||
|
int y = tr[x].left;
|
||||||
|
tr[x].left = tr[y].right;
|
||||||
|
tr[y].right = x;
|
||||||
|
pushUp(x);
|
||||||
|
pushUp(y);
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(int &x, int key) {
|
||||||
|
if (!x) {
|
||||||
|
x = newNode(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key < tr[x].key) {
|
||||||
|
insert(tr[x].left, key);
|
||||||
|
if (tr[tr[x].left].priority > tr[x].priority)
|
||||||
|
rotateRight(x);
|
||||||
|
} else {
|
||||||
|
insert(tr[x].right, key);
|
||||||
|
if (tr[tr[x].right].priority > tr[x].priority)
|
||||||
|
rotateLeft(x);
|
||||||
|
}
|
||||||
|
pushUp(x);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. FHQ Treap — C++ Template
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
int key, priority;
|
||||||
|
int left, right;
|
||||||
|
int size;
|
||||||
|
};
|
||||||
|
|
||||||
|
const int MAXN = 200000;
|
||||||
|
Node tr[MAXN];
|
||||||
|
int tot = 0;
|
||||||
|
|
||||||
|
int newNode(int key) {
|
||||||
|
++tot;
|
||||||
|
tr[tot] = {key, rand(), 0, 0, 1};
|
||||||
|
return tot;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSize(int x) {
|
||||||
|
return x ? tr[x].size : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushUp(int x) {
|
||||||
|
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split by key: left <= key, right > key
|
||||||
|
void split(int root, int key, int &x, int &y) {
|
||||||
|
if (!root) {
|
||||||
|
x = y = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tr[root].key <= key) {
|
||||||
|
x = root;
|
||||||
|
split(tr[root].right, key, tr[root].right, y);
|
||||||
|
pushUp(x);
|
||||||
|
} else {
|
||||||
|
y = root;
|
||||||
|
split(tr[root].left, key, x, tr[root].left);
|
||||||
|
pushUp(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int merge(int x, int y) {
|
||||||
|
if (!x || !y) return x | y;
|
||||||
|
if (tr[x].priority > tr[y].priority) {
|
||||||
|
tr[x].right = merge(tr[x].right, y);
|
||||||
|
pushUp(x);
|
||||||
|
return x;
|
||||||
|
} else {
|
||||||
|
tr[y].left = merge(x, tr[y].left);
|
||||||
|
pushUp(y);
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert key into treap
|
||||||
|
void insert(int &root, int key) {
|
||||||
|
int x, y;
|
||||||
|
split(root, key, x, y);
|
||||||
|
root = merge(merge(x, newNode(key)), y);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Closing Remarks
|
||||||
|
|
||||||
|
Treap combines the simplicity of BSTs with the robustness of randomized balancing.
|
||||||
|
Among its variants, **FHQ Treap** stands out for its elegance, extensibility, and practical usefulness, especially in sequence-based problems.
|
||||||
67
Data-Structure/Tree/Treap/rotate.cpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
int key, priority;
|
||||||
|
int left, right;
|
||||||
|
int size;
|
||||||
|
};
|
||||||
|
|
||||||
|
const int MAXN = 200000;
|
||||||
|
Node tr[MAXN];
|
||||||
|
int tot = 0;
|
||||||
|
int root = 0;
|
||||||
|
|
||||||
|
int newNode(int key) {
|
||||||
|
++tot;
|
||||||
|
tr[tot] = {key, rand(), 0, 0, 1};
|
||||||
|
return tot;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSize(int x) {
|
||||||
|
return x ? tr[x].size : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushUp(int x) {
|
||||||
|
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rotateLeft(int &x) {
|
||||||
|
int y = tr[x].right;
|
||||||
|
tr[x].right = tr[y].left;
|
||||||
|
tr[y].left = x;
|
||||||
|
pushUp(x);
|
||||||
|
pushUp(y);
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rotateRight(int &x) {
|
||||||
|
int y = tr[x].left;
|
||||||
|
tr[x].left = tr[y].right;
|
||||||
|
tr[y].right = x;
|
||||||
|
pushUp(x);
|
||||||
|
pushUp(y);
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(int &x, int key) {
|
||||||
|
if (!x) {
|
||||||
|
x = newNode(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key < tr[x].key) {
|
||||||
|
insert(tr[x].left, key);
|
||||||
|
if (tr[tr[x].left].priority > tr[x].priority)
|
||||||
|
rotateRight(x);
|
||||||
|
} else {
|
||||||
|
insert(tr[x].right, key);
|
||||||
|
if (tr[tr[x].right].priority > tr[x].priority)
|
||||||
|
rotateLeft(x);
|
||||||
|
}
|
||||||
|
pushUp(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||