AC自动机详解,原理、优化分析,代码实现

零、前言

对于模式串匹配问题,在很多基础的数据结构课程中都有涉及到,如 KMP 算法,BM算法,Trie。

但是给定文本串,我们有多个模式串要去查询。难道要多次调用KMP / BM,或者在Trie上多次查询吗?

Aho 和 Corasick 基于 Trie,对 KMP 进行了推广,使得 Trie 可以在一个文本串中识别一个关键字集合中的任何关键字。

这就是本博客的主题——AC自动机。


一、AC 自动机

1.1 概述

AC 自动机是 以 Trie 的结构为基础,结合 KMP 的思想 建立的自动机,用于解决多模式匹配等任务。

AC 自动机本质上是 Trie 上的自动机。

关于KMP:[KMP算法详解 c++]_kmpc+±CSDN博客

关于Trie:[Trie树/字典树的原理及实现C/C++]_trie字典树原理-CSDN博客

1.2 基本原理

建立 AC 自动机 一般分为两步:

  1. 用 所有的模式串 构建一个 Trie
  2. 为 Trie 上的所有结点构建 失配指针(fail)

1.3 Trie 构建

按照 普通 Trie 插入模式串的方式将 模式串集合中的串依次插入到Trie中

我们在Trie 上匹配模式串的过程,实质上就是在自动机上进行状态转换的过程。

因此,我们可以把Trie 的每个节点看作一个状态,每个状态事实上都对应了某个模式串的前缀

所以下文有时会称Trie 上的节点为 前缀、状态,这里约定下。

1.4 失配指针

AC 自动机利用一个 fail 指针来辅助多模式串的匹配。

fail 指针节点(状态) u 的 fail 指针 指向另一个 节点 v,v 是 u 的最长后缀

换句话说,v 所代表的前缀 是 u所代表的前缀的最长后缀

fail vs KMP的next

  1. 共同点:两者都是在失配时用于跳转的指针

  2. 不同点:next 指针 求的是最长公共前后缀,fail 指针则指向的是 所有模式串前缀中 和当前前缀 公共后缀最长的那个

    KMP 只对一个模式串做匹配,而AC自动机要对多个模式串做匹配。因而fail 指针可能指向的是另一个模式串,二者前缀不同。

简而言之:AC 自动机的失配指针指向当前状态的最长后缀状态。

下面先解释如何构建fail指针,再解释为何能够通过fail指针来实现多模式串匹配。

1.5 fail 指针的构建

fail 指针的构建其实就是利用 bfs 自顶向下递推的过程,从而避免了后效性。

为了方便描述,先给出节点定义:

struct AhoCorasick {static constexpr int ALPHABET = 26;static constexpr char base = 'a';struct Node {int len;    // 当前前缀长度int fail;   // 失配指针int cnt;    // 此状态结尾单词数目std::array<int, ALPHABET> son;  // 儿子节点Node(): len(0), fail(0), cnt(0), son{} {}};std::vector<Node> tr;	// 节点池
};

构建流程:

