2023年5月

文章图片全部来自 Oi-wiki,部分图片加以修改

前面我们在学 tarjan 算法时,提到过强连通分量,即有向图上的环,那么无向图上是否也有强连通分量呢?很遗憾,没有
但是,无向图有双连通分量!分为点双连通和边双连通(下面简称点双和边双)。

边双连通分量

概念

在一张联通的无向图中,对于两个点
\(x\)

\(y\)
,删去图上的任意一条边,两个点始终保持联通,则这两个点是边双连通。
边双连通分量,即
极大边双连通子图
,边双连通分量中的任意两点都是边双连通的,且如果加入一个不属于该子图的点,都会导致这个图不再满足两两之间边双的性质。
在无向图中。删掉一条边,导致两个图不连通了,这条边就是
割边
,也叫做

边双连通具有传递性,即如果
\(x\)

\(y\)
边双连通,
\(y\)

\(z\)
边双连通,则
\(x\)

\(z\)
也边双连通。
image
如图,在这张图中,
\(A\)

\(B\)
边双连通,
\(B\)

\(C\)
边双连通,根据传递性,
\(A\)

\(C\)
边双连通。(即使不跟据传递性,他们也的确是边双连通)

找桥的过程

image
如图,绿色的边和黑色的边都是树边,红色的边是返祖边。
我们发现,每一条返祖边都对应着一条树上的简单路径,即覆盖了树上的一些边,覆盖了的边我们用绿边表示,黑色的边没有被覆盖。我们如果删去返祖边或者任意一条绿边,都不会影响图的连通性(如果删掉返祖边就从绿边走,如果删掉绿边就从返祖边走),但是,如果我们删掉黑边,那么整个图就会被一分为二,不再保持联通,
这些黑色的边就是桥
,同时
返祖边和绿边一定不是桥

这样,我们只要找到所有的桥,就能确定边双连通分量了。
找边双连通分量,我们可以用 tarjan 算法。

void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++ tim; // 打上时间戳
	for (int i = h[u], v; i; i = e[i].nxt) {
		v = e[i].v;
		if ((i ^ 1) == fa)	continue;
		if (!dfn[v]) { // 如果这个点从未被搜过
			tarjan(v, i); // 继续往下搜
			low[u] = min(low[u], low[v]); // 正常更新 low 值
			if (low[v] > dfn[u]) { // 如果 v 点无法通过返祖边向上返回到 u 点即往上
				e[i].ok = e[i ^ 1].ok = 1; // 那么这条边就是桥
			}
			// 不理解的话可以想一想,v 点不管怎么向上都不能到达 u 点即更靠上的位置,那 u -> v 这条边就没有被返祖边覆盖,它就是桥
		}
		else { // 如果这个点已经被搜过了(说明这条边是返祖边)
			low[u] = min(low[u], dfn[v]); // 更新 low 值(比较的是 dfn[v],不是 low[v])
		}
	}
}

找边双连通分量

有两种方式,像找强连通分量那样用一个栈,或者标记桥之后再 dfs。
洛谷模板题测试,用栈比标记桥更快一些。

  • 用栈找双连通分量
void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++ tim;
	sta.push_back(u);
	for (auto [v, i] : son[u]) {
		if (i == fa)	continue;
		if (! dfn[v]) {
			tarjan(v, i);
			low[u] = min(low[u], low[v]);
		}
		else {
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u]) {
		ans[++ dcc].push_back(u);
		while (sta.back() != u) {
			ans[dcc].push_back(sta.back());
			sta.pop_back();
		}
		sta.pop_back();
	}
}
  • 标记桥,dfs。
void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++ tim;
	for (auto [v, i] : son[u]) {
		if (i == fa)	continue;
		if (! dfn[v]) {
			tarjan(v, i);
			low[u] = min(low[u], low[v]);
			if (low[v] > dfn[u]) {
				ok[i] = 1;
			}
		}
		else {
			low[u] = min(low[u], dfn[v]);
		}
	}
}

void dfs(int u) { // dfn 要清零,你也可以再开一个数组
	ans[dcc].push_back(u);
	dfn[u] = 1;
	for (auto [v, i] : son[u]) {
		if (dfn[v] || ok[i])	continue;
		dfs(v);
	}
}

点双连通分量

概念

在一张联通的无向图中,对于两个点
\(x\)

\(y\)
,删去图上的任意一个点(不能删去
\(x\)

\(y\)
),两个点始终保持联通,则这两个点是点双连通。

删去一个点,会删掉这个点以及这个点所连接的所有的边,所以桥连接的两个点都是割点。

点双连通分量,即
极大点双连通子图
,点双连通分量中的任意两点都是点双连通的,且如果加入一个不属于该子图的点,都会导致这个图不再满足两两之间点双的性质。
在无向图中。删掉一个点,导致两个图不连通了,这个点就是
割点

点双连通没有传递性,即如果
\(x\)

\(y\)
点双联通,
\(y\)

\(z\)
点双联通,
\(x\)

\(z\)
不一定点双联通。
举个例子。
image
\(A\)

\(B\)
点双连通,
\(B\)

\(C\)
点双连通,但是
\(A\)

