Commit on 2025/05/14 周三 13:59:24.57

This commit is contained in:
zhangsan 2025-05-14 13:59:24 +08:00
parent 6c517faa49
commit fb3525c12d
7 changed files with 296 additions and 116 deletions

View File

@ -97,6 +97,18 @@ $$
| **性质** | **特征分解/谱分解** | **奇异值分解SVD** |
| ------------ | -------------------------------- | ------------------------------------------ |
| **适用矩阵** | 仅限**方阵**$n \times n$ | **任意矩阵**$m \times n$,包括矩形矩阵) |
| **分解形式** | $A = P \Lambda P^{-1}$ | $A = U \Sigma V^*$ |
| **矩阵类型** | 可对角化矩阵(如对称、正规矩阵) | 所有矩阵(包括不可对角化的方阵和非方阵) |
| **输出性质** | 特征值($\lambda_i$)可能是复数 | 奇异值($\sigma_i$)始终为非负实数 |
| **正交性** | 仅当 $A$ 正规时 $P$ 是酉矩阵 | $U$ 和 $V$ 始终是酉矩阵(正交) |
谱分解的对象为实对称矩阵,
## 网络重构分析

View File

@ -334,77 +334,83 @@ $$
由于直接计算期望复杂TGAT 用蒙特卡洛采样近似:
1. 从分布 $\mu$ 中采样 $k$ 个频率 $\omega_1, \omega_2, \ldots, \omega_k$。
1. 从分布 $\mu$ 中采样 $d$ 个频率 $\omega_1, \omega_2, \ldots, \omega_k$。
2. 用这些频率构造实值编码(取复指数的实部和虚部,即 $\cos$ 和 $\sin$
$$
\phi(\Delta t) = \sqrt{\frac{1}{k}} \left[ \cos(\omega_1 \Delta t), \sin(\omega_1 \Delta t), \ldots, \cos(\omega_k \Delta t), \sin(\omega_k \Delta t) \right].
\Phi_d(\Delta t) = \frac{1}{\sqrt d}\bigl[\cos(\omega_1 \Delta t),\sin(\omega_1 \Delta t),\dots,\cos(\omega_d \Delta t),\sin(\omega_d \Delta t)\bigr]
$$
里 $k$ 是采样频率的数量,决定了编码的维度 $d=2k$(每组 $\cos + \sin$ 占 2 维)。
样就把时间差转成了一个长度为 $2d$ 的实值向量。
#### 例子
假设将时间$t$编码为一个2维向量实际论文中维度更高比如100维
时间编码函数为:
取 $d=1$(此时时间编码向量就是二维的),有
$$
\Phi_d(t) = \sqrt{\frac{1}{d}} \left[ \cos(\omega_1 t), \sin(\omega_1 t), \cos(\omega_2 t), \sin(\omega_2 t), \ldots \right]
\Phi_1(\Delta t)\;=\;\frac{1}{\sqrt1}\bigl[\cos(\omega_1\Delta t),\;\sin(\omega_1\Delta t)\bigr]
=\bigl[\cos(\omega_1\Delta t),\;\sin(\omega_1\Delta t)\bigr].
$$
其中:
- $d$是维度(这里$d=2$即2维向量
- $\omega_1, \omega_2, \ldots$ 是从某个分布$p(\omega)$中随机采样的频率参数(可以理解为“时间刻度”)。
例如,若我们令 $\omega_1=1$ 且 $\Delta t = \pi/3$,那么
$$
\Phi_1\bigl(\pi/3\bigr)
=\bigl[\cos(\pi/3),\;\sin(\pi/3)\bigr]
=\bigl[0.5,\;0.866\!\dots\bigr].
$$
1. **随机初始化频率**
假设我们随机设定两个频率:
- $\omega_1 = 0.5$
- $\omega_2 = 1.0$
2. **编码时间点**
- 对时间$t=2$,计算:
$$
\Phi_2(2) = \sqrt{\frac{1}{2}} \left[ \cos(0.5 \cdot 2), \sin(0.5 \cdot 2) \right] = \sqrt{0.5} \left[ \cos(1.0), \sin(1.0) \right] \approx [0.54, 0.84]
$$
- 对时间$t=5$,计算:
$$
\Phi_2(5) = \sqrt{0.5} \left[ \cos(2.5), \sin(2.5) \right] \approx [-0.35, 0.94]
$$
3. **时间差异的体现**
- 两个时间点$t=2$和$t=5$的编码向量不同,且它们的点积(相似度)会反映时间跨度$|5-2|=3$
$$
\Phi_2(2) \cdot \Phi_2(5) \approx 0.54 \cdot (-0.35) + 0.84 \cdot 0.94 \approx 0.59
$$
- 如果$t=2$和$t=4$的编码点积更大,说明$\Delta t=2$比$\Delta t=3$的交互更相关。
所以在这个例子里,二维时间编码就是 $\bigl[0.5,\;0.866]$。
#### **为什么这样设计?**
- **谐波基函数**`cos(ωt)``sin(ωt)`是周期性函数,能捕捉时间的周期性模式(比如白天/夜晚的周期性行为,但不会强制引入周期性)。
- **内积反映时间差**通过Bochner定理两个时间编码的内积`Φ(t₁)·Φ(t₂)`近似于一个时间核函数`𝒦(t₁-t₂)`,时间差越小,内积越大(相似性越高)。
- 普通的图神经网络即使有时间戳也只能把它当作一个离散特征很难推广到模型没见过的时刻。TGAT通过把相对时间差 $\Delta t$ 映射成一组连续、周期性的函数值 $(\cos(\omega_1 \Delta t),\sin(\omega_1 \Delta t))$,让模型天然具备处理任意实数时间差的能力。
- 用一堆正余弦函数来编码时间,使得时间特征是平滑连续的
- 节点对邻居信息的关注度不仅由节点特征决定,也由时间编码决定。模型可以自动学出“距离现在越近的交互越重要”“一周前的活动强度要比一个月前的活动强度高”之类的规律。
- **可学习性**:频率参数`{ωᵢ}`可以通过训练优化(例如让模型自动学习“短期交互”和“长期交互”的不同时间尺度)。
### 需要维护的数据:
1. **全量事件日志(事件流)**
整个动态图被表示成一条“事件流”──每一条事件是一个三元组
$$
(u, v, t)
$$
表示节点 $u$ 和节点 $v$ 在时刻 $t$ 上发生了一次交互。训练推断时TGAT 会遍历这条有时间戳的边列表,按时间顺序对每个目标节点做消息聚合。
2. **每个节点的历史邻居列表**
为了快速地找到“截止当前时刻 $t$,某节点 $v$ 最近接触过哪些节点”TGAT 通常会为每个节点维护一个按时间排序的邻居列表:
$$
\mathit{Nbrs}(v) \;=\; [(u_1,t_1),\,(u_2,t_2),\,\dots]
$$
其中每个 $(u_i, t_i)$ 表示节点 $u_i$ 在时间 $t_i$ 与 $v$ 交互过。这样,给定当前时间 $t$,就可以高效地检索出所有满足 $t_i < t$ 的历史交互
3. **每层的节点隐藏状态**
TGAT 是一个多层multi-layer注意力网络在第 $l$ 层它会产生每个节点在各个时间点的隐藏表示
$$
\tilde h_v^{(l)}(t)\,.
$$
为了在更高层次继续聚合,这些中间表示需要被缓存下来,至少要保存上一次(前一层)计算出的向量,才能和后续的时间编码一起拼接、送入下一层注意力。
4. **时间编码的频率参数**
TGAT 中的 $\{\omega_k\}_{k=1}^d$(映射 $\Delta t\mapsto [\cos(\omega_k\Delta t),\sin(\omega_k\Delta t)]$ 用的频率向量)是模型的可学习参数,也需要存储。
### 工作原理
#### 1.时间约束的邻居集合
#### 1.检索时间邻居
**时间约束**:对于目标节点 $v_0$ 在时间 $t$,其邻居 $\mathcal{N}(v_0; t)$ 仅包含与 $v_0$ **在时间 $t$ 之前发生过交互的节点**(即 $t_i < t$)。
从事件流中取出所有在 $t$ 之前与 $v_0$ 有过交互的节点集合以及交互的时间 $t_i$。
$$
\mathcal{N}(v_0; t) = \{ v_i \mid \exists (v_0, v_i, t_i) \in \mathcal{E}, t_i < t \}
$$
实际实现中TGAT通过以下两种策略避免邻居集合无限膨胀
1**固定时间窗口Sliding Time Window**
1**时间窗口截断**
- 仅保留目标时间 $t$ 的最近 $\Delta T$ 时间内的交互节点例如过去7天的邻居
@ -416,50 +422,91 @@ $$
2**邻居采样Neighborhood Sampling**
- 即使在一个时间窗口内如果邻居数量过多例如社交网络中的活跃用户TGAT会随机采样固定数量的邻居如最多20个
- 随机采样可行是因为**时间编码**和**注意力权重**会自动学习为近期交互分配更高权重。
#### 2.时间编码的邻居特征矩阵
#### 2.准备输入特征+时间编码
在 TGAT 模型中,每一层都需要计算隐藏表示:
在第 $l$ 层里,既要计算目标节点 $v_0$ 在当前时刻 $t$ 的隐藏表示 $\tilde{h}_0^{(l-1)}(t)$,也要计算它的每个邻居 $v_i$ 在各自交互时刻 $t_i$ 的隐藏表示 $\tilde{h}_i^{(l-1)}(t_i)$。
- 若 $l=1$,则将原始静态特征 $x_i$ 作为第 0 层隐藏表示 $\tilde h_i^{(0)}(t_i)$
- 若 $l>1$,则使用第 $(l-1)$ 层计算出的隐藏表示 $\tilde h_i^{(l-1)}(t_i)$。
对每个邻居 $v_i$ 计算相对时间差 $\Delta t_i = t - t_i$,再通过
$$
Z(t) = \left[ \tilde{h}_0^{(l-1)}(t) \| \Phi_{d_T}(0), \tilde{h}_1^{(l-1)}(t_1) \| \Phi_{d_T}(t-t_1), \ldots \right]^\top
\Phi_d(\Delta t_i) = \tfrac1{\sqrt d}\bigl[\cos(\omega_1\Delta t_i),\sin(\omega_1\Delta t_i),\dots\bigr]
$$
**输入**
得到长度为 $2d$ 的时间编码向量。对目标节点自身,使用 $\Delta t=0$ 的时间编码。
- 目标节点 $v_0$ 在时间 $t$ 的上一层特征:$\tilde{h}_0^{(l-1)}(t)$。
- 邻居节点 $v_i$ 在历史时间 $t_i$ 的特征:$\tilde{h}_1^{(l-1)}(t_1), \tilde{h}_2^{(l-1)}(t_2), \ldots$。
- 时间编码函数 $\Phi_{d_T}$:将时间差 $t-t_i$ 映射为向量
#### 3.注意力权重计算
通过Query-Key-Value机制计算邻居权重
$$
\alpha_i = \text{softmax} \left( \frac{(q(t)^\top K_i(t))}{\sqrt{d_h}} \right), \quad q(t) = [Z(t)]_0 W_Q, \quad K_i(t) = [Z(t)]_i W_K
$$
- $q(t)$目标节点的Query。
- $K_i(t)$邻居节点的Key。
- **动态性**:权重 $\alpha_i$ 依赖当前时间 $t$ 和邻居交互时间 $t_i$。
#### 4.多头注意力聚合
#### 3.拼接构建"实体—时间"矩阵
$$
h(t) = \text{Attn}(q(t), K(t), V(t)) = \sum_{i=1}^N \alpha_i V_i(t), \quad V_i(t) = [Z(t)]_i W_V
Z(t) =
\begin{bmatrix}
\tilde h_0^{(l-1)}(t)\,\|\,\Phi_d(0)\\
\tilde h_1^{(l-1)}(t_1)\,\|\,\Phi_d(\Delta t_1)\\
\;\;\vdots\\
\tilde h_N^{(l-1)}(t_N)\,\|\,\Phi_d(\Delta t_N)
\end{bmatrix}
$$
$V_i(t)$邻居节点的Value含时间编码的特征。
其中 $\Delta t_i=t-t_i$ ,这里 $v_0$ 一共有 $N$ 个节点。可能存在同一时间有多个交互的节点
#### 5.节点嵌入更新
#### 4. 线性映射到 Query/Key/Value
分别用三组可学习的线性变换把 $Z(t)$ 投影出
$$
Q = Z(t)W^Q,\quad
K = Z(t)W^K,\quad
V = Z(t)W^V
$$
其中 $Q$ 取第一行(目标节点),$K,V$ 取其余行。
#### 5.计算注意力权重
对每个邻居 $i$ 计算
$$
\alpha_i \;=\;\frac{\exp\bigl(Q^\top K_i\bigr)}{\sum_j\exp\bigl(Q^\top K_j\bigr)}\,,
$$
这样就能在同一时刻、同一节点间对「谁更重要」自动打分。
#### 6.加权求和生成聚合向量
$$
\tilde{h}_0^{(l)}(t) = \text{FFN}(h(t) \| x_0)
h^{(l)}(t)\;=\;\sum_{i\in N(v_0;t)}\alpha_i\,V_i\quad\in\mathbb{R}^{d_h}.
$$
- $h(t)$:聚合后的邻居表示。
- $x_0$:目标节点的原始特征。
- **FFN**Feed-Forward Network进一步融合时空信息。
**动态推理**:对任意新时间 $t$,只需输入当前邻接关系和节点特征,通过前向传播计算嵌入。
#### 7.与目标节点静态特征融合并通过 FFN
把聚合向量 $h^{(l)}(t)$ 和目标节点的原始特征 $x_0$ 拼接,送入两层前馈网络:
$$
\tilde h_0^{(l)}(t)
= \mathrm{FFN}\bigl(h^{(l)}(t)\,\|\,x_0\bigr)
= \mathrm{ReLU}\Bigl(\bigl[h^{(l)}(t)\parallel x_0\bigr] W_0^{(l)} + b_0^{(l)}\Bigr) W_1^{(l)} + b_1^{(l)}
$$
这样就得到了第 $l$ 层节点 $v_0$ 在时刻 $t$ 的最终输出表示。
FFN 并不是只在最后一层使用,而是在**每一层**注意力聚合后都要执行,以产生该层的节点时刻表示。
#### 8.**多头与多层堆叠**
- 如果使用多头注意力,则在步骤 46 中并行做 $k$ 组不同投影,得出 $\{h^{(l,i)}(t)\}_{i=1}^k$,再将它们拼接后送入 FFN
- 堆叠 $L$ 层上述流程,就能把多跳($L$ 跳)邻居信息聚合进来。
@ -469,14 +516,6 @@ $$
每个节点运行的模型一样的。实时性。
节点是否需要交互(是否需要全局信息) //
不知道全局信息(只知道邻居) 训练出来的模型是否一样?(能否协同。)
#### **举例**
假设目标节点 $v_0$ 在时间 $t=10$ 需要聚合历史邻居,其中:
@ -582,7 +621,7 @@ $$
**4.聚合邻居信息**
**4.加权求和生成聚合向量**
$$
h(10) = \sum_{i=1}^3 \alpha_i V_i = 0.024 \cdot \begin{bmatrix}3.52 \\ 3.92 \\ 4.32\end{bmatrix} + 1.6 \times 10^{-5} \cdot \begin{bmatrix}2.12 \\ 2.42 \\ 2.72\end{bmatrix} + 0.976 \cdot \begin{bmatrix}4.48 \\ 4.88 \\ 5.28\end{bmatrix}\approx [4.48, 4.88, 5.28]
$$
@ -603,13 +642,40 @@ $$
### 需提前训练的参数
| **参数名称** | **符号** | **维度** | **作用** | **来源模块** |
| -------------------- | ---------------------------- | ------------------------ | ------------------------------------------------------ | ----------------------------- |
| **时间编码频率参数** | $\omega_1, \ldots, \omega_d$ | $d \times 1$ | 控制时间编码的基频率,用于生成 $\Phi_{d_T}(t)$。 | 功能性时间编码Bochner定理 |
| **Query投影矩阵** | $W_Q$ | $(d + d_T) \times d_h$ | 将目标节点特征和时间编码映射为Query向量。 | 自注意力机制 |
| **Key投影矩阵** | $W_K$ | $(d + d_T) \times d_h$ | 将邻居节点特征和时间编码映射为Key向量。 | 自注意力机制 |
| **Value投影矩阵** | $W_V$ | $(d + d_T) \times d_h$ | 将邻居节点特征和时间编码映射为Value向量。 | 自注意力机制 |
| **FFN第一层权重** | $W_0^{(l)}$ | $(d_h + d_0) \times d_f$ | 前馈网络的第一层线性变换,融合邻居聚合特征和原始特征。 | 前馈网络FFN |
| **FFN第一层偏置** | $b_0^{(l)}$ | $d_f \times 1$ | 第一层的偏置项。 | 前馈网络FFN |
| **FFN第二层权重** | $W_1^{(l)}$ | $d_f \times d$ | 前馈网络的第二层线性变换,生成最终节点表示。 | 前馈网络FFN |
| **FFN第二层偏置** | $b_1^{(l)}$ | $d \times 1$ | 第二层的偏置项。 | 前馈网络FFN |
| 参数名称 | 符号 | 维度 | 来源层 | 作用 |
| ---------------- | ----------------------------- | ----------------------- | ------------- | --------------------------------------------------------- |
| 时间编码频率参数 | $\omega_1,\dots,\omega_{d_T}$ | $d_T\times1$ | 全模型 | 控制功能性时间编码基频率,用于生成 $\Phi_{d_T}(\Delta t)$ |
| Query 投影矩阵 | $W_Q^{(l)}$ | $(d_h + d_T)\times d_h$ | 第 $l$ 层 | 将拼接后的"隐藏表示+时间编码"映射为 Query 向量 |
| Key 投影矩阵 | $W_K^{(l)}$ | $(d_h + d_T)\times d_h$ | 第 $l$ 层 | 将拼接后的"隐藏表示+时间编码"映射为 Key 向量 |
| Value 投影矩阵 | $W_V^{(l)}$ | $(d_h + d_T)\times d_h$ | 第 $l$ 层 | 将拼接后的"隐藏表示+时间编码"映射为 Value 向量 |
| FFN 第一层权重 | $W_0^{(l)}$ | $(d_h + d_0)\times d_f$ | 第 $l$ 层 FFN | 拼接向量 $[\,h^{(l)}(t)\,\|\,x_0\,]$ 的第一层线性变换 |
| FFN 第一层偏置 | $b_0^{(l)}$ | $d_f$ | 第 $l$ 层 FFN | 第一层线性偏置 |
| FFN 第二层权重 | $W_1^{(l)}$ | $d_f\times d$ | 第 $l$ 层 FFN | 第二层线性变换,生成本层最终节点表示 |
| FFN 第二层偏置 | $b_1^{(l)}$ | $d$ | 第 $l$ 层 FFN | 第二层线性偏置 |
每个节点运行的模型是一样的QKV这些参数都是一样的。
运行过程中这些训练好的参数不会变化,模型的动态性体现在:
- 维护**全局事件流**;当新的边 $(u, v, t)$ 加入或旧的边变得不再被采样时,下一次你对某个节点做推理时,检索到的邻居集合就不一样;
- 时间编码会根据新的 $\Delta t$ 动态计算;
- 注意力权重 $\alpha_i$ 会重新分配FFN 也会基于新的聚合结果给出新的输出。
节点是否需要交互(是否需要全局信息)
- 如果节点仅关注自己的特征更新以及任务,仅需维护:
从事件流中取出所有在 $t$ 之前与 $v_0$ 有过交互的节点集合以及交互的时间 $t_i$。
$$
\mathcal{N}(v_0; t) = \{ v_i \mid \exists (v_0, v_i, t_i) \in \mathcal{E}, t_i < t \}
$$
- 如果节点还关注其他节点的特征更新及任务,需要知道**全局事件流** (若干时刻邻接矩阵转化而来)

View File

@ -1,35 +1,32 @@
以下是修改后的 Markdown 格式内容,公式用 `$``$$` 包裹:
以下是修改后的内容,所有公式部分都已用 `$`(行内公式)或 `$$`(行间公式)包裹:
---
2. **截断的谱分解(取前 $r$ 个特征值和特征向量)**
如果我们只保留前 $r$ 个最大的(或最重要的)特征值和对应的特征向量,那么:
- **特征向量矩阵 $U_r$**:取 $U$ 的前 $r$ 列,维度为 $n \times r$。
- **特征值矩阵 $\Lambda_r$**:取 $\Lambda$ 的前 $r \times r$ 子矩阵(即前 $r$ 个对角线元素),维度为 $r \times r$。
因此,截断后的近似分解为:
$$A \approx U_r \Lambda_r U_r^T$$
### **1. 核心区别**
| **性质** | **特征分解/谱分解** | **奇异值分解SVD** |
| ------------ | -------------------------------- | ------------------------------------------ |
| **适用矩阵** | 仅限**方阵**$n \times n$ | **任意矩阵**$m \times n$,包括矩形矩阵) |
| **分解形式** | $A = P \Lambda P^{-1}$ | $A = U \Sigma V^*$ |
| **矩阵类型** | 可对角化矩阵(如对称、正规矩阵) | 所有矩阵(包括不可对角化的方阵和非方阵) |
| **输出性质** | 特征值($\lambda_i$)可能是复数 | 奇异值($\sigma_i$)始终为非负实数 |
| **正交性** | 仅当 $A$ 正规时 $P$ 是酉矩阵 | $U$ 和 $V$ 始终是酉矩阵(正交) |
---
### 修改说明:
1. 行内公式用 `$...$` 包裹(如 `$r$`, `$n \times r$`)。
2. 独立公式用 `$$...$$` 包裹(如最后的近似分解公式)。
3. 保留原文的列表结构和强调标记(`**`)。
4. 统一了中文标点(如冒号使用全角 ``)。
1. **行内公式**(用 `$...$` 包裹):
- 矩阵维度:$n \times n$、$m \times n$
- 分解形式:$A = P \Lambda P^{-1}$、$A = U \Sigma V^*$
- 数学符号:$\lambda_i$、$\sigma_i$、$A$、$P$、$U$、$V$ 等
以下是修改后的 Markdown 格式内容,公式用 `$$` 包裹:
2. **特殊符号**
- 使用 `^*` 表示共轭转置 $V^*$
- 使用 `\times` 表示乘号 $\times$
- 使用 `\Lambda``\Sigma` 表示对角矩阵
---
3. **表格结构**
- 保持原有 Markdown 表格格式
- 对齐表头和内容
- 保留中文说明部分
$$A(G) = \sum_{i=1}^n \lambda_i x_i x_i^T$$
---
### 说明:
1. 这是一个独立的数学公式,因此使用 `$$...$$` 包裹实现居中显示
2. 保持了原公式的所有数学符号和结构
3. 如果需要在行内使用,可以改为 `$...$` 包裹
这样修改后,公式可以兼容 LaTeX 渲染,同时保持内容的清晰性和可读性。

View File

@ -263,7 +263,7 @@ FCM的目标是将矩阵中的元素聚类到$K$个簇中,每个元素通过**
**2. 更新隶属度**
对于每个元素 $a_{ij} $,计算其属于簇 $k$ 的隶属度 $ \mu_k(a_{ij})$
对于每个元素 $a_{ij}$,计算其属于簇 $k$ 的隶属度 $ \mu_k(a_{ij})$
$$
\mu_k(a_{ij}) = \frac{1}{\sum_{l=1}^{K} \left( \frac{\|a_{ij} - c_k\|}{\|a_{ij} - c_l\|} \right)^{2/(m-1)}}
$$
@ -288,7 +288,7 @@ $$
**4. 判断收敛**
- 计算簇中心的变化量 \( $\Delta C$ = \|$C_{\text{new}} - C_{\text{old}}$\| \)。
- 计算簇中心的变化量 \( $\Delta C = |C_{\text{new}} - C_{\text{old}}|$ \)。
- 如果 \( $\Delta C < \epsilon $\)则停止迭代否则返回步骤2
**5. 量化处理**
@ -562,6 +562,8 @@ $$
这里得到的 $U \in \mathbb{R}^{n \times r}$
**例:假设我们有一个 $2 \times 2$ 对称非负矩阵**
@ -681,6 +683,11 @@ $$
为什么采用SNMF? 5.13日我的思考:卡尔曼滤波得到的特征值和特征向量存在噪声 直接进行谱分解重构会导致重构出来的矩阵不满足对称性。但是SNMF在迭代的过程中增加了非负且对称的约束并且得到的U是个低维的你可以仅在需要的时候进行重构其他时候就保留U就行
#### **时间复杂度分析**
(1) 初始构造阶段(假设特征值 特征向量已提前获取,不做分析)

View File

@ -388,3 +388,11 @@ target/
**编译** 这些测试类到 `target/test-classes`
**逐个执行**(默认是串行)所有这些编译后的测试类。
#### package
打包失败的把test步骤去掉
![image-20250514125309214](https://pic.bitday.top/i/2025/05/14/kq2fpz-0.png)

View File

@ -253,7 +253,7 @@ public static List<String> splitBySpace(String s) {
向字符串末尾追加内容。
2.**`insert(int offset, String str)`**
在指定位置插入字符串。
在指定位置插入字符串。(有妙用,头插法可以实现倒序)`insert(0,str)`
3.**`delete(int start, int end)`**
删除从 `start``end` 索引之间的字符。
@ -1448,6 +1448,33 @@ public class ComparatorSortExample {
### 列表
“头插法”本质上就是把新节点“插”到已构建链表的头部
1.反转链表
2.从头开始构建链表(逆序插入)
```java
ListNode buildList(int[] arr) {
ListNode head = null;
for (int i = 0; i < arr.length; i++) {
ListNode node = new ListNode(arr[i]);
node.next = head; // 头插:新节点指向原 head
head = node; // head 指向新节点
}
return head;
}
// 结果链表的顺序是 arr 最后一个元素在最前面,如果你想保持原序,可以倒序遍历 arr。
```
输入arr[0] -> arr[1] -> … -> arr[n-1]
输出arr[n-1]-> arr[n-2]-> …->arr[0]
### 哈希
**问题分析**

View File

@ -463,6 +463,69 @@ public class EmployeeController {
## 后端代码部署
### 项目已经开发完毕
这种情况下JAVA代码无需改动直接本地打包maven->package成Jar包复制到服务器上部署
本项目为multi-module (聚合) Maven 工程父工程为sky-take-out子模块有common,pojo,server其中server依赖common和pojo
```xml
<dependency>
<groupId>com.sky</groupId>
<artifactId>sky-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.sky</groupId>
<artifactId>sky-pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
```
打包方式:
1.直接对父工程执行mvn clean install
2.分别对子模块common和pojo执行install再对server执行package
因为Maven 在构建 `sky-server` 时,去你本地仓库或远程仓库寻找它依赖的两个 SNAPSHOT 包。
dockerfile
```dockerfile
# 使用 JDK 17 运行时镜像
FROM openjdk:17-jdk-slim
# 设置时区为上海
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 创建工作目录
WORKDIR /app
# 复制 Fat Jar重命名为 app.jar
COPY sky-server-1.0-SNAPSHOT.jar ./app.jar
# 暴露端口(与 application.properties 中的 server.port 保持一致)
EXPOSE 8080
# 以 exec 形式启动
ENTRYPOINT ["java", "-jar", "app.jar"]
```
`ENTRYPOINT ["java", "-jar", "app.jar"]` 能够启动
### 滚动开发阶段
## 实战开发
### 分页查询