  • 队列 q 保存 bfs 过程中的节点
  • 节点 u 从队头出队,此时 u 以及 u 以上的节点的fail 都已经构建完毕
  • 遍历 u 的 儿子节点 son[i]
  • 如果 son[i] 为空,那么 son[i] 指向 fail 的 son[i]
  • 如果 son[i] 存在,那么 son[i] 的 fail 指向 u 的 fail 的 son[i],并将 son[i] 入队
  • 队空,算法结束

由于自顶向下构建,保证了每一个节点出队时,它前面的节点都已构建完毕,保证了对子节点 fail指针构建的正确性。

事实上这是对于暴力构建fail指针的递推优化。

代码如下:

void build_fail() {std::queue<int> q;q.push(1);while (q.size()) {int u = q.front();q.pop();for (int i = 0; i < ALPHABET; ++ i) {if (!tr[u].son[i])tr[u].son[i] = tr[tr[u].fail].son[i];else {tr[tr[u].son[i]].fail = tr[tr[u].fail].son[i];q.push(tr[u].son[i]);}}}
}

放个图可以手玩一下。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.6 多模式串匹配

先给出多模式串匹配的代码:

/*文本串: s模式串构建的 AC 自动机: ac
*/
int cur = 1; // 根结点编号为1
int res = 0;
for (char ch : s) {cur = ac.son(cur, ch - 'a');	// 往下跳一步// 开始跳fail,遍历所有后缀for (int newcur = cur; newcur && ac.cnt(newcur) >= 0; newcur = ac.fail(newcur)) {res += ac.cnt(newcur), ac.cnt(newcur) = -1;}
}
  • 默认从根节点开始匹配s
  • 当前节点号为cur
  • 遍历到字符ch
  • cur = son[ch]
    • son[ch] 要么是成功匹配节点,要么是 构建fail 时 指向的最长后缀的 son[ch]
  • 然后从 cur 开始跳fail,将所有后缀节点都跑一遍,记录匹配

事实上,大部分 编译原理 课程中都会介绍最长匹配原则,保证了我们的匹配不漏,这点可以自行了解。

1.7 后缀链接优化

尝试自己模拟下这组样例:

target 为文本串,words 为 模式串集合

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

发现复杂度会爆炸的

巧妙的是,KMP算法中也对nxt 数组进行了优化,我们这里也是利用类似思想来进行优化。

这里引入后缀链接(link)

后缀链接(suffix link),用来快速跳到一定是某个 模式串 的最后一个字母的节点(等于 root 则表示没有)

这样一来节点定义如下:

struct Node {std::vector<int> id;int len;int fail;int last;std::array<int, ALPHABET> son;Node() : last(0), id{}, len(0), fail(0), son{} {}
};

构建过程:

last 是和 fail 在一起构建的:

根节点的 last 默认指向 根节点