\(C\)
并不是点双连通。(割点为
\(B\)

过程

image
如图,黑色的边是树边,红色的边是返祖边,每一条返祖边对应着一条简单路径。
现在,我们将每一条边看作是一个点,即图上蓝色的点,返祖边所覆盖的简单路径上的边都连上边,即图上的蓝边。
这样,要判断点是否为割点,只要判断这个点连出去的边是否在一个连通分量里,都在一个连通分量里,就不是割点,否则就是割点
这里还有另一种做法。
对于某个顶点
\(u\)
,如果存在至少一个顶点
\(v\)
,使得
low[v]
\(\geq\)
dfn[u]
,即不能回到祖先,那么
\(u\)
点为割点。
但这个做法唯独不适用于搜索树的起始点,即搜索树的根,如果根只有一个子树,那我们把根节点删去,对图的连通性不会有任何影响,即根节点不是割点,如果根节点有着至少两个子树,那么根节点就是割点。

void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++ cnt;
	int son = 0;
	for (int i = h[u], v; i; i = e[i].nxt) {
		v = e[i].v;
		if (v == fa)	continue;
		if (!dfn[v]) {
			++ son;
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u]) {
				ok[u] = 1;
				++ dcc;
			}
		}
		else {
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (fa == 0 && son < 2)	ok[u] = 0;
}

应用

在题目中,桥一般出现在“给定一张无向图,问是否有一种方案,能给定向,同时保证每个点都能走到”这样类似的题目上,在这道题中,有桥就没有可行方案,倘若要输出方案,我们可以利用 dfs 生成树。
由于边双比点双有着更好的性质,所以一般题目都是有关边双的。

关于用
vector
来写 tarjan

优点:动态空间,方便。
缺点:
慢!
上面的代码我们也看到了,有些题目有重边,用一般的
vector
存图方式判断是否走过重边,这里有一个方式可以实现用
vector
来找重边,那就是将
vector
的变量类型改成
pair
,第一个元素存到达的节点,第二个元素存这条边的编号,如果不保险可以再开一个
vector
、结构体或两个数组来存第
\(i\)
条边的两个端点的编号,像这样。

e.push_back({0, 0});
for (int i = 1, x, y; i <= m; ++ i) {
	scanf("%d%d", &x, &y);
	son[x].push_back({y, i});
	son[y].push_back({x, i});
	e.push_back({x, y});
}

这样,我们在 tarjan 判重边的的过程中可以直接判断编号了。

void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++ tim;
	for (auto [v, i] : son[u]) {
		if (i == fa)	continue;
		if (! dfn[v]) {
			tarjan(v, i);
			low[u] = min(low[v], low[u]);
			if (low[v] > dfn[u]) {
				ok[i] = 1;
			}
		}
		else {
			low[u] = min(low[u], dfn[v]);
		}
	}
}

对于找割点,我们直接用
vector
就行了,这里不存在任何限制,就是会慢。

void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++ cnt;
	st[++ top] = u;
	int ch = 0;
	for (int v : son[u]) {
		if (v == fa)	continue;
		if (!dfn[v]) {
			++ ch;
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u]) {
				ok[u] = 1;
				++ dcc;
				while (st[top + 1] != v) {
					ans[dcc].push_back(st[top --]);
				}
				ans[dcc].push_back(u);
			}
		}
		else {
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (fa == 0 && ch == 0)	ans[++ dcc].push_back(u);
	if (fa == 0 && ch < 2)	ok[u] = 0;
}

题目

都是来源于洛谷的模板题
P8436 【模板】边双连通分量

  • 直接用栈来找边双。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;

const int N = 5e5 + 5;
const int M = 2e6 + 5;

int n, m, cnt = 1, tim, top, dcc;
int h[N], dfn[N], low[N];
bool ok[M];
vector<pii> son[N];
vector<int> ans[N], sta;

struct edge {
	int v, nxt;
	bool ok;
} e[M << 1];

void add(int u, int v) {
	e[++ cnt].nxt = h[u];
	e[h[u] = cnt].v = v;
}

void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++ tim;
	sta.push_back(u);
	for (auto [v, i] : son[u]) {
		if (i == fa)	continue;
		if (! dfn[v]) {
			tarjan(v, i);
			low[u] = min(low[u], low[v]);
		}
		else {
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u]) {
		ans[++ dcc].push_back(u);
		while (sta.back() != u) {
			ans[dcc].push_back(sta.back());
			sta.pop_back();
		}
		sta.pop_back();
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1, x, y; i <= m; ++ i) {
		scanf("%d%d", &x, &y);
		son[x].push_back({y, i});
		son[y].push_back({x, i});
	}
	for (int i = 1; i <= n; ++ i) {
		if (!dfn[i]) {
			tarjan(i, 0);
		}
	}
	printf("%d\n", dcc);
	for (int i = 1; i <= dcc; ++ i) {
		int siz = ans[i].size();
		printf("%d ", siz);
		for (int j : ans[i]) {
			printf("%d ", j);
		}
		putchar('\n');
	}
	return 0;
}
  • 标记桥,通过 dfs 来找边双。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;

const int N = 5e5 + 5;
const int M = 2e6 + 5;

int n, m, cnt = 1, tim, top, dcc;
int h[N], dfn[N], low[N];
bool ok[M];
vector<int> ans[N];
vector<pii> son[N];

struct edge {
	int v, nxt;
	bool ok;
} e[M << 1];

void add(int u, int v) {
	e[++ cnt].nxt = h[u];
	e[h[u] = cnt].v = v;
}

void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++ tim;
	for (auto [v, i] : son[u]) {
		if (i == fa)	continue;
		if (! dfn[v]) {
			tarjan(v, i);
			low[u] = min(low[u], low[v]);
			if (low[v] > dfn[u]) {
				ok[i] = 1;
			}
		}
		else {
			low[u] = min(low[u], dfn[v]);
		}
	}
}

