简述

对于初学 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. Python-对字典进行排序

    案例: 某班英语成绩以字典的形式存储为: {'lili':78, 'jin':50, 'liming': 30, ......} 依据成绩高低,进行学生成绩排名 如何对字典排序? 方法1: #!/us ...

  2. 正交矩阵(Orthogonal Matrix)

  3. 【Python】类

    初探类 类定义与函数定义( def语句 )一样必须被执行才会起作用 调用 x.f() 其实就相当于 MyClass.f(x) 补充说明 数据属性会覆盖掉具有相同名称的方法属性 命名方法 方法名称使用大 ...

  4. 【题解】[SDOI2015]星际战争

    \(\color{red}{Link}\) \(\text{Solution:}\) 观察到,如果一个时间\(T\)可以完成任务,则\(T+1\)这个时间也可以完成任务. 于是我们可以二分. 为了避免 ...

  5. 达梦产品技术支持培训-day7-DM8数据库备份与还原-原理

    (本文部分内容摘自DM产品技术支持培训文档,如需要更详细的文档,请查询官方操作手册,谢谢) 1.DM8备份还原简介 1.1.基本概念 (1)表空间与数据文件 ▷ DM8表空间类型: ▷ SYSTEM ...

  6. CDH5部署三部曲之一:准备工作

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  7. js 递归的理解

    友情提示:阅读本文需花 3分钟左右! 递归函数必须接受参数. (比如我要递归谁?) 在递归函数的定义初始,应该有一个判断条件,当参数满足这个条件的时候,函数停止执行,并返回值.(指定退出条件,否则就会 ...

  8. MeteoInfoLab脚本示例:线性拟合

    MeteoInfoLab提供一个线性拟合函数linregress,参数是参与拟合的两个数据序列,返回拟合的斜率.截距和相关系数.有了上述拟合参数可以用polyval函数生成拟合数据(直线).然后可以将 ...

  9. Android开发签名证书的生成

    现在都说互联网寒冬,其实只要自身技术能力够强,咱们就不怕!我这边专门针对Android开发工程师整理了一套[Android进阶学习视频].[全套Android面试秘籍].[Android知识点PDF] ...

  10. k8s-命令创建service

    查看命令帮助 [root@master kubernetes]# kubectl create service -h Create a service using specified subcomma ...