  • 继承前面bfs 构建fail 的逻辑
  • u出队,此时u 以及前面的 fail 和 last 都已经构建完毕
  • 遍历 u 的 儿子节点 son[i]
  • 如果 son[i] 为空,那么 son[i] 指向 fail 的 son[i]
  • 如果 son[i] 存在,那么 son[i] 的 fail 指向 u 的 fail 的 son[i]
    • 如果 son[i].fail 是单词结尾,那么 son[i].last = son[i].fail,否则指向 son[i].fail.last
    • 将 son[i] 入队
  • 队空,算法结束

代码如下:

void build() {std::queue<int> q;q.push(1);while (q.size()) {int u = q.front();q.pop();for (int i = 0; i < ALPHABET; ++ i) {if (!tr[u].son[i]) {tr[u].son[i] = tr[tr[u].fail].son[i];continue;}tr[tr[u].son[i]].fail = tr[tr[u].fail].son[i];tr[tr[u].son[i]].last = tr[tr[tr[u].son[i]].fail].cnt ? tr[tr[u].son[i]].fail : tr[tr[tr[u].son[i]].fail].last;q.push(tr[u].son[i]);}}
}

1.8 拓扑优化

见OJ练习2.3

1.9 dfs 优化

见OJ练习2.3

二、OJ 练习

2.1 P3808 AC 自动机(简单版I)

原题链接

P3808 AC 自动机(简单版) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路分析

如果暴力跳 fail 或者 last 都会TLE,我们对于走过的点打个标记,即 cnt = -1

AC代码

#include <bits/stdc++.h>
// #include <ranges>using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;constexpr int P = 1E9 + 7;;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;struct AhoCorasick {static constexpr int ALPHABET = 26;static constexpr char base = 'a';struct Node {int len;    // 当前前缀长度int fail;   // 失配指针int cnt;    // 此状态结尾单词数目int last;   // 后缀链接std::array<int, ALPHABET> son;  // 儿子节点Node(): last{0}, len(0), fail(0), cnt(0), son{} {}};std::vector<Node> tr;AhoCorasick() {init();}void init() {tr.assign(2, Node());tr[0].son.fill(1);tr[0].len = -1;tr[0].last = 1;}int newNode() {tr.emplace_back();return (int)tr.size() - 1;}int add(const std::string &s) {int cur = 1;for (char ch : s) {int x = ch - base;if (!tr[cur].son[x]) {tr[cur].son[x] = newNode();tr[tr[cur].son[x]].len = tr[cur].len + 1;}cur = tr[cur].son[x];}++ tr[cur].cnt;return cur;}void work() {std::queue<int> q;q.push(1);while (q.size()) {int u = q.front();q.pop();for (int i = 0; i < ALPHABET; ++ i) {if (!tr[u].son[i]) {tr[u].son[i] = tr[tr[u].fail].son[i];continue;}tr[tr[u].son[i]].fail = tr[tr[u].fail].son[i];tr[tr[u].son[i]].last = tr[tr[tr[u].son[i]].fail].cnt > 0 ? tr[tr[u].son[i]].fail : tr[tr[tr[u].son[i]].fail].last;q.push(tr[u].son[i]);}}}int son(int cur, int x) const{return tr[cur].son[x];}int fail(int cur) const{return tr[cur].fail;}int len(int cur) const{return tr[cur].len;}int size() const{return tr.size();}int last(int cur) const {return tr[cur].last;}int& cnt(int cur) {return tr[cur].cnt;}
};void solve() {int n;std::cin >> n;AhoCorasick ac;for (int i = 0; i < n; ++ i) {std::string s;std::cin >> s;ac.add(s);}ac.work();std::string t;std::cin >> t;int res = 0;for (int i = 0, cur = 1; i < t.size(); ++ i) {cur = ac.son(cur, t[i] - 'a');for (int ncur = cur; ncur && ac.cnt(ncur) >= 0; ncur = ac.last(ncur)) {res += ac.cnt(ncur);ac.cnt(ncur) = -1;}}std::cout << res << '\n';
}int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int t = 1;// std::cin >> t;while (t--) {solve();}return 0;
}

2.2 P3796 AC 自动机(简单版 II)

原题链接

P3796 AC 自动机(简单版 II) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路分析

也是板题,额外存个id就行

AC代码

#include <bits/stdc++.h>
// #include <ranges>using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;constexpr int P = 1E9 + 7;;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;struct AhoCorasick{static constexpr int ALPHABET = 26;static constexpr char base = 'a';struct Node{int len;int cnt;int fail;int last;int id;std::array<int, ALPHABET> son;Node(): len{0}, cnt{0}, fail{0}, last{0}, id{-1}, son{} {}};std::vector<Node> tr;AhoCorasick() {init();}void init() {tr.assign(2, Node());tr[0].son.fill(1);tr[0].len = -1;tr[0].last = 1;}int newNode() {tr.emplace_back();return (int)tr.size() - 1;}int add(const std::string &s, int id) {int cur = 1;for (char ch : s) {int x = ch - base;if (!tr[cur].son[x]) {tr[cur].son[x] = newNode();tr[tr[cur].son[x]].len = tr[cur].len + 1;}cur = tr[cur].son[x];}++ tr[cur].cnt;tr[cur].id = id;return cur;}void work() {std::queue<int> q;q.push(1);while (q.size()) {int u = q.front();q.pop();for (int i = 0; i < ALPHABET; ++ i) {if (!tr[u].son[i]) {tr[u].son[i] = tr[tr[u].fail].son[i];continue;}tr[tr[u].son[i]].fail = tr[tr[u].fail].son[i];tr[tr[u].son[i]].last = tr[tr[tr[u].son[i]].fail].cnt > 0 ? tr[tr[u].son[i]].fail : tr[tr[tr[u].son[i]].fail].last;q.push(tr[u].son[i]);}}}int cnt(int cur) const{return tr[cur].cnt;}int len(int cur) const{return tr[cur].len;}int fail(int cur) const{return tr[cur].fail;}int son(int cur, int x) const{return tr[cur].son[x];}int last(int cur) const{return tr[cur].last;}int id(int cur) const{return tr[cur].id;}
};void solve() {int n;while (std::cin >> n, n)  {AhoCorasick ac;std::vector<std::string> words(n);for (int i = 0; i < n; ++ i) {std::cin >> words[i];ac.add(words[i], i);}ac.work();std::string t;std::cin >> t;std::vector<int> cnt(n);int cur = 1;for (char ch : t) {int x = ch - 'a';cur = ac.son(cur, x);for (int newcur = cur; newcur > 1; newcur = ac.last(newcur))if (~ac.id(newcur))++ cnt[ac.id(newcur)];}int ma = *std::max_element(cnt.begin(), cnt.end());std::cout << ma << '\n';for (int i = 0; i < n; ++ i)if (cnt[i] == ma)std::cout << words[i] << '\n';}
}int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int t = 1;// std::cin >> t;while (t--) {solve();}return 0;
}

