简述

对于初学 Tarjan 的你来说,肯定和我一开始学 Tarjan 一样无比迷茫。

网上大框大框的定义就足以让一个萌新从入门到入土自闭。

所以本文决定不在这里对于 Tarjan 的定义和原理做过多介绍。(当然如果还是无法理解可以尝试直接理解代码)

注意&特别鸣谢:这篇文章,本文也有多处讲解与图片转自此文

作用

其实这都是后话了...。(毕竟你不会这个东西知道它的作用也没什么用)

下面是一段令人窒息的专业定义,萌新慎入:

  1. 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。
  2. 如果有向图G的每两个顶点都强连通,称G是一个强连通图
  3. 非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。

所以 Tarjan 的基础用处就是:求一个图的强连通分量

举个栗子:

下图中,子图 \(\{1,2,3,4\}\) 为一个强连通分量,因为顶点 \(1,2,3,4\) 两两可达。\(\{5\},\{6\}\)也分别是两个强连通分量。

让我们带着疑惑和不解去探索 Tarjan 的奥秘吧。

Tarjan 算法

原理

Tarjan 算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。

搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

当然,主要还是靠下面两个标记。

出场人物

其实就是用到了哪几个变量。

  1. \(Dfn(u)\):为节点u搜索的次序编号(时间戳)。
  2. \(Low(u)\):为 \(u\) 或 \(u\) 的子树能够追溯到的最早的栈中节点的次序号(人话:\(u\) 的子树以及其连向的节点中 \(dfn[]\) 的最小值)。

由定义可以得出:

Low(u)=Min
{
DFN(u),
Low(v),(u,v)为树枝边,u为v的父节点
DFN(v),(u,v)为指向栈中节点的后向边(非横叉边)
}

当 \(Dfn(u)=Low(u)\) 时,以 \(u\) 为根的搜索子树上所有节点是一个强连通分量。

感性理解一下,当一个集合 \(S\) 构成强连通分量时,它们的祖先节点肯定是 \(dfn[]\) 最小的。(遍历时间最早)

那么除了这个祖先节点之外,其他所有节点的 \(low[]\) 肯定都会被刷新。

所以唯一不变的就是祖先节点,找到祖先节点也就找到了整个强连通分量,而栈中的残留数字都属于 \(S\) 集合。

图示

从节点 \(1\) 开始 DFS,把遍历到的节点加入栈中。

搜索到节点 \(u=6\) 时,\(Dfn[6]=Low[6]\),找到了一个强连通分量。

退栈到 \(u=v\) 为止,\(\{6\}\)为一个强连通分量。

返回节点 \(5\),发现 \(Dfn[5]=Low[5]\),退栈后 \(\{5\}\) 为一个强连通分量。

返回节点 \(3\),继续搜索到节点 \(4\),把 \(4\) 加入堆栈。

发现节点 \(4\) 向节点 \(1\) 有后向边,节点 \(1\) 还在栈中,所以 \(Low[4]=1\)。

节点 \(6\) 已经出栈,\((4,6)\) 是横叉边,返回 \(3\),\((3,4)\) 为树枝边,所以 \(Low[3]=Low[4]=1\)。

继续回到节点 \(1\),最后访问节点 \(2\)。访问边 \((2,4)\),\(4\) 还在栈中,所以 \(Low[2]=Dfn[4]=5\)。

返回 \(1\) 后,发现 \(Dfn[1]=Low[1]\),把栈中节点全部取出,组成一个连通分量 \(\{1,3,4,2\}\)。

至此,算法结束。经过该算法,求出了图中全部的三个强连通分量 \(\{1,3,4,2\},\{5\},\{6\}\)。

可以发现,运行 Tarjan 算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为 \(O(N+M)\)。

代码实现

代码实现很简单(前提是你看懂了上面的讲解,也不排除很多同学可以直接通过代码加深理解)。

//co[] 表示这个强联通分量的“颜色”,sum[] 表示这个“颜色”的强连通分量的节点个数。
//num 是时间戳,tot 是栈顶,col 是“颜色”总数。
void tarjan(int u){
dfn[u]=low[u]=++num;//打上时间戳,并同时给 low[u] 赋初值。
s[++tot]=u;//进栈。
for(int i=head[u];i;i=ed[i].nxt){
int v=ed[i].to;
if(!dfn[v])//如果没有遍历过(即这是一条树枝边)。
tarjan(v),low[u]=min(low[u],low[v]);
else if(!co[v])//遍历过(即这是一条返祖变或横向边)。
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){//找到啦。
co[u]=++col,sum[col]=a[u];
while(u!=s[tot])
sum[col]+=a[s[tot]],co[s[tot]]=col,--tot;
--tot;
}
return;
} for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);