void dfs(int u) {
	ans[dcc].push_back(u);
	dfn[u] = 1;
	for (auto [v, i] : son[u]) {
		if (dfn[v] || ok[i])	continue;
		dfs(v);
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1, x, y; i <= m; ++ i) {
		scanf("%d%d", &x, &y);
		son[x].push_back({y, i});
		son[y].push_back({x, i});
	}
	for (int i = 1; i <= n; ++ i) {
		if (!dfn[i]) {
			tarjan(i, 0);
		}
	}
	memset(dfn, 0, sizeof dfn);
	for (int i = 1; i <= n; ++ i) {
		if (!dfn[i]) {
			++ dcc;
			dfs(i);
		}
	}
	printf("%d\n", dcc);
	for (int i = 1; i <= dcc; ++ i) {
		int siz = ans[i].size();
		printf("%d ", siz);
		for (int j : ans[i]) {
			printf("%d ", j);
		}
		putchar('\n');
	}
	return 0;
}

P8435 【模板】点双连通分量

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 5e5 + 5;
const int M = 2e6 + 5;

int n, m, cnt, top, dcc;
int h[N], dfn[N], low[N], st[N];
bool ok[N];
vector<int> son[N], ans[N];

void tarjan(int u, int fa) {
	dfn[u] = low[u] = ++ cnt;
	st[++ top] = u;
	int ch = 0;
	for (int v : son[u]) {
		if (v == fa)	continue;
		if (!dfn[v]) {
			++ ch;
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u]) {
				ok[u] = 1;
				++ dcc;
				while (st[top + 1] != v) {
					ans[dcc].push_back(st[top --]);
				}
				ans[dcc].push_back(u);
			}
		}
		else {
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (fa == 0 && ch == 0)	ans[++ dcc].push_back(u);
	if (fa == 0 && ch < 2)	ok[u] = 0;
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1, x, y; i <= m; ++ i) {
		scanf("%d%d", &x, &y);
		son[x].push_back(y);
		son[y].push_back(x);
	}
	cnt = 0;
	for (int i = 1; i <= n; ++ i) {
		if (!dfn[i]) {
			top = 0;
			tarjan(i, 0);
		}
	}
	printf("%d\n", dcc);
	for (int i = 1; i <= dcc; ++ i) {
		printf("%d ", (int)ans[i].size());
		for (int j : ans[i]) {
			printf("%d ", j);
		}
		putchar('\n');
	}
	return 0;
}

应用时间序列

时间序列分析是一种重要的数据分析方法,应用广泛。以下列举了几个时间序列分析的应用场景:

1.经济预测:时间序列分析可以用来分析经济数据,预测未来经济趋势和走向。例如,利用历史股市数据和经济指标进行时间序列分析,可以预测未来股市的走向。

2.交通拥堵预测:时间序列分析可以用来预测交通拥堵情况。例如,根据历史车流量和天气信息,可以建立一个时间序列模型来预测未来某个时间段的路况。

3.天气预测:时间序列分析可以用于天气预测,例如预测未来几天或几周的降雨量、温度等。这对于农业生产和水资源的管理非常重要。

4.财务规划:时间序列分析可以用来帮助企业进行财务规划。例如,通过分析历史销售数据,可以预测未来销售额,并制定相应的预算计划。

5.工业控制:时间序列分析可以用来优化工业生产过程。例如,根据机器运行状态和历史生产数据,可以建立一个时间序列模型来优化生产线的运行,提高生产效率。

以下是数据的具体时间序列分析步骤:

1.导入数据

a=c(12.373,12.871,11.799,8.850,8.070,7.886,6.920,7.593,7.574,8.230,10.347,9.549,7.461,8.159,9.243,9.160,10.683,10.516,9.077,8.104,7.700,8.640 ,8.736 ,9.027 ,9.380 ,9.783 ,9.648, 8.135 ,8.222, 9.155,8.941, 9.682, 10.331, 10.601, 10.693 ,8.311)

2.更改为时间序列型数据

a=ts(a)

3.绘制时序图

时序图是一种用于展示时间序列数据的图表,通常将时间作为X轴,将变量(如销售额、温度等)作为Y轴。时序图可以帮助我们观察和分析时间序列数据的趋势、季节性、周期性以及异常值等。

一个典型的时序图通常包括以下几个元素:

1-->X轴:表示时间轴,通常根据数据的时间粒度来设置刻度。例如,如果数据是按日收集的,则X轴可能显示日期;如果是按小时收集的,则X轴可能显示小时数。

2-->Y轴:表示变量的取值范围,通常根据数据的特性来设置刻度。例如,如果数据表示某个产品的销售额,则Y轴可能显示金额数值;如果数据表示温度,则Y轴可能显示摄氏度或华氏度。、

3-->数据线:表示时间序列数据的变化趋势,通常用一条连续的曲线或折线来表示。数据线的颜色和样式可以根据需要进行调整,以突出重点信息。

4-->标题和注释:用于说明时序图的含义、数据来源和相关信息。

将时间序列数据可视化成时序图,可以更加直观地观察和分析数据的变化趋势和规律,从而更好地理解数据。

首先可以绘制线图直接观察数据走势粗略判断平稳性,既无趋势也无周期

1 #时序图
2 plot(a,col='blue')

4.自相关图和偏自相关图是用于识别时间序列数据中的自相关性(autocorrelation)和偏自相关性(partial autocorrelation)的工具。

自相关性是指时间序列数据中相邻观测值之间的相关性,即一个滞后版本与原始序列之间的相关程度。偏自相关性则是在控制其他滞后项的影响下,特定滞后项与序列之间的相关性度量。

自相关图和偏自相关图可以帮助我们识别时间序列数据中的自相关性和偏自相关性,从而确定时间序列模型的阶数。这对于进行预测和分析非常重要。

自相关图通常以滞后时间(lag)为X轴,以自相关系数为Y轴绘制。自相关系数是一个介于-1和1之间的值,表示当前时点与滞后版本之间的相关程度。自相关图中的峰值通常表示时间序列数据中的周期性或季节性。

