文章目录

  • 搜索与图论(三)
    • 最小生成树
      • Prim算法
      • Kruskal算法
      • 总结
    • 二分图
      • 染色法
      • 匈牙利算法
      • 小结

搜索与图论(三)

这一节讲解的是最小生成树和二分图

最小生成树

什么是最小生成树?首先,给定一个节点数是n,边数是m的无向连通图G。

则由全部的n个节点,和n-1条边构成的无向连通图被称为G的一颗生成树,在G的所有生成树中,边的权值之和最小的生成树,被称为G的最小生成树。

有两种常用算法:

  • Prim算法(普利姆)

    • 朴素版Prim(时间复杂度O(n2),适用于稠密图)
    • 堆优化版Prim(时间复杂度O(mlogn),适用于稀疏图)
  • Kruskal算法(克鲁斯卡尔)

    适用于稀疏图,时间复杂度O(mlogm)

对于最小生成树问题,如果是稠密图,通常选用朴素版Prim算法,因为其思路比较简洁,代码比较短,如果是稀疏图,通常选用Kruskal算法,因为其思路比Prim简单清晰。堆优化版的Prim通常不怎么用。

Prim算法

这里只讲朴素版Prim。其算法流程如下

(其中用集合s表示,当前已经在连通块中的所有的点)

1. 初始化距离, 将所有点的距离初始化为+∞
2. n次循环1. t <- 找到不在集合s中, 且距离最近的点2. 用t来更新其他点到集合s的距离3. 将t加入到集合s中

注意,一个点t到集合s的距离,指的是:若点t和集合s中的3个点有边相连。则点t到集合s的距离就是,t与3个点相连的边中,权重最小的那条边的权重。

练习图:acwing - 858: Prim算法求最小生成树

题解:(C++)

#include<iostream>
#include<cstring>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N], d[N];
bool visited[N];void prim() {memset(d, 0x3f, sizeof d); // 初始化距离为正无穷int sum = 0;for(int i = 1; i <= n; i++) {// 循环n次// 选出距离集合s最小的点int t = 0;for(int j = 1; j <= n; j++) {if(!visited[j] && d[j] <= d[t]) t = j; // 这里用<=, 可以避免对第一次选点做特判}if(i == 1) d[t] = 0;// 第一次加入集合的点, 其到集合的距离为0if(d[t] == INF) {// 选中的点距离是正无穷, 无效printf("impossible\n");return;}// 把这个点放到集合s里visited[t] = true; sum += d[t]; // 这次放进来的// 更新其他点到集合s的距离, for(int j = 1; j <= n; j++) {if(!visited[j] && g[t][j] != INF && g[t][j] < d[j]) {d[j] = g[t][j];}}}printf("%d\n", sum);
}int main() {memset(g, 0x3f, sizeof g);scanf("%d%d", &n, &m);while(m--) {int x, y, w;scanf("%d%d%d", &x, &y, &w);g[x][y] = min(g[x][y], w);g[y][x] = g[x][y];}prim();return 0;
}

题解:(Java)

import java.util.Scanner;/*** @Author yogurtzzz* @Date 2021/6/28 16:43**/
public class Main {static int INF = 0x3f3f3f3f;static Scanner scanner = new Scanner(System.in);public static void main(String[] args) {int n = readInt(), m = readInt();int[][] g = new int[n + 1][n + 1];for(int i = 1; i <= n; i++)for (int j = 1; j <= n; j++) g[i][j] = INF;while(m-- > 0) {int u = readInt(), v = readInt(), w = readInt();g[v][u] = g[u][v] = Math.min(g[u][v], w); // 无向图, 连两条边}int res = prim(g, n);if (res == INF) System.out.println("impossible");else System.out.println(res);}static int prim(int[][] g, int n) {int[] d = new int[n + 1];boolean[] st = new boolean[n + 1];for(int i = 0; i < n + 1; i++) d[i] = INF;int sum = 0; // 最小生成树的边权之和// 循环n次, 每次选择一个不在集合s中, 但是距离集合s最小的点for(int i = 1; i <= n; i++) {// 遍历, 找出一个不在s中, 距离最小的点int t = 0;for(int j = 1; j <= n; j++) {if (!st[j] && d[j] <= d[t]) t = j;}if (i == 1) d[t] = 0; // 第一次选取的点, 距离应当是0if (d[t] == INF) return INF; // 若选出来的点到集合的距离是正无穷, 则说明不联通sum += d[t]; // 将这条边加入到生成树st[t] = true; // 将点t加入到集合s// 更新其他点到集合的距离, 只更新和t相连的边for(int j = 1; j <= n; j++) {if (!st[j] && g[t][j] != INF && d[j] > g[t][j]) {d[j] = g[t][j];}}}return sum;}static int readInt() {return scanner.nextInt();}
}

Kruskal算法

基本思路:

  1. 先将所有边,按照权重,从小到大排序

  2. 从小到大枚举每条边(a,b,w)

    若a,b不连通,则将这条边,加入集合中(将a点和b点连接起来)

Kruskal算法初始时,相当于所有点都是独立的,没有任何边。

Kruskal不需要用邻接表或者邻接矩阵来存图,只需要存所有边就可以了

其实就是并查集的简单应用,可以参考acwing - 837: 连通块中点的数量

练习题:acwing - 859: Kruskal算法求最小生成树

题解:(C++)

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;struct Edge {int a, b, w;bool operator < (const Edge& W) {return w < W.w;}
} edges[M];int n, m;
int p[N];int find(int x) {if(x != p[x]) p[x] = find(p[x]);return p[x];
}void kruskal() {// 先对所有边从小到大排序sort(edges, edges + m);int totalW = 0, edgeCtn = 0;// 枚举全部边for(int i = 0; i < m; i++) {int a = edges[i].a, b = edges[i].b, w = edges[i].w;a = find(a);b = find(b);if(a != b) {// 若a和b不连通, 则加入这条边p[a] = b; // 将a和b连通totalW += w;edgeCtn++;}}if(edgeCtn == n - 1) printf("%d\n", totalW);else printf("impossible\n");
}int main() {scanf("%d%d", &n, &m);for(int i = 0; i < m; i++) {int a, b, w;scanf("%d%d%d", &a, &b, &w);edges[i] = {a, b, w};}for(int i = 1; i <= n; i++) p[i] = i;kruskal();return 0;
}

题解:(Java)

import java.util.Arrays;
import java.util.Scanner;/*** @Author yogurtzzz* @Date 2021/6/30 16:42**/
public class Main {static class Edge implements Comparable<Edge> {int a, b, w;public Edge(int a, int b, int w) {this.a = a;this.b = b;this.w = w;}@Overridepublic int compareTo(Edge o) {return w - o.w;}}static Scanner scanner = new Scanner(System.in);public static void main(String[] args) {int n = readInt(), m = readInt();Edge[] edges = new Edge[m];int[] p = new int[n + 1];for(int i = 0; i < m; i++) {int a = readInt(), b = readInt(), w = readInt();edges[i] = new Edge(a, b, w);}// 初始化所有点都是孤立的for(int i = 1; i <= n; i++) p[i] = i;kruskal(edges, n, m, p);}static void kruskal(Edge[] edges, int n, int m, int[] p) {// 1. 先按权重从小到大, 对所有边排序Arrays.sort(edges);int totalWeight = 0, edgeCtn = 0;// 2. 遍历所有边for(int i = 0; i < m; i++) {int a = edges[i].a, b = edges[i].b, w = edges[i].w;a = find(a, p);b = find(b, p);if (a != b) {// a和b还不连通, 则连接p[a] = b;totalWeight += w;edgeCtn++;}}// 若存在最小生成树, 则加入的边一共是 n-1 条if (edgeCtn == n - 1) System.out.println(totalWeight);else System.out.println("impossible");}static int find(int x, int[] p) {if (x != p[x]) p[x] = find(p[x], p);return p[x];}static int readInt() {return scanner.nextInt();}
}

总结

与最短路的算法思想类似,求解最小生成树的算法,也是分别从点的维度边的维度出发

  • Prim

    思想有点类似最短路中的Dijkstra。从点的维度出发,使用一个集合s来表示当前已纳入连通块的点。用d[i]来表示节点i与集合s的距离。注意,一个节点i与一个连通块的距离,指的是,这个节点到连通块中任意点的边权的最小值。

    假设连通块中已经有了3个点a, b, c,现在有一个点d,它与a有一条边相连,且边权为4,与b的边权为6,与c的边权为2。则d与这个连通块的距离就是2。