2.3 P5357 【模板】AC 自动机(拓扑&dfs优化)

原题链接

P5357 【模板】AC 自动机 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路分析

用后缀链接优化可以跑到84分,我们这个时候有两个选择:

  • 拓扑优化
  • dfs 优化

二者其实思想相同,只不过一个实现方式是bfs拓扑排序,一个是dfs(类似树形dp)

我们看这个样例图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们把fail 边抽离出来会得到什么?

一棵树

因为一个节点只有一条fail边

那么我们按照fail边建树,有两种方式,一个是不改变原图的fail方向,一个是翻转原图fail方向

两种方式分别对应了拓扑和dfs的做法

那么我们在文本串上匹配,每匹配一个字符,自动机都会跳转到一个状态,我们将该状态贡献+1

那么每个模式串都对应着AC自动机上的一个状态节点

每个模式串在文本串中出现次数 就是 从该状态节点,沿着fail边反方向所能到达节点的贡献和

因而我们可以:

  • 不改变fail边方向建树,进行拓扑排序,在拓扑排序的时候将贡献往后累加
  • 改变fail边方向建树,进行dfs,树形dp计数

AC代码

拓扑优化

#include <bits/stdc++.h>
// #include <ranges>using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;constexpr int P = 1E9 + 7;;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;struct AhoCorasick{static constexpr int ALPHABET = 26;static constexpr char base = 'a';struct Node{int len;int cnt;int fail;int last;int in;std::array<int, ALPHABET> son;Node(): len{0}, cnt{0}, fail{0}, last{0}, in{0}, son{} {}};std::vector<Node> tr;AhoCorasick() {init();}void init() {tr.assign(2, Node());tr[0].son.fill(1);tr[0].len = -1;tr[0].last = 1;}int newNode() {tr.emplace_back();return (int)tr.size() - 1;}int add(const std::string &s) {int cur = 1;for (char ch : s) {int x = ch - base;if (!tr[cur].son[x]) {tr[cur].son[x] = newNode();tr[tr[cur].son[x]].len = tr[cur].len + 1;}cur = tr[cur].son[x];}++ tr[cur].cnt;return cur;}void work() {std::queue<int> q;q.push(1);while (q.size()) {int u = q.front();q.pop();for (int i = 0; i < ALPHABET; ++ i) {if (!tr[u].son[i]) {tr[u].son[i] = tr[tr[u].fail].son[i];continue;}tr[tr[u].son[i]].fail = tr[tr[u].fail].son[i];++ tr[tr[tr[u].son[i]].fail].in;tr[tr[u].son[i]].last = tr[tr[tr[u].son[i]].fail].cnt > 0 ? tr[tr[u].son[i]].fail : tr[tr[tr[u].son[i]].fail].last;q.push(tr[u].son[i]);}}}int cnt(int cur) const{return tr[cur].cnt;}int len(int cur) const{return tr[cur].len;}int fail(int cur) const{return tr[cur].fail;}int son(int cur, int x) const{return tr[cur].son[x];}int last(int cur) const{return tr[cur].last;}int &in(int cur) {return tr[cur].in;}int size() const{return tr.size();}
};void solve() {int n;std::cin >> n;AhoCorasick ac;std::vector<int> end(n);for (int i = 0; i < n; ++ i) {std::string s;std::cin >> s;end[i] = ac.add(s);}ac.work();std::string t;std::cin >> t;std::vector<int> f(ac.size());int cur = 1;for (char ch : t) {cur = ac.son(cur, ch - 'a');++ f[cur];}std::queue<int> q;for (int i = 2; i < ac.size(); ++ i)if (!ac.in(i))q.push(i);while (q.size()) {int u = q.front();q.pop();f[ac.fail(u)] += f[u];if (! -- ac.in(ac.fail(u)))q.push(ac.fail(u));}for (int i = 0; i < n; ++ i)std::cout << f[end[i]] << '\n';
}int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int t = 1;// std::cin >> t;while (t--) {solve();}return 0;
}