偏自相关图也以滞后时间为X轴,以偏自相关系数为Y轴绘制。偏自相关系数也是介于-1和1之间的值,但它表示特定滞后项与序列之间的相关程度,同时控制了其他滞后项的影响。偏自相关图中的峰  值通常表示时间序列数据中的趋势或周期性。

一个极大的用途是人工定阶:

自相关图                                                 偏自相关图

AR       拖尾                p阶截尾

MA      q阶截尾                 拖尾

ARMA       拖尾                 拖尾

总之,自相关图和偏自相关图是识别时间序列数据中的自相关性和偏自相关性的重要工具。通过观察这些图形的峰值和趋势,可以确定时间序列模型的阶数,并进行进一步的分析和预测。

1 #自相关图与偏自相关图
2 acf(a)3 pacf(a)


5.纯随机性检验(白噪声检验)

纯随机性检验,也称为白噪声检验(White Noise Test),是一种用于检验时间序列数据是否具有纯随机性的方法。在时间序列分析中,纯随机性通常是假设时间序列数据中不存在任何潜在的模式、趋势或周期性。

白噪声指的是均值为0、方差为常数的纯随机序列。在进行纯随机性检验时,我们希望时间序列数据的残差值(即实际值与预测值之间的误差)符合白噪声假设。如果残差值不符合白噪声假设,则可能存在潜在的模式、趋势或周期性等,需要进一步分析和处理。

常见的纯随机性检验方法包括:

1-->
Ljung-Box检验:基于残差序列的自相关和偏自相关系数来检验序列是否具有相关性。

2-->Breusch-Godfrey检验:基于回归模型的残差来检验序列是否具有相关性。

3-->ARCH检验:用于检验序列的异方差性(heteroscedasticity)。

4-->Runs检验:基于序列的正负交替次数来检验序列是否具有趋势或周期性。

在实践中,我们通常会使用多种方法来检验时间序列数据的纯随机性,并结合其他分析方法来判断数据是否存在潜在的模式和趋势。只有在满足纯随机性假设的情况下,才能进行有效的预测和分析。

注:当p值小于0.05时为显著非白噪声序列

1 #纯随机性检验,白噪声检验
2 Box.test(a)

6.单位根检验:ADF检验

单位根检验,也称为增广迪基-福勒检验(Augmented Dickey-Fuller test,ADF test),是一种用于检验时间序列数据是否具有单位根(unit root)的方法。在时间序列分析中,单位根通常是假设时间序列数据中存在一种长期的趋势或非平稳性。

单位根检验旨在验证时间序列数据是否具有平稳性。如果时间序列数据具有单位根,则说明数据存在非平稳性,并且预测和分析可能会出现问题。因此,在进行时间序列分析之前,我们需要先进行单位根检验,以确保数据具有平稳性。

ADF检验是一种常用的单位根检验方法,它通过计算时间序列数据的ADF统计量来判断数据是否具有单位根。ADF统计量与t值类似,表示观测值与滞后版本之间的差异程度,同时考虑了其他因素的影响。如果ADF统计量小于对应的临界值,则拒绝原假设,即数据不存在单位根,表明数据具有平稳性。

除了ADF检验外,还有许多其他的单位根检验方法,例如Dickey-Fuller检验、KPSS检验等。不同的单位根检验方法具有不同的假设条件和适用范围,需要根据具体情况来选择合适的方法。

总之,单位根检验是一种重要的时间序列分析工具,用于验证数据是否具有平稳性。只有在数据具有平稳性的情况下,才能进行有效的预测和分析。

检验假设:H0:存在单位根 vs H1:不存在单位根

如果序列平稳,则不应存在单位根,所以我们希望能拒绝原假设

1 #ADF检验
2 #install.packages('aTSA')
3 library(aTSA)4 adf.test(a)

7.ARMA参数估计

1 #参数估计
2 result=arima(a,order=c(2,0,1),method = 'CSS')3 result

8.序列拟合结果图

ts.diag(result)

9.模型预测

1 #预测
2 #install.packages('forecast')
3 library(forecast)4 pre=forecast(result,h=5)5 pre6 plot(pre,lty=2)7 lines(pre$fitted,col=2)


R语言代码实战:


#题3
a=c(12.373,12.871,11.799,8.850,8.070,7.886,6.920,7.593,7.574     ,8.230,10.347,9.549,7.461,8.159,9.243,9.160,10.683,10.516,9.077,8.104,7.700,8.640 ,8.736 ,9.027 ,9.380 ,9.783 ,9.648, 8.135 ,8.222, 9.155,8.941, 9.682, 10.331, 10.601, 10.693 ,8.311)
a
=ts(a)#时序图 plot(a,col='blue')#自相关图与偏自相关图 acf(a)
pacf(a)
#纯随机性检验,白噪声检验 Box.test(a)#ADF检验#install.packages('aTSA') library(aTSA)
adf.test(a)
#参数估计 result=arima(a,order=c(2,0,1),method = 'CSS')
result

ts.diag(result)
#预测#install.packages('forecast') library(forecast)
pre
=forecast(result,h=5)
pre
plot(pre,lty
=2)
lines(pre$fitted,col
=2)

View Code

说明

使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记。本篇介绍 VLD 源码的调试。同系列文章目录可见
《内存泄漏检测工具》目录


1. VLD 库源码调试步骤


vld2.5.1
版本为例,
下载源码
后,源码包中各文件的用途可看本人另一篇博客
【VLD】源码文件概览
。使用
VLD
进行泄漏检测时,有时候会出现突然崩溃、退出时没有打印以下提示信息等情况,此时就可以调试一下
VLD
的源码,查一查是哪里出了问题。

Visual Leak Detector is now exiting.

与其他
DLL
的调试方法一样,
VLD
源码调试遵循以下步骤(参考
MSDN - how-to-debug-from-a-dll-project
)。