例题

多做例题通常是了解新算法的重要过程,建议读者完全消化上面的模板后在食用。

题目都不难,而且十分适合初学者。

例题一

受欢迎的牛

简化题意:

有一个包括 \(N\) 个点,\(M\) 条边的有向图,求所有节点都能到达的节点的个数。如果没有,则输出 0。

分析题目,这样的节点只可能出现在唯一一个出度为 0 的强联通分量内。

若出现两个以上出度为 0 的强连通分量则不存在这样的节点,因为那几个出度为零的分量无法传递出去。

而分量内的节点一定是可以相互到达的。

那么题目就变为求:图中出度为 0 的强联通分量的节点总数,如果有多个这样的强连通分量则输出 0

标准的 Tarjan 模板题。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#define N 10010
#define M 50010
using namespace std; int n,m,head[N],cnt=0;
int col=0,num=0;
int co[N],dfn[N],low[N],s[N],de[N],sum[N];
struct Edge{
int nxt,to;
}ed[M]; int read(){
int x=0,f=1;char c=getchar();
while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
return x*f;
} void add(int u,int v){
cnt++;
ed[cnt].nxt=head[u];
ed[cnt].to=v;
head[u]=cnt;
return;
} int tot=0; void tarjan(int u){
dfn[u]=low[u]=++num;
tot++;
s[tot]=u;
for(int i=head[u];i;i=ed[i].nxt){
int v=ed[i].to;
if(!dfn[v])
tarjan(v),low[u]=min(low[u],low[v]);
else if(!co[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
co[u]=++col;
++sum[col];
while(s[tot]!=u)
++sum[col],co[s[tot]]=col,--tot;
--tot;
}
return;
} int main(){
n=read(),m=read();
int u,v;
for(int i=1;i<=m;i++)
u=read(),v=read(),add(u,v);
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
int ans,k=0;
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=ed[j].nxt)
if(co[i]!=co[ed[j].to])
de[co[i]]++;
for(int i=1;i<=col;i++)
if(!de[i])
ans=sum[i],k++;
if(k==1) printf("%d\n",ans);
else puts("0");
return 0;
}

例题二

最大半连通子图

对于初学者来说可能有一定的难度,码量也是 100+ 行。

简单来说,这道题是有向图中常用的套路:Tarjan + 拓扑排序 + DP

定义挺长的,理解起来比较困难,但是读懂后就发现是水题了:缩点后求有向图中的最长链

我们先用模板式的 Tarjan 把图进行缩点。

然后我们对这个缩过点的图重新进行建边(注意:重新建图需要判重边,否则最后最长链的数量会受到影响)。

最后我们拓扑加 DP 把最长链的节点的数量和最长链的数量求出。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#define N 100010
#define M 1000010
using namespace std; int n, m, MOD, head[N], cnt = 0;
int low[N], dfn[N], s[N], co[N], sum[N];
int tot = 0, numm = 0, col = 0;
struct Edge {
int nxt, to;
} ed[M]; int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
} void add(int u, int v) {
++cnt;
ed[cnt].nxt = head[u];
ed[cnt].to = v;
head[u] = cnt;
return;
} void tarjan(int u) {
dfn[u] = low[u] = ++numm;
s[++tot] = u;
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if (!co[v])
low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
co[u] = ++col;
++sum[col];
while (s[tot] != u) ++sum[col], co[s[tot]] = col, --tot;
--tot;
}
return;
} int nu[M], x[M], y[M], ru[N]; bool cmp(int a, int b) {
if (x[a] != x[b])
return x[a] < x[b];
return y[a] < y[b];
} void remove() {
for (int i = 1; i <= m; i++) {
nu[i] = i;
x[i] = co[x[i]];
y[i] = co[y[i]];
}
sort(nu + 1, nu + m + 1, cmp);
cnt = 0;
memset(head, 0, sizeof(head));
memset(ed, 0, sizeof(ed));
for (int i = 1; i <= m; i++) {
int z = nu[i];
if (x[z] != y[z] && (x[z] != x[nu[i - 1]] || y[z] != y[nu[i - 1]])) {
add(x[z], y[z]);
++ru[y[z]];
}
}
return;
} int dis[N], num[N], ans; void topo() {
queue<int> q;
for (int i = 1; i <= col; i++) {
if (!ru[i]) {
q.push(i), dis[i] = sum[i], num[i] = 1;
if (dis[ans] < dis[i])
ans = i;
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (dis[v] < dis[u] + sum[v]) {
dis[v] = dis[u] + sum[v];
num[v] = 0;
if (dis[ans] < dis[v])
ans = v;
}
if (dis[v] == dis[u] + sum[v])
num[v] = (num[v] + num[u]) % MOD;
if (!--ru[v])
q.push(v);
}
}
return;
} int anss; void ask() {
for (int i = 1; i <= n; i++)
if (dis[i] == dis[ans])
anss = (anss + num[i]) % MOD;
} int main() {
n = read(), m = read(), MOD = read();
int u, v;
for (int i = 1; i <= m; i++) {
x[i] = read(), y[i] = read();
add(x[i], y[i]);
}
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
remove();
topo();
ask();
printf("%d\n%d\n", dis[ans], anss);
return 0;
}