dfs优化:

#include <bits/stdc++.h>
// #include <ranges>using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;constexpr int P = 1E9 + 7;;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;struct AhoCorasick{static constexpr int ALPHABET = 26;static constexpr char base = 'a';struct Node{int len;int cnt;int fail;int last;std::array<int, ALPHABET> son;Node(): len{0}, cnt{0}, fail{0}, last{0}, son{} {}};std::vector<Node> tr;AhoCorasick() {init();}void init() {tr.assign(2, Node());tr[0].son.fill(1);tr[0].len = -1;tr[0].last = 1;}int newNode() {tr.emplace_back();return (int)tr.size() - 1;}int add(const std::string &s) {int cur = 1;for (char ch : s) {int x = ch - base;if (!tr[cur].son[x]) {tr[cur].son[x] = newNode();tr[tr[cur].son[x]].len = tr[cur].len + 1;}cur = tr[cur].son[x];}++ tr[cur].cnt;return cur;}void work() {std::queue<int> q;q.push(1);while (q.size()) {int u = q.front();q.pop();for (int i = 0; i < ALPHABET; ++ i) {if (!tr[u].son[i]) {tr[u].son[i] = tr[tr[u].fail].son[i];continue;}tr[tr[u].son[i]].fail = tr[tr[u].fail].son[i];tr[tr[u].son[i]].last = tr[tr[tr[u].son[i]].fail].cnt > 0 ? tr[tr[u].son[i]].fail : tr[tr[tr[u].son[i]].fail].last;q.push(tr[u].son[i]);}}}int cnt(int cur) const{return tr[cur].cnt;}int len(int cur) const{return tr[cur].len;}int fail(int cur) const{return tr[cur].fail;}int son(int cur, int x) const{return tr[cur].son[x];}int last(int cur) const{return tr[cur].last;}int size() const{return tr.size();}
};void solve() {int n;std::cin >> n;AhoCorasick ac;std::vector<int> end(n);for (int i = 0; i < n; ++ i) {std::string s;std::cin >> s;end[i] = ac.add(s);}ac.work();std::string t;std::cin >> t;std::vector<int> f(ac.size());int cur = 1;for (char ch : t) {cur = ac.son(cur, ch - 'a');++ f[cur];}std::vector<std::vector<int>> adj(ac.size());for (int i = 2; i < ac.size(); ++ i)adj[ac.fail(i)].push_back(i);auto dfs = [&](auto &&self, int x) -> void {for (int y : adj[x]) {self(self, y);f[x] += f[y];}};dfs(dfs, 1);for (int i = 0; i < n; ++ i)std::cout << f[end[i]] << '\n';
}int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int t = 1;// std::cin >> t;while (t--) {solve();}return 0;
}

2.4 L语言

原题链接

https://www.luogu.com.cn/problem/P2292

思路分析