1.1 设置为启动项目

使用
VS2015
打开
vld_vs14.sln
,将
vld
设置为启动项目。

Oh Shit!-图片走丢了-打个广告-欢迎来博客园关注“木三百川”

1.2 设置调试程序

进入
vld 属性页
->
配置属性
->
调试
页面。选择
Debug
模式,选择
本地 Windows 调试器
,根据自己需求设置调试程序与调试程序的启动参数。点击确定。

Oh Shit!-图片走丢了-打个广告-欢迎来博客园关注“木三百川”

这个
命令
就是指调用
vld.dll
的程序(被称为调试程序)所在路径,我的是
E:\Cworkspace\VSDemo\testVLD\Debug\testVLD.exe
,如果这个调试程序需要额外的参数,就把参数填写在下一行
命令参数
中,没有参数空着就好。

Oh Shit!-图片走丢了-打个广告-欢迎来博客园关注“木三百川”

1.3 设置输出目录

进入
vld 属性页
->
配置属性
->
常规
页面。选择
Debug
模式,根据自己需求设置输出目录。点击确定。

Oh Shit!-图片走丢了-打个广告-欢迎来博客园关注“木三百川”

设置为调试程序的同一级目录,我的是
E:\Cworkspace\VSDemo\testVLD\Debug
,由于
vld_vs14.sln
的路径为
E:\Cworkspace\VSDemo\vld-master
,因此这里显示为相对路径。

Oh Shit!-图片走丢了-打个广告-欢迎来博客园关注“木三百川”

1.4 拷贝 vld 依赖文件


vld
安装目录中的
dbghelp.dll

Microsoft.DTfW.DHL.manifest
这两个文件拷贝至调试程序的同一级目录,我的是
E:\Cworkspace\VSDemo\testVLD\Debug
,要注意是
Win32
还是
x64
,拷对应的才行。

Oh Shit!-图片走丢了-打个广告-欢迎来博客园关注“木三百川”

1.5 加断点调试

至此,就可以开始调试了,不妨在
VisualLeakDetector
类的构造函数中加一个断点,点击
本地 Windows 调试器
开始调试(或者按
F5
),程序成功停在了断点处。

Oh Shit!-图片走丢了-打个广告-欢迎来博客园关注“木三百川”

2. 注意事项

调试时,有以下几点需注意:

  • vld
    源码生成的
    vld.dll
    与调试程序所引用的
    vld.dll
    必须是
    同一路径下的同一个文件
    ,这也是要重新设置
    vld
    输出目录的原因。为确保调试程序运行时能正确找到
    vld
    生成的
    dll
    ,可以将
    vld
    输出目录设置
    为调试程序所在目录
    ,或者

    Path
    环境变量的某个目录(例如 vld 安装目录的 bin 子目录下)

  • vld
    的依赖文件
    dbghelp.dll

    Microsoft.DTfW.DHL.manifest
    也应该放在调试程序能找到的地方。
  • 平台位数必须一致,
    Win32
    时都必须得是
    Win32

    x64
    时都必须得是
    x64
  • 调试程序必须能找到它所依赖的其他环境(比如
    xx.dll
    )。特别是 QT 开发的调试程序,其依赖的
    Qt DLL
    比较多,直接运行时会提示缺失某某
    DLL
  • 调试程序与
    DLL
    都必须是
    Debug
    版本。

国内文章

基于 Github 平台的 .NET 开源项目模板. 嘎嘎实用!

https://www.cnblogs.com/NMSLanX/p/17326728.html

大家好,为了使开源项目的维护和管理更方便一些,出于个人需求写了一款开源项目的模板,该模板基于 Github 平台,并使用 .NET 来实现管道功能.
在接受过实战检验后, 于今天开源, 项目地址:
https://github.com/night-moon-studio/Template

.NET开源分布式锁DistributedLock

https://www.cnblogs.com/Z7TS/p/17359113.html

本文介绍了.NET开源分布式锁DistributedLock项目,讲述了线程锁和分布式锁的区别,Redis分布式锁的实现原理,RedLock算法的加锁过程,以及DistributedLock项目的简介和源码分析。本文使用了一些代码示例和图片来说明分布式锁的概念和用法。本文可以帮助读者了解.NET中如何使用Redis实现分布式锁的功能。

Sementic Kernel 案例之网梯科技在线教育

https://www.cnblogs.com/shanyou/p/17363651.html

2023年4月22日北京网梯科技发展有限公司研发总监马鸿图分享了他本人对 AI 应用于教育的看法,以及如何将 AI 应用于在线教育,并向大家展示了基于 ChatGPT 的智能教学应用。视频参见 B站【将 ChatGPT 与 AI 应用于在线教育产品,实现智能化教学|开源云原生开发者日 2023】在AI2.0 :将ChatGPT和AI技术更深层次“融入”教学场景的分享部分给大家分享了一个Sementic Kernel的案例,当前Semantic Kernel 主要是使用C# 开发,因此网梯科技也拥抱了.NET 7 来实施AI2.0的落地。

为HttpClient开启HTTP/2

https://www.cnblogs.com/chenyishi/p/17361557.html

本文介绍了.NET Core中使用HttpClient启用HTTP/2和HTTP/3的方法和优势。文章分别说明了如何设置DefaultRequestVersion和DefaultVersionPolicy选项,如何在HttpRequestMessage实例上设置Version和VersionPolicy属性,以及如何在SocketsHttpHandler类上开启EnableMultipleHttp2Connections属性。文章还提到了.NET 6中HTTP/3的预览功能。文章的内容涉及网络编程、性能优化和新技术的应用。

C# 手写识别方案整理

https://www.cnblogs.com/kybs0/p/17361589.html