例题三

网络协议

Tarjan 和 DAG 是有不解之缘的,所以经常通过 DAG 的某些性质来做题。

其实如果没有第二问的话,这道题就十分的简单了。

  1. 第一问:Tarjan 缩点后寻找入度为 0 的强连通分量的个数。
  2. 第二问:\(max(sum_{入度为 0 的强连通分量},sum_{出度为 0 的强连通分量})\)。

那么第二问的为什么可以这么求呢?

定理:将 DAG 变为有向连通图的最小方案数为 \(max(sum_{入度为 0 的强连通分量},sum_{出度为 0 的强连通分量})\)。

构造:连接 \(min(sum_{入度为 0 的强连通分量},sum_{出度为 0 的强连通分量})\) 个入度为 0 与出度为 0 的强连通分量构成环,

然后再随意连接剩余的入度为 0 或出度为 0 的强连通分量到环上任意节点。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <queue>
#define N 110
using namespace std; int n, head[N], cnt = 0;
int low[N], dfn[N], s[N], co[N], sum[N], col = 0, tot = 0, num = 0;
struct Edge {
int nxt, to;
} ed[N * N]; int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
} void add(int u, int v) {
ed[++cnt].nxt = head[u];
ed[cnt].to = v;
head[u] = cnt;
return;
} void tarjan(int u) {
dfn[u] = low[u] = ++num;
s[++tot] = u;
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if (!co[v])
low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
co[u] = ++col;
++sum[col];
while (s[tot] != u) ++sum[col], co[s[tot]] = col, --tot;
--tot;
}
return;
} int ru[N], cu[N]; int main() {
n = read();
int v;
for (int u = 1; u <= n; u++) {
v = read();
while (v) add(u, v), v = read();
}
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
if (col == 1) {
printf("1\n0\n");
return 0;
}
for (int u = 1; u <= n; u++)
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (co[u] != co[v])
++cu[co[u]], ++ru[co[v]];
}
int a = 0, b = 0;
for (int i = 1; i <= col; i++) {
if (!ru[i])
++a;
if (!cu[i])
++b;
}
printf("%d\n%d\n", a, max(a, b));
return 0;
}

例题四

间谍网络

这道题没有看上去那么简单,与普通缩点题唯一的不同是它加上了限制条件。