对于每个 字符串t,我们要判断其是否可以由给定的字符串集合构造出来

那么考虑dp

定义 f(i) 为 t 的前 i 个字符是否可以被构造

那么 f(i) = f(i - len(s1)) || f(i - len(s2))…

其中 si 为 t[1, i] 的 后缀,且 si 在字符串集合中

对于 si 的获取我们可以跳 last 来快速获取

这个题比较 sb 的是洛谷加数据了,题解很多用的状态压缩,实际上不用

我们加两个剪枝:

记 ma 为最长匹配前缀长度

  • 如果 i - ma > 20,我们就break(因为 s 最长也就20)
  • 如果 f[i] 已经为 true,我们就不跳last

AC代码

#include <bits/stdc++.h>
// #include <ranges>using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;constexpr int P = 1E9 + 7;;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;struct AhoCorasick{static constexpr int ALPHABET = 26;static constexpr char base = 'a';struct Node{int len;int cnt;int fail;int last;std::array<int, ALPHABET> son;Node(): len{0}, cnt{0}, fail{0}, last{0}, son{} {}};std::vector<Node> tr;AhoCorasick() {tr.reserve(10000);init();}void init() {tr.assign(2, Node());tr[0].son.fill(1);tr[0].len = -1;tr[0].last = 1;}int newNode() {tr.emplace_back();return (int)tr.size() - 1;}int add(const std::string &s) {int cur = 1;for (char ch : s) {int x = ch - base;if (!tr[cur].son[x]) {tr[cur].son[x] = newNode();tr[tr[cur].son[x]].len = tr[cur].len + 1;}cur = tr[cur].son[x];}++ tr[cur].cnt;return cur;}void work() {std::queue<int> q;q.push(1);while (q.size()) {int u = q.front();q.pop();for (int i = 0; i < ALPHABET; ++ i) {if (!tr[u].son[i]) {tr[u].son[i] = tr[tr[u].fail].son[i];continue;}tr[tr[u].son[i]].fail = tr[tr[u].fail].son[i];tr[tr[u].son[i]].last = tr[tr[tr[u].son[i]].fail].cnt > 0 ? tr[tr[u].son[i]].fail : tr[tr[tr[u].son[i]].fail].last;q.push(tr[u].son[i]);}}}int cnt(int cur) const{return tr[cur].cnt;}int len(int cur) const{return tr[cur].len;}int fail(int cur) const{return tr[cur].fail;}int son(int cur, int x) const{return tr[cur].son[x];}int last(int cur) const{return tr[cur].last;}int size() const{return tr.size();}
};void solve() {int n, m;std::cin >> n >> m;AhoCorasick ac;for (int i = 0; i < n; ++ i) {std::string s;std::cin >> s;ac.add(s);}ac.work();for (int i = 0; i < m; ++ i) {std::string t;std::cin >> t;n = t.size();std::vector<bool> f(n + 1);f[0] = true;int cur = 1;int ma = 0;for (int i = 1; i <= n; ++ i) {cur = ac.son(cur, t[i - 1] - 'a');for (int newcur = cur; newcur > 1; newcur = ac.last(newcur)) {if (f[i]) break;if (ac.cnt(newcur)) {f[i] = f[i] || f[i - ac.len(newcur)];}}if (f[i]) ma = i;if (i - ma > 20) break;}std::cout << ma << '\n';}}int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int t = 1;// std::cin >> t;while (t--) {solve();}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1541479.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

2024 研究生数学建模竞赛(F题)建模秘籍|X射线脉冲星光子到达时间建模|文章代码思路大全

铛铛&#xff01;小秘籍来咯&#xff01; 小秘籍团队独辟蹊径&#xff0c;运用轨道动力学模型&#xff0c;脉冲轮廓折叠&#xff0c;几何传播时延模型&#xff0c;相对论修正计算&#xff0c;泊松分布模拟等强大工具&#xff0c;构建了这一题的详细解答哦&#xff01; 为大家量…