本文介绍了如何在.NET Core中使用书写识别的方案。文章首先给出了官网的案例,并指出了输出准确度不高的问题。然后,文章结合作者的开发经验,提供了一个书写识别的代码示例,并说明了需要引用的命名空间和DLL文件。文章最后给出了一个参考链接,介绍了C# vs2012中如何实现手写识别。文章的内容涉及.NET Core、HttpClient、书写识别等技术主题。

【Dotnet 工具箱】JIEJIE.NET - 强大的 .NET 代码混淆工具

https://www.cnblogs.com/dotnet-box/p/17360983.html

IEJIE.NET 是一个使用 C# 开发的开源 .NET 代码加密工具。很多 .NET 开发人员担心他们的软件被破解,版权受到侵犯,所以他们使用一些工具来混淆 IL 代码。比如 PreEmptive dotfuscator, 但有些场景的需求,是这些工具不能满足的。所以作者写了 JieJie.NET,它可以深度加密.NET程序集,帮助大家保护版权。重要的是,这个工具是开源的。

记一次 Windows10 内存压缩模块 崩溃分析

https://www.cnblogs.com/huangxincheng/p/17355938.html

在给各位朋友免费分析 .NET程序 各种故障的同时,往往也会收到各种其他类型的dump,比如:Windows 崩溃,C++ 崩溃,Mono 崩溃,真的是啥都有,由于基础知识的相对缺乏,分析起来并不是那么的顺利,今天就聊一个
Windows
崩溃的内核dump 吧,这个 dump 是前几天有位朋友给到我的,让我帮忙看一下,有了dump之后上 windbg 分析。

自学C#,要懂得善用MSDN

https://www.cnblogs.com/chingho/p/17349305.html

很多初学者学习编程,都会通过看别人写的教程、或者录制的视频,来学习。

这是一个非常好的途径,因为这个是非常高效的。

但是这样,存在两个问题:

1、
教程不够全面
:任何再好的教程,都无法囊括所有的知识点,更多是讲解部分精髓而已;

2、
无法掌握学习方法
:很多教程都是教你如何使用,但是没有教你如何学习,如何掌握自学方法。

针对以上问题,
我首推大家要懂得善用MSDN。

.NET Web入门到高级路线(新版本)

https://www.cnblogs.com/hejiale010426/p/17349394.html

本文总结了学习.NET Web开发技术的从入门到高级的路线图,包括简单的C#和.NET基础知识,常用的ORM、关系型数据库中间件等等方面。

C# 异步进阶— 自定义 TaskSchedule(三)

https://www.cnblogs.com/aoximin/p/17324471.html

本文介绍了如何使用TaskThreadPool类实现一个自定义的线程池,用于控制task的调度和执行。文章首先创建了一个线程安全的队列,用于存储task,并使用Semaphore实现了等待和通知的机制。然后创建了指定数量的线程,每个线程从队列中取出task并执行。最后,文章提供了一个Dispose方法,用于释放资源和停止线程。文章还简要介绍了Semaphore的作用和原理。

.NET使用nacos配置,手把手教你分布式配置中心

https://www.cnblogs.com/raok/p/17348508.html

Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

这么优秀的分布式服务管理平台,怎么能不接入呢?

nacos的安装和使用这里就不细说了,可以参考网上教程和官方文档。
https://nacos.io/zh-cn/docs/quick-start.html

我们以创建一个webapi项目为例手把手教你使用Nacos进行配置管理和服务注册

主题

【英文】microsoft/SqlScriptDOM:ScriptDOM/SqlDOM 是一个用于解析 T-SQL 语句和与其抽象语法树交互的 .NET 库

https://github.com/microsoft/SqlScriptDOM

【英文】发布 11.0.0 预览 7 · AvaloniaUI/Avalonia

https://github.com/AvaloniaUI/Avalonia/releases/tag/11.0.0-preview7

【英文】ReSharper and Rider 2023.1.1 - 错误修复在这里 | .NET 工具博客

https://blog.jetbrains.com/dotnet/2023/04/27/resharper-and-rider-2023-1-1-bug-fixes/

【英文】.NET Framework 2023 年 4 月累积更新预览更新 - .NET 博客

https://devblogs.microsoft.com/dotnet/dotnet-framework-april-2023-cumulative-update-preview-updates/

【英文】发布 v7.0.4 · npgsql/npgsql

https://github.com/npgsql/npgsql/releases/tag/v7.0.4

加入 .NET 团队参加 Microsoft Build 2023!- .NET 博客

https://devblogs.microsoft.com/dotnet/microsoft-build-2023-and-dotnet/

文章、幻灯片等

【日文】在外部文件中定义 Semantic Kernel 的技能并加载

https://zenn.dev/microsoft/articles/semantic-kernel-2

【日文】在 C# 上尝试使用 Azure OpenAI 服务的 Semantic Kernel

https://zenn.dev/microsoft/articles/semantic-kernel-1