那怎么办?其实我们在循环是也可以加上限制条件,同时通过 \(dfn[]\) 判重(具体见代码)。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#define N 3010
#define M 8010
using namespace std; int n, m1, m2, head[N], cnt = 0, cost[N];
int low[N], dfn[N], co[N], s[N], ru[N], sum[N];
int col = 0, tot = 0, num = 0;
struct Edge {
int nxt, to;
} ed[M]; int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
} void add(int u, int v) {
ed[++cnt].nxt = head[u];
ed[cnt].to = v;
head[u] = cnt;
return;
} void init() {
n = read(), m1 = read();
int u, v;
memset(cost, 0x3f, sizeof(cost));
memset(sum, 0x3f, sizeof(sum));
for (int i = 1; i <= m1; i++) u = read(), v = read(), cost[u] = v;
m2 = read();
for (int i = 1; i <= m2; i++) u = read(), v = read(), add(u, v);
return;
} void tarjan(int u) {
dfn[u] = low[u] = ++num;
s[++tot] = u;
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if (!co[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
co[u] = ++col, sum[col] = cost[u];
while (s[tot] != u) sum[col] = min(sum[col], cost[s[tot]]), co[s[tot]] = col, --tot;
--tot;
}
return;
} int main() {
init();
for (int i = 1; i <= n; i++)
if (!dfn[i] && cost[i] != 0x3f3f3f3f)//限制条件。
tarjan(i);
for (int i = 1; i <= n; i++)
if (!dfn[i]) {//判断无解。
printf("NO\n%d\n", i);
return 0;
}
for (int u = 1; u <= n; u++)
for (int i = head[u]; i; i = ed[i].nxt)
if (co[ed[i].to] != co[u])
++ru[co[ed[i].to]];
int ans = 0;
for (int i = 1; i <= col; i++)
if (!ru[i])
ans += sum[i];
printf("YES\n%d\n", ans);
return 0;
}

例题五

抢掠计划

首先日常缩点,重新建立有向图,然后...,有一个神仙做法:

  1. 化点权为边权:举个栗子,假设有边 \(u\to v\),\(v\) 的点权为 \(w\),那么令 \(edge(u,v)=-w\)。
  2. 因为置了负边权,所以将最长路变为熟悉的最短路,跑 SPFA 即可。
  3. 找到有酒吧的点中 \(-dis[]\) 最大的即可。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <queue>
#define N 500010
using namespace std; int n, m, S, P, x[N], y[N], a[N], head[N], cnt = 0;
int low[N], dfn[N], co[N], s[N], sum[N];
int col = 0, tot = 0, num = 0;
bool bar[N];
struct Edge {
int nxt, to, val;
} ed[N]; int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
} void add(int u, int v) {
ed[++cnt].nxt = head[u];
ed[cnt].to = v;
head[u] = cnt;
return;
} void tarjan(int u) {
dfn[u] = low[u] = ++num;
s[++tot] = u;
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if (!co[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
co[u] = ++col, sum[col] = a[u];
while (u != s[tot]) sum[col] += a[s[tot]], co[s[tot]] = col, --tot;
--tot;
}
return;
} void addedge(int u, int v, int w) {
ed[++cnt].nxt = head[u];
ed[cnt].to = v;
ed[cnt].val = w;
head[u] = cnt;
return;
} void Remove() {
cnt = 0;
memset(head, 0, sizeof(head));
memset(ed, 0, sizeof(ed));
for (int i = 1; i <= m; i++) {
if (co[x[i]] != co[y[i]])
addedge(co[x[i]], co[y[i]], -sum[co[y[i]]]);
}
return;
} int dis[N];
bool vis[N];
void SPFA(int s) {
queue<int> q;
memset(dis, 0x3f, sizeof(dis));
memset(vis, false, sizeof(vis));
s = co[s];
dis[s] = -sum[s];
vis[s] = true;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to, w = ed[i].val;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!vis[v])
q.push(v), vis[v] = true;
}
}
}
return;
} int main() {
n = read(), m = read();
int u, v;
for (int i = 1; i <= m; i++) x[i] = read(), y[i] = read(), add(x[i], y[i]);
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
Remove();
S = read();
SPFA(S);
P = read();
int ans = 0;
for (int i = 1; i <= P; i++) u = read(), ans = max(ans, -dis[co[u]]);
printf("%d\n", ans);
return 0;
}

总结

图论中常用的算法之一就是 Tarjan 了,所以也是很重要的啦。

希望这篇文章对每一位读者都有帮助。

再次注意&特别鸣谢:这篇文章,本文也有多处讲解与图片转自此文

完结撒花。