数据预处理方法—数据标准化和数据归一化

1.数据标准化 1.1 概念&#xff1a; 标准化是将数据转化为均值为0&#xff0c;标准差为1的分布。通过标准化处理&#xff0c;所有特征在同一个尺度上&#xff0c;使得模型更加稳定和高效&#xff0c;尤其适用于正态&#xff08;高斯&#xff09;分布的数据。 1.2 原理 标准化…

【HTTP】构造HTTP请求和状态码

状态码 用于响应中&#xff0c;表示响应的结果如何 正确&#xff1f;错误&#xff1f;什么原因&#xff1f; HTTP 中的状态码都是标准约定好的 200 OK 成功了&#xff0c;一切顺利 在抓包到的响应中 404 Not Found 访问的资源&#xff08;URL 中的路径&#xff09;没找…

【已解决】编译报错:fatal error: Eigen/Core: 没有那个文件或目录 #include <Eigen/Core>

1、如果没有安装过Eigen&#xff0c;可以使用以下git指令进行下载&#xff0c;或者也可以通过以下网址下载 git clone https://gitlab.com/libeigen/eigen.git网址1&#xff1a;https://eigen.tuxfamily.org/index.php?titleMain_Page 网址2: https://gitlab.com/libeigen/ei…

BeautifulSoup与lxml解析网页:技术详解与实战案例

目录 一、引言 1.1 网页解析的重要性 1.2 BeautifulSoup与lxml简介 二、安装BeautifulSoup和lxml 三、BeautifulSoup基础 3.1 创建BeautifulSoup对象 3.2 基本元素 3.3 遍历和搜索文档树 3.4 CSS选择器 四、lxml基础 4.1 解析HTML 4.2 XPath选择器 4.3 CSS选择器 …

简单多状态dp第二弹 leetcode -删除并获得点数 -粉刷房子

740. 删除并获得点数 删除并获得点数 分析: 使用动态规划解决 这道题依旧是 打家劫舍I 问题的变型。 我们注意到题目描述&#xff0c;选择 x 数字的时候&#xff0c; x - 1 与 x 1 是不能被选择的。像不像 打家劫舍 问题中&#xff0c;选择 i 位置的金额之后&#xff0c;就不…

C++速通LeetCode中等第20题-随机链表的复制(三步简单图解)

方法图解&#xff1a; class Solution { public:Node* copyRandomList(Node* head) {if ( !head ) {return nullptr;}Node *cur head;// 1. 在原节点的每个节点后创建一个节点while ( cur ) {Node *newNode new Node(cur -> val);newNode -> next cur -> next;cur …

大小端字节序 和 内存高低地址顺序

目录 1. 大小端字节序 1.1 什么是大小端字节序&#xff1f; 1.2 为什么有大小端字节序? 1.3 习题&#xff1a;用程序结果判断大端小端 2. 各种易混淆的高低地址顺序 2.1 监视窗口的地址表示【计算机标准展示方式】 2.2 横向地址表示 2.3 一个字节 与 多个字节 的地址…

g1:基于 Llama,用提示工程实现类似 o1 的深度推理

开源项目 g1 利用巧妙的提示策略&#xff0c;在 Groq 硬件上使用 Llama-3.1 70b 模型实现了类似 OpenAI o1 的推理链能力。g1 将推理过程可视化&#xff0c;并结合多种技巧引导 LLM 进行深度思考&#xff0c;显著提升了其在逻辑问题上的准确率&#xff0c;为 LLM 推理能力的提升…

Win10 安装Node.js 以及 Vue项目的创建

一、Node.js和Vue介绍 1. Node.js Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。它允许你在服务器端运行 JavaScript&#xff0c;使得你能够使用 JavaScript 来编写后端代码。以下是 Node.js 的一些关键特点&#xff1a; 事件驱动和非阻塞 I/O&#xff1a;Node…

【24华为杯数模研赛赛题思路已出】国赛F题第二套思路丨附参考代码丨免费分享