    初始时,连通块s中没有点,为空。所有点到连通块s的距离初始化为+∞。我们先随便选一个点(比如1号点),将d[1]设为0,表示这个点到连通块s的距离为0,从这个点开始扩展,建立最小生成树。

    循环n次,每次选取一个与连通块距离最小的点,将该点加入连通块s,并通过这个点的出边,去更新其他点到连通块的距离。

    即,每次选择一个点,加入到最小生成树。每次选择哪个点呢,选择与当前连通块距离最小的点。

    一个点的加入伴随着一条边的加入(除了第一个点)。

    所以最终只要加入了n-1条边,就表明n个点已经全部加入了连通块,最小生成树构建完成。

    实际代码中,也是通过一个布尔数组,来维护每个点是否已计入连通块。

    其实某个点i到连通块的距离d[i],一定是这个点到连通块中某个点的边,且一定是边权最小的那个边。所以这样就能保证,每次迭代加入一个点到连通块中,都是以最小的代价(最小权重的边)将这个点加入到连通块中的。所以最终就能保证得到的连通块是最小生成树。

  • Kruskal

    边的维度出发,先将全部边按照权重从小到大排序。然后遍历全部边,每次查看这条边的左右两个端点,是否处于同一个连通块。若不是,则将这条边加入到最小生成树中,并把两个端点放入到同一连通块。这样,当遍历完全部的边之后,会尽可能地将所有点加入到同一个连通块中。且每次是仅当两个点不在连通块时,才将这条边加入,而边是从小到大排序的。这样就能保证尽可能以小的代价,来构造生成树。所以最终得到的就是最小生成树。