浅谈 Tarjan 算法的更多相关文章

  1. 浅谈Tarjan算法

    从这里开始 预备知识 两个数组 Tarjan 算法的应用 求割点和割边 求点-双连通分量 求边-双连通分量 求强连通分量 预备知识 设无向图$G_{0} = (V_{0}, E_{0})$,其中$V_ ...

  2. 浅谈Tarjan算法及思想

    在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连 ...

  3. 浅谈 Tarjan 算法之强连通分量(危

    引子 果然老师们都只看标签拉题... 2020.8.19新初二的题集中出现了一道题目(现已除名),叫做Running In The Sky. OJ上叫绮丽的天空 发现需要处理环,然后通过一些神奇的渠道 ...

  4. 浅谈分词算法(5)基于字的分词方法(bi-LSTM)

    目录 前言 目录 循环神经网络 基于LSTM的分词 Embedding 数据预处理 模型 如何添加用户词典 前言 很早便规划的浅谈分词算法,总共分为了五个部分,想聊聊自己在各种场景中使用到的分词方法做 ...

  5. 浅谈分词算法(4)基于字的分词方法(CRF)

    目录 前言 目录 条件随机场(conditional random field CRF) 核心点 线性链条件随机场 简化形式 CRF分词 CRF VS HMM 代码实现 训练代码 实验结果 参考文献 ...

  6. 浅谈分词算法(3)基于字的分词方法(HMM)

    目录 前言 目录 隐马尔可夫模型(Hidden Markov Model,HMM) HMM分词 两个假设 Viterbi算法 代码实现 实现效果 完整代码 参考文献 前言 在浅谈分词算法(1)分词中的 ...

  7. 浅谈分词算法基于字的分词方法(HMM)

    前言 在浅谈分词算法(1)分词中的基本问题我们讨论过基于词典的分词和基于字的分词两大类,在浅谈分词算法(2)基于词典的分词方法文中我们利用n-gram实现了基于词典的分词方法.在(1)中,我们也讨论了 ...

  8. 浅谈Manacher算法与扩展KMP之间的联系

    首先,在谈到Manacher算法之前,我们先来看一个小问题:给定一个字符串S,求该字符串的最长回文子串的长度.对于该问题的求解.网上解法颇多.时间复杂度也不尽同样,这里列述几种常见的解法. 解法一   ...

  9. 浅谈KMP算法及其next[]数组

    KMP算法是众多优秀的模式串匹配算法中较早诞生的一个,也是相对最为人所知的一个. 算法实现简单,运行效率高,时间复杂度为O(n+m)(n和m分别为目标串和模式串的长度) 当字符串长度和字符集大小的比值 ...

随机推荐

  1. ECharts系列:玩转ECharts之常用图(折线、柱状、饼状、散点、关系、树)

    一.背景 最近产品叫我做一些集团系列的统计图,包括集团组织.协作.销售.采购等方面的.作为一名后端程序员,于是趁此机会来研究研究这个库. 如果你仅仅停留在用的层面,那还是蛮简单的. 二.介绍 ECha ...

  2. python排序算法总结和实现

    ------------------希尔排序------------- 一直没搞懂希尔排序怎么搞得 def Shell_sort(L): step = len(L)/2 while step > ...

  3. 【题解】Product

    \(\color{brown}{Link}\) \(\text{Solution:}\) \(Question:\) \(\prod_{i=1}^n \prod_{j=1}^n \frac{lcm(i ...

  4. css引入本地字体

    1.首先创建一个字体 @font-face { font-family: 'number_font'; //创建一个number_font字体名称 src: url('../../../style/F ...

  5. helm包管理工具

    K8S正常部署应用是如下方式 kubectl create deployment web --image=nginx --dru-run=client -o yaml > web.yaml ku ...

  6. YCM 安装小记

    layout: post title: YCM 安装小记 半夜,女朋友在那边抱怨购物车的物品秒无货,我这边刚好成功安装了vim上最难装的插件--YouCompleteMe,内心非常激动,于是本着取之于 ...

  7. Sqlite嵌入式数据库讲解

    在计算机系统中,保存数据的方式一般有两种:1. 普通文件方式2. 数据库方式 相比于普通文件方式,使用数据库来管理大批量数据具有更高的效率与安全性. 数据库系统一般由3个部分构成1. 数据库2. 数据 ...

  8. 多测师讲解接口测试__mock___高级讲师肖sir

    一.关于Mock测试 1.什么是Mock测试?mock测试,源自于英文单词fake,意为假的测试实际工作中用于模拟那些无法实时连接的后端,或是没有开发出来的后端,用于获得结果反馈的一种测试方式.通过发 ...

  9. ip地址和网络端口总结

    ip地址 ip地址默认指ipv4地址,用4个字节表示,转换为点分10进制,可以表达范围0.0.0.0到255.255.255.255的地址,大约为42.95亿个地址.互联网编号分配机构(IANA,In ...

  10. 调试与优化:一次数据中心看板 T+1 改 T+0 优化过程

    背景 团队目前在做一个用户数据看板(下面简称看板),基本覆盖用户的所有行为数据,并生成分析数据,用户行为数据来源于多个数据源(餐饮.生活日用.充值消费.交通出行.通讯物流.交通出行.医疗保健.住房物业 ...