2024年数模研赛E题解题思路 X 射线脉冲星光子到达时间建模思路分析 该题目是天文学背景的数学建模题目&#xff0c;其涉及到物理学中关于光线传播过程受多种因素的共同干扰的复合模型&#xff0c;以及天体和卫星的坐标变换和运动模型&#xff0c;首先我们要&#xff0c;建立卫…

JavaScript使用leaflet库显示信息窗口

前言 我们可千万不能忘记我们之前花的流程图哦&#xff0c;我们所有的计划都按照我们的流程图来去构建&#xff1b; 我们已经完成了&#xff0c;页面的加载&#xff0c;也已经完成获取用户当前的位置坐标&#xff0c;并且我们通过地图的API将当前的位置在地图中渲染出来&…

基于协同过滤推荐算法的影视推荐系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目源码、Python精…

缓存数据和数据库数据一致性问题

根据以上的流程没有问题&#xff0c;但是当数据变更的时候&#xff0c;如何把缓存变到最新&#xff0c;使我们下面要讨论的问题 1. 更新数据库再更新缓存 场景&#xff1a;数据库更新成功&#xff0c;但缓存更新失败。 问题&#xff1a; 当缓存失效或过期时&#xff0c;读取…

【C++】C++库:如何链接外部库、静态链接和动态链接,以及如何自建库并使用

十三、C库&#xff1a;如何链接外部库、静态链接和动态链接&#xff0c;以及如何自建库并使用 本篇讲C库&#xff0c;先讲如何在项目中使用外部库&#xff0c;包括静态链接和动态链接的实现&#xff1b;再讲如何在VisualStudio中自建模块或库项目&#xff0c;让所有项目都能使…

Java-数据结构-排序-(一) (。・ω・。)

文本目录&#xff1a; ❄️一、排序的概念及引用&#xff1a; ➷ 排序的概念&#xff1a; ➷ 常见的排序算法&#xff1a; ❄️二、插入排序的实现&#xff1a; ➷ 1、直接插入排序&#xff1a; ☞ 直接插入排序的基本思想&#xff1a; ☞ 直接插入排序的实现&#xff1a; ▶…

OBB-最小外接矩形包围框-原理-代码实现

前言 定义&#xff1a;OBB是相对于物体方向对齐的包围盒&#xff0c;不再局限于坐标轴对齐&#xff0c;因此包围点云时更加紧密。优点&#xff1a;能够更好地贴合物体形状&#xff0c;减少空白区域。缺点&#xff1a;计算较为复杂&#xff0c;需要计算物体的主方向&#xff0c…

linux 操作系统下dhcpd命令介绍和案例应用

linux 操作系统下dhcpd命令介绍和案例应用 DHCP&#xff08;动态主机配置协议&#xff09;在Linux操作系统中用于自动为网络中的设备分配IP地址和其他网络配置 DHCP的基本概念 DHCP协议通过UDP工作&#xff0c;主要有两个用途&#xff1a; 自动分配IP地址给网络中的设备。提…

Sn=a+aa+aaa+aaaa+aaaaa的前五项之和,其中a是一个数字

//计算求和 //Snaaaaaaaaaaaaaaa的前五项之和&#xff0c;其中a是一个数字 //如&#xff1a;222222222222222 #include<stdio.h> #include<math.h> #define A 2 //数字a #define B 5 //前几项的和 int main() {int n 0;int sum 0;int i 0;for (i 0; i <B;…

STM32F407单片机编程入门(十一) ESP8266 WIFI模块实战含源码

文章目录 一.概要二.ESP8266 WIFI模块主要性能参数三.ESP8266 WIFI模块芯片内部框图四.ESP8266 WIFI模块原理图五.ESP8266 WIFI模块与单片机通讯方法1.硬件连接2.ESP8266模块AT指令介绍 六.STM32单片机与ESP8266WIFI模块通讯实验1.硬件准备2.软件工程3.软件主要代码4.实验效果 …