    注意kruskal的算法流程中,为了确定2个点是否处于同一连通块,需要用到并查集。

二分图

首先,什么是二分图呢?

二分图指的是,可以将一个图中的所有点,分成左右两部分,使得图中的所有边,都是从左边集合中的点,连到右边集合中的点。而左右两个集合内部都没有边。图示如下

这一节讲了2部分内容,分别是染色法匈牙利算法

其中染色法是通过深度优先遍历实现,时间复杂度是O(n×m);匈牙利算法的时间复杂度理论上是O(n×m),但实际运行时间一般远小于O(n×m)。

图论中的一个重要性质:一个图是二分图,当且仅当图中不含奇数环

奇数环,指的是这个环中边的个数是奇数。(环中边的个数和点的个数是相同的)

在一个环中,假设共有4个点(偶数个),由于二分图需要同一个集合中的点不能互相连接。

则1号点属于集合A,1号点相连的2号点就应当属于集合B,2号点相连的3号点应当属于集合A,3号点相连的4号点应当属于集合B。4号点相连的1号点应当属于集合A。这样是能够二分的。

而若环中点数为奇数,初始时预设1号点属于集合A,绕着环推了一圈后,会发现1号点应当属于集合B。这就矛盾了。所以存在奇数环的话,这个图一定无法二分。

可以用染色法来判断一个图是否是二分图,使用深度优先遍历,从根节点开始把图中的每个节点都染色,每个节点要么是黑色要么是白色(2种),只要染色过程中没有出现矛盾,说明该图是一个二分图,否则,说明不是二分图。

染色法

练习题:acwing - 860: 染色法判定二分图

先上个错误解法

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
// 使用邻接表来存储图
int h[N], e[M], ne[M], idx;int n, m;
int color[N];void add(int x, int y) {// 链表的头插法e[idx] = y;ne[idx] = h[x];h[x] = idx++;
}bool dfs(int x) {// 深搜这个节点的全部子节点for(int i = h[x]; i != -1; i = ne[i]) {int u = e[i]; // 子节点if(color[u] == -1) {// 子节点还未染色, 则直接染色, 并深搜color[u] = !color[x];if(!dfs(u)) return false;} else if(color[u] == color[x]) return false; // 若子节点和父节点颜色一致, 则说明矛盾}// 深搜结束, 未出现矛盾, 则染色成功, 判定是二分图return true;
}int main() {memset(h, -1, sizeof h); // 初始化空链表memset(color, -1, sizeof color); // 颜色初始化为-1, 表示还未染色scanf("%d%d", &n, &m);while(m--) {int x, y;scanf("%d%d", &x, &y);add(x, y); // 无向图, 加两条边add(y, x);}color[1] = 0; // 给1号点先染个色// 不能直接从一个点进行dfs,因为可能整个图不是一个连通图// 而其他的连通块不一定是二分图// 所以要对1~n所有点依次进行染色if(dfs(1)) printf("Yes\n");else printf("No\n");return 0;
}

不能直接从一个点直接进行dfs,因为可能整个图不是一个连通图,此时若从一个点进行dfs,则只能把这个点所在的连通块都染色,无法触及到其他的连通块,而其他的连通块不一定是个二分图,所以要对1~n所有点依次进行染色,确保全部点都被染色。

正确解法如下(dfs)

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10; // 由于是无向图, 需要建两条边, 所以边数设为2倍
// 使用邻接表来存储图
int h[N], e[M], ne[M], idx; // 注意这里单链表的实现, 数组大小开为Mint n, m;
int color[N];void add(int x, int y) {// 链表的头插法e[idx] = y;ne[idx] = h[x];h[x] = idx++;
}bool dfs(int x) {// 深搜这个节点的全部子节点for(int i = h[x]; i != -1; i = ne[i]) {int u = e[i]; // 子节点if(color[u] == -1) {// 子节点还未染色, 则直接染色, 并深搜color[u] = !color[x];if(!dfs(u)) return false;} else if(color[u] == color[x]) return false; // 若子节点和父节点颜色一致, 则说明矛盾, 自环应该也算矛盾?}// 深搜结束, 未出现矛盾, 则染色成功, 判定是二分图return true;
}int main() {memset(h, -1, sizeof h); // 初始化空链表memset(color, -1, sizeof color); // 颜色初始化为-1, 表示还未染色scanf("%d%d", &n, &m);while(m--) {int x, y;scanf("%d%d", &x, &y);add(x, y);add(y, x);}bool flag = true;// 依次对所有点进行染色for(int i = 1; i <= n; i++) {if(color[i] == -1) {// 该点还未染色, 则直接染色, 随便染一个色即可(0或1), 并dfs, dfs完成后, 就对这个点所在的连通块都染上了色color[i] = 0;// 进行dfs, 若在对这个连通块染色的过程中出现矛盾, 则直接breakif(!dfs(i)) {flag = false;break;}}}if(flag) printf("Yes\n");else printf("No\n");return 0;
}

题解:Java

import java.util.*;/*** @Author yogurtzzz* @Date 2021/6/30 16:42**/
public class Main {static Scanner scanner = new Scanner(System.in);static int[] h, e, ne;static int idx;static int[] color;public static void main(String[] args) {int n = readInt(), m = readInt();h = new int[n + 1];e = new int[2 * m + 1];ne = new int[2 * m + 1];color = new int[n + 1];Arrays.fill(h, -1);while(m-- > 0) {int a = readInt(), b = readInt();add(a, b);add(b, a);}boolean flag = true;// 染色for(int i = 1; i <= n; i++) {if (color[i] == 0) {// 还没有被染色color[i] = 1; // 染色if (!dfs(i)) { // 深搜, 深搜结束后该点所在的连通块的所有点都会被染色flag = false;break;}}}if (flag) System.out.println("Yes");else System.out.println("No");}static boolean dfs(int x) {for(int i = h[x]; i != -1; i = ne[i]) {int u = e[i];if (color[u] == 0) {// 还未被染色, 直接染color[u] = 3 - color[x];// 染完深搜if (!dfs(u)) return false;} else if (color[u] == color[x]) return false;}return true;}static void add(int a, int b) {e[idx] = b;ne[idx] = h[a];h[a] = idx++;}static String readString() {return scanner.next();}static int readInt() {return scanner.nextInt();}
}

由于染色的本质是遍历图,所以除了dfs,也可以用bfs来做,但是需要注意,bfs中的队列,存的是一个<点的编号,点颜色>这样的二元组(pair),bfs的题解如下:

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;const int N = 2e5 + 10;typedef pair<int, int> PII;int h[N], e[N], ne[N], idx;int c[N]; // colorint n, m, u, v;void add(int a, int b) {e[idx] = b;ne[idx] = h[a];h[a] = idx++;
}bool bfs(int x, int xc) {queue<PII> q;q.push({x, xc});while(!q.empty()) {PII p = q.front();q.pop();int o = p.first, oc = p.second;for(int i = h[o]; i != -1; i = ne[i]) {int u = e[i];if(c[u] == 0) {c[u] = 3 - oc;q.push({u, c[u]});} else if(c[u] == oc) return false;}}return true;
}int main() {memset(h, -1, sizeof h);scanf("%d%d", &n, &m);while(m--) {scanf("%d%d", &u, &v);add(u, v);add(v, u);}bool flag = true;for(int i = 1; i <= n; i++) {if(c[i] == 0) {c[i] = 1;if(!bfs(i, 1)) {flag = false;break;}}}if(flag) printf("Yes");else printf("No");
}

课后题:acwing - 257: 关押罪犯

可以使用二分图来做,也可以使用并查集

//TODO

匈牙利算法

匈牙利算法,是给定一个二分图,用来求二分图的最大匹配的。

给定一个二分图G,在G的一个子图M中,M的边集中的任意两条边,都不依附于同一顶点,则称M是一个匹配。就是每个点只会有一条边相连,没有哪一个点,同时连了多条边。(参考yxc的例子:男生女生恋爱配对,最多能凑出多少对)

所有匹配中包含边数最多的一组匹配,被称为二分图的最大匹配。其边数即为最大匹配数

假设一个二分图,左半边部分节点表示男生,右半边部分节点表示女生。一个男生节点和一个女生节点连了一条边,则表示这两个人之间有感情基础,可以发展为情侣。当我们把一对男女凑成一对时,称为这两个节点匹配。

匈牙利算法的核心思想是:我们枚举左半边所有男生(节点),每次尝试给当前男生找对象。我们先找到这个男生看上的全部女生。(即找到这个节点连接的所有右侧的节点)。遍历这些女生,当一个女生没有和其他男生配对时,直接将这个女生和这个男生配对。则该男生配对成功。当这个女生已经和其他男生配对了,则尝试给这个女生的男朋友,找一个备胎。如果这个女生的男朋友有其他可选择的备胎。则将这个女生的男朋友和其备胎配对。然后将这个女生和当前男生配对。如此找下去…

练习题:acwing - 861: 二分图的最大匹配

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1010,  M = 1e5 + 10;int h[N], e[M], ne[M], idx;
int match[N];
bool st[N]; // 状态变量int n1, n2, m;void add(int a, int b) {e[idx] = b;ne[idx] = h[a];h[a] = idx++;
}// 给一个男生找女朋友
bool find(int x) {// 找出这个男生所有看上的女生for(int i = h[x]; i != -1; i = ne[i]) {int u = e[i]; // 女生节点编号if(st[u]) continue; // 如果这个女生已经被标记, 则跳过st[u] = true; // 先把这个女生标记, 使得后续递归时时跳过这个女生if(match[u] == 0 || find(match[u])) {// 如果当前这个女生没有被匹配, 或者能够给这个女生已匹配的男生另外找个备胎, 则可以match[u] = x;return true;}}return false; // 找了一圈还是不行
}int main() {memset(h, -1, sizeof h);scanf("%d%d%d", &n1, &n2, &m);while(m--) {int a, b;scanf("%d%d", &a, &b);add(a, b); // 只从左半边连到右半边}// 枚举左半边所有点int res = 0;for(int i = 1; i <= n1; i++) {// 每次给一个男生找女朋友时, 清空状态变量memset(st, false, sizeof st);if(find(i)) res++;}printf("%d\n", res);return 0;
}

课后题:acwing - 372: 棋牌摆放

//TODO

小结

二分图相关的算法主要有2个

  • 染色法:是用来判断一个图是否是二分图的
  • 匈牙利算法:是用来求二分图的最大匹配的

Acwing - 算法基础课 - 笔记(图论 · 三)相关推荐

  1. 算法与数据结构模版(AcWing算法基础课笔记,持续更新中)

    AcWing算法基础课笔记 文章目录 AcWing算法基础课笔记 第一章 基础算法 1. 排序 快速排序: 归并排序: 2. 二分 整数二分 浮点数二分 3. 高精度 高精度加法 高精度减法 高精度乘 ...

  2. 背包四讲 (AcWing算法基础课笔记整理)

    背包四讲 背包问题(Knapsack problem)是一种组合优化的NP完全问题.问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高 ...

  3. Acwing - 算法基础课 - 笔记(数学知识 · 二)

    文章目录 数学知识(二) 欧拉函数 公式法 筛法 欧拉定理 快速幂 扩展欧几里得算法 中国剩余定理 数学知识(二) 这一小节主要讲解的内容是:欧拉函数,快速幂,扩展欧几里得算法,中国剩余定理. 这一节 ...

  4. Acwing - 算法基础课 - 笔记(数学知识 · 一)

    文章目录 数学知识(一) 质数 质数的判定 分解质因数 朴素思路 优化 筛选质数 朴素筛法 埃氏筛法 线性筛法 小结 约数 求一个数的所有约数 求约数个数 求约数之和 求最大公约数 数学知识章节,主要 ...

  5. 算法复习笔记(三)分治法

    算法复习笔记(三)分治法 1.引入语 分治法划分对策: 子问题与原问题相比:问题性质一致,问题规模不同 求解一般分为三个阶段: 1.划分:直到问题足够小可以直接求解为止 2.求解: 3.合并:将子问题 ...

  6. ACwing算法基础课全程笔记(2021年8月12日开始重写+优化)

    更好的阅读体验 ※基础模板 2021年8月12日开始对基础课笔记进行重写+优化 请大家支持AcWing正版,购买网课能让自己获得更好的学习体验哦~ 链接:https://www.acwing.com/ ...

  7. AcWing 算法基础课第三节基础算法3 双指针、位运算、离散化、区间合并

    1.该系列为ACWing中算法基础课,已购买正版,课程作者为yxc 2.y总培训真的是业界良心,大家有时间可以报一下 3.为啥写在这儿,问就是oneNote的内存不够了QAQ ACwing C++ 算 ...

  8. AcWing 算法基础课第一节基础算法1排序、二分

    1.该系列为ACWing中算法基础课,已购买正版,课程作者为yxc 2.y总培训真的是业界良心,大家有时间可以报一下 3.为啥写在这儿,问就是oneNote的内存不够了QAQ ACwing C++ 算 ...

  9. AcWing算法基础课第一讲(2):高精度加减乘除、前缀和、差分

    文章目录 1. 高精度加法 2. 高精度减法 3. 高精度乘低精度 4. 高精度除以低精度 5. 一维前缀和 6. 二维前缀和 7. 一维差分 8. 二维差分 1. 高精度加法 这里讲解两个大整数的加 ...

最新文章

  1. ext前后台数据传输的标准化
  2. tomcat项目自动发布脚本.脚本运行效果
  3. appium+python自动化45-夜神模拟器连不上(adb server version (36) doesn't match this client (39); killing...)...
  4. 依赖倒转原则(Dependency Inversion Principle,DIP)
  5. 一对经典的时间获取客户/服务器程序
  6. docker mysql优势_前端使用docker有什么优势呢?
  7. vim 插件之vim-trailing-whitespace
  8. 法国计算机科学专业大学排名,法国计算机视觉专业大学排名(2020年USNEWS)_快飞留学...
  9. HBase从入门到精通系列:误删数据如何抢救?
  10. 软件正在吞噬整个世界?!
  11. 如何在VS上用C#玩坏“Hello World”。
  12. UVA12015 Google is Feeling Lucky【最值+排序】
  13. 从闪迪的一个U盘看整个行业
  14. win7安装android驱动失败怎么办,win7未能成功安装设备驱动程序怎么办
  15. PHPCAS客户端搭建教程
  16. 猪悟能淘宝店商品下载专家 v1.2
  17. Linux系统中社保卡阅读
  18. ABAP tRFC和qRFC
  19. 计算机语言对传统文化的影响,网络文化对传统文化的冲击
  20. 51 地图基本接口(三)

热门文章

  1. Yotta企业云盘:怎样让企业数据文档防泄密
  2. 计算机专业学自我分析,计算机专业学习自我鉴定(精选6篇)
  3. Yii框架下使用redis做缓存,读写分离
  4. 什么是继承python_面向对象继承
  5. 2、生成二维码API接口,免费好用
  6. Elasticsearch7.2中文文档翻译(一):Elasticsearch简介
  7. 辞职在家半年,有幸获得阿里内推机会,鏖战4面成功拿下offer!
  8. 煤炭企业内部调拨物资称重问题如何管理(一)
  9. 尝试做个公众号,歌曲类型的,有兴趣可以关注一下
  10. 【文献学习】热电偶信号调理电路