【日文】通过代码获取主题的实际颜色等(C# / WinUI 3)

https://zenn.dev/shinta0806/articles/theme-property

【英文】探索 Visual Studio 中的 JavaScript 和 TypeScript 开发 - Visual Studio 博客

https://devblogs.microsoft.com/visualstudio/exploring-javascript-and-typescript-development-in-visual-studio/

【英文】Rider 2023.1 中的 HTML、XML、JSON 和 JWT 可视化工具 | .NET 工具博客

https://blog.jetbrains.com/dotnet/2023/04/27/html-xml-json-and-jwt-visualizers-in-rider-2023-1/

【英文】使用 JetBrains Rider 进行远程开发 | .NET 工具博客

https://blog.jetbrains.com/ja/dotnet/2023/04/10/remote-development-with-jetbrains-rider/

【英文】如何用一个简单的控制台消息保存我的生产数据库

https://dev.to/vanenshi/how-i-saved-my-production-database-with-one-simple-console-message-4fjm

【英文】为 Supabase 创建一个 Dapr 可插入组件

https://dev.to/diagrid/creating-a-dapr-pluggable-component-for-supabase-32kj

【英文】ASP.NET Core Minimal APIs 简介 | .NET 工具博客

https://blog.jetbrains.com/dotnet/2023/04/25/introduction-to-asp-net-core-minimal-apis/

【英文】使用 Elastic APM / RUM agent 将 .Net 应用程序跟踪发送到 Elasticsearch

https://medium.zenika.com/send-net-application-traces-to-elasticsearch-using-elastic-apm-rum-agent-d7ff666666b1ef

【英文】可观察性 .NET & OpenTelemetry Collector

https://dev.to/kim-ch/observability-net-opentelemetry-collector-25g1

【日文】.NET MAUI中Ctrl+Enter等键盘事件处理的困难 - Qiita

https://qiita.com/selfstudy/items/d0f9520d3b40f8946d36

【英文】将字符串转换为 .NET 对象 - IParsable 和 ISpanParsable

https://csharp.christiannagel.com/2023/04/14/iparsable/

【英文】使用 Coyote 为 C# 程序提供工业级可控并发测试 - Microsoft Research

https://www.microsoft.com/en-us/research/publication/industrial-strength-controlled-concurrency-testing-for-c-programs-with-coyote/

【日文】让我们学习 .NET - Azure - YouTube

https://www.youtube.com/live/g5KIYx6p8Ek

【英文】开始在 .NET 中使用 OpenAI - .NET 博客

https://devblogs.microsoft.com/dotnet/getting-started-azure-openai-dotnet/

【日文】Windows App SDK 1.3 中的 System Backdrop 设置和 AppWindow 使用变得更简单 - しばやん雑記

https://blog.shibayan.jp/entry/20230425/1682412792

【英文】你知道你的 API 发生了什么吗?使用 AppMetrics 收集 ASP.NET Core API 指标。

https://medium.com/@milwojarski/do-you-know-what-is-going-on-with-your-api-collect-asp-net-core-api-metrics-using-appmetrics-f5efa2cf050d

【英文】使用 C# 示例的 Redis 键空间通知

https://dev.to/sayganov/redis-keyspace-notifications-with-a-c-example-2ahp

【日文】WPF 与 WinUI 3 功能对比表

https://zenn.dev/shinta0806/articles/wpf-vs-winui3

【日文】关于 HTTP/2 和 gRPC 的常见误解。- ねののお庭。

https://blog.neno.dev/entry/2023/04/22/190510

【日文】与 HttpClientFactory 成为朋友:一个轻松升级 .NET 中的 HttpClient 游戏的指南

https://medium.com/@longeardev/making-friends-with-httpclientfactory-a-chill-guide-to-upgrading-your-httpclient-game-in-net-f4cef3f72a63

【日文】[C#] 探讨 EnumerateFiles() 的异常处理的最佳方法 - Qiita

https://qiita.com/hqf00342/items/3707ab2bf5f480ec41f8

【英文】使用 Vault .NET 客户端库探索 HashiCorp Vault

https://dev.to/berviantoleo/exploring-hashicorp-vault-with-vault-net-client-library-194n

版权声明

由于笔者没有那么多时间对国内的一些文章进行整理,欢迎大家为《.NET周报-国内文章》板块进行贡献,需要推广自己的文章或者框架、开源项目可以下方的项目地址提交Issue或者在我的微信公众号私信。

格式如下:

  • 10~50字左右的标题
  • 对应文章或项目网址访问链接
  • 200字以内的简介,如果太长会影响阅读体验

https://github.com/InCerryGit/.NET-Weekly

.NET性能优化交流群

相信大家在开发中经常会遇到一些性能问题,苦于没有有效的工具去发现性能瓶颈,或者是发现瓶颈以后不知道该如何优化。之前一直有读者朋友询问有没有技术交流群,但是由于各种原因一直都没创建,现在很高兴的在这里宣布,我创建了一个专门交流.NET性能优化经验的群组,主题包括但不限于:

  • 如何找到.NET性能瓶颈,如使用APM、dotnet tools等工具
  • .NET框架底层原理的实现,如垃圾回收器、JIT等等
  • 如何编写高性能的.NET代码,哪些地方存在性能陷阱

希望能有更多志同道合朋友加入,分享一些工作中遇到的.NET性能问题和宝贵的性能分析优化经验。
目前一群已满,现在开放二群。

如果提示已经达到200人,可以加我微信,我拉你进群:
ls1075

另外也创建了
QQ群
,群号: 687779078,欢迎大家加入。

本文首发于公众号:Hunter后端

原文链接:
Django笔记三十七之多数据库操作(补充版)

这一篇笔记介绍一下 Django 里使用多数据库操作。

在第二十二篇笔记中只介绍了多数据库的定义、同步命令和使用方式,这一篇笔记作为补充详细介绍如何对 Django 系统的多个数据库进行针对的建表同步操作。

以下是本篇笔记目录:

  1. DATABASES 定义
  2. application创建和设置
  3. migration 和 migrate 操作
  4. 几个注意的点

1、DATABASES 定义

这里还是复用之前的 Django 系统,这里我们额外建立两个数据库连接,之前的 default 还是不变:

# hunter/settings.py

DATABASES = {
    'default': {
        ...
    },
    'user': {
        'ENGINE': "django.db.backends.mysql",
        'NAME': "db_1",
        "USER": "root",
        "PASSWORD": "123456",
        "HOST": "192.168.1.10",
        "PORT": 3306,
    },
    'other': {
        'ENGINE': "django.db.backends.mysql",
        'NAME': "db_2",
        "USER": "root",
        "PASSWORD": "123456",
        "HOST": "192.168.1.11",
        "PORT": 3306,
    },
}

数据库里的连接名称分别是 user 和 other。

注意,这里我们使用的是不同的数据库 DATABASE,分别是 db_1 和 db_2,他们可以在一个地址的 MySQL 里,也可以在不同地址。

2、application创建和设置

接下来我们以 application 为整体来指定 model 对数据库进行操作。

上面这句话这里释义一下,就是说针对多个数据库,我们这里默认使用整个 application 下的 model 表与之对应,比如说 new_user 这个 app 下的 model 的 migration 操作都写入 DATABASE 下 user 对应的数据库。

当然,这个操作过程我们还需要在 settings.py 中定义一个映射 DATABASE_APPS_MAPPING,这个我们后面再说。

创建application

首先,我们分别创建两个 application,一个 application 名为 new_user,另一个名为 other_info,使用下面的命令创建:

python3 manage.py startapp new_user

python3 manage.py startapp other_info

然后在系统的根目录会出现这两个文件夹。

然后在 settings.py 中注册这两个 app:

# hunter/settings.py

INSTALLED_APPS = [
    ...
    'new_user.apps.NewUserConfig',
    'other_info.apps.OtherInfoConfig',
    ...
]

application与数据库的对应设置

然后设置 application 与 DATABASE 的对应关系:

DATABASE_APPS_MAPPING = {
    "new_user": "user",
    "other_info": "other",
}

在这里的这个映射关系的 key 是我们的 application 的名称,value 则是 settings.py 中 DATABASES 对应的数据库的 key。

比如这里我们将 new_user 这个 app 指定到了 user 数据库。

创建 model

接下来我们分别在两个 application 下创建对应的 model:

# new_user/models.py

from django.db import models

class NewUser(models.Model):
    pass

    class Meta:
        app_label = "new_user"
# other_info/models.py

from django.db import models

class OtherInfo(models.Model):
    pass

    class Meta:
        app_label = "other_info"

在这两个 model 里,我手动给其添加了 app_label 字段,值为各自所在 application 下的名,表示这个 model 是从属于 app_label 这个 application 下。

其实对于每个 model,meta 信息下都会有这个字段,默认值为该 model 所处的 application 的名称,这里为了显示对比,我额外标记了出来。

查看 app_label 的方式为:

from new_user.models import NewUser
NewUser._meta.app_label

# new_user

而在前面的 settings.py 里我们设置了 DATABASE_APPS_MAPPING 映射

DATABASE_APPS_MAPPING = {
    "new_user": "user",
    "other_info": "other",
}

所以这里的 NewUser model 使用的就是 user 这个数据库。

接下来我们可以进行 migration 操作来测试将表结构写入 user 数据库。

3、migration 和 migrate 操作

接下来我们创建 migration 文件:

python3 manage.py makemigrations new_user

python3 manage.py makemigrations other_info

然后会在 new_user 和 other_info 下分别创建对应的 migration 文件。

接下来进行 migrate 的时候需要指定 database 参数,也就是我们前面 settings.py 里的 DATABASES 的对应的 key:

python3 manage.py migrate new_user --database=user

python3 manage.py migrate other_info --database=other

根据 settings.py 里 DATABASE_APPS_MAPPING 里的映射关系,--database 对应的参数就是相应的数据库。

执行完上面的命令之后,在两个对应的数据库里就会创建 django_migrations 表和 model 对应的表。

创建 django_migrations 表是因为每个 database 也需要记录各自的 migration 迁移记录。

至此,我们就将 application 下的 model 和 database 对应了起来。

4、几个注意的点

数据的增删改查

前面我们将 model 和 database 对应了起来之后,在操作对应的 model 的时候还是需要 using() 来指定操作的 database:

from new_user.models import NewUser
NewUser.objects.using("user").create(id=1)

default数据库

在这篇笔记里,我们另外设置了两个数据库用于对应新建的 application,且在 DATABASE_APPS_MAPPING 中设置了 application 到 database 的映射,那么没有设置映射关系的 application 下的 model 其实就还是默认属于 default 数据库的。

比如我们之前创建的 blog 这个 application,就相当于是:

DATABASE_APPS_MAPPING = {
    "blog": "default",
    "new_user": "user",
    "other_info": "other",
}

不过因为是默认设置,所以为了方便我们没有显式的设置出来。

并且,对于多个 application 是可以对应同一个数据库链接的,比如我们默认的 default,没有设置的 application 都对应的是 default 的数据库链接。

假设我们又创建了一个名为 article 的 app,也想要对应 other 数据库,可以这样操作:

DATABASE_APPS_MAPPING = {
    "blog": "default",
    "new_user": "user",
    "other_info": "other",
    "article": "other",
}

某 app 下设置其他 app 的 model

这个操作是否可以呢?

可以,假设我们在 new_user 下创建一个 model,但是设置的是 other_info 的 app_label:

# new_user/models.py

class OtherInfoInNewUser(models.Model):
    pass

    class Meta:
        app_label = "other_info"

然后我们对 new_user 这个 app 执行下面的操作是检测不到有新 migration 的

python3 manage.py makemigrations new_user

因为当我们 makemigrations 指定 app 名称的时候,系统会去检测这个 app 下是否有属于这个 app 的新的 model 变化,而我们设置 OtherInfoInNewUser 这个 model 却从属于 other_info,所以是检测不到变化的。

只有当我们执行:

python3 manage.py makemigrations other_info

这个操作的时候,系统才会检测到 app_label='other_info' 的 model 的变化,然后创建新的 migration。

上面这个操作虽然是可行的,但是为了统一管理,还是不推荐这么操作。

如果想获取更多后端相关文章,可扫码关注阅读:
image