笔试ACM模式图论建图模板(Java&Python&C++)
互联网笔试中图论的题目比例很多,不管是基础的DFS,BFS,还是涉及到一些算法,如拓扑排序,dijkstra等,都离不开图的构建,LeetCode中的二叉树类型的题目,以及图论的题目,都是已经提供好对应的领接表或者领接矩阵,所以对于初学者来说,还是有必要去学习一下图论中的相关知识点,以及如何构建图的关系的。
树
树这个数据结构,相信看过一些后端八股,比如MySQL,Redis,又或者刷过一些数据结构题目的同学,一定对这种结构比较熟悉,树是由个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
树有以下特点
- 每个节点具有0个或多个子节点
- 没有父节点的节点被称为根节点
- 每一个非根节点只有一个父节点
- 每一个节点及其后代节点整体可以被视为一个树
树中有一类很特殊的树:二叉树,二叉树是一种特殊的树,其中任何一个节点具有的子节点个数不超过2,因此被称为二叉树,如下图所示,就是一个根节点为1的二叉树。1号节点的子节点是2和3,2节点的子节点是4和5,3节点的子节点是6和7。
图
那么树与图具有什么关系呢?其实树是一种特殊的图,就是有向无环图。
- 有向,指的是有方向,比如上面这张图,箭头的方向就表示了其是一个有向图,对于二叉树而言,在递归搜索的时候,你可以从父节点搜到子节点,但是你不能从子节点搜索到父节点
- 无环,不能自己指向自己(自环),或者由多个节点构成一个环,比如
a->b
,b->c
,c->a
,这就构成了一个环,对于树这种数据结构,从根节点出发是自顶向下的,是不会存在环的一个关系的,因此树一定是一种无环图。
图论基本概念
入度:指向当前节点的有向边的数量,比如有一条a->b的有向边,则节点b的入度为1
出度:当前节点被指向的有向边的数量,比如有一条a->b的有向边,则节点a的出度为1
注意:如果在一个有向图中,一个节点的入度为0,那么该节点,就是一个没有父节点的根节点。比如拓扑排序中,起始的入队元素就是入度为0的所有点。邻接矩阵:邻接矩阵是一种常见的图的存储结构,用于表示顶点之间的关系。它使用一个二维数组来表示图中顶点之间的连接关系,其中数组的行和列分别代表图中的顶点,而数组中的元素表示顶点之间是否有边相连,以及边的权重。如果顶点和顶点
之间有边相连,则邻接矩阵中的元素
(或边的权重),否则为0。比如有如下无向图
邻接矩阵表示为:
0 1 0 1 1 0 1 1 0 1 0 1 1 1 1 0
邻接表:邻接表是另一种常见的图的存储结构,它使用一个数组和链表来表示图中的顶点和它们的邻接顶点。具体来说,数组中的每个元素代表图中的一个顶点,而每个元素对应的链表存储了与该顶点相邻的顶点。比如有如下无向图
邻接表表示为:
1: 2 -> 4 #表示1有2和4与它相连 2: 1 -> 3 3: 2 -> 4 4: 1 -> 3
这篇文章讲解领接表的质量还是相当不错的,可以供大家参考:领接表讲解
建图模版
笔试中常考的建图,一般都是需要使用领接表建图
对于一般的BFS或者DFS来说,很多题目是没有边权这种说法的,那么就可以直接使用下面的点权建图模版建图,如果有边权,则可以使用下面的边权模版建图,如果边权在点上,仍然可以使用下面的边权模版建图。
常用的建图方法主要是有点权建图(权值在节点上),边权建图(权值在边上),还有一种就是使用离散化建图(使用哈希表)
领接表点权建图
权值在点上,对应点有权值,对于C++选手来说,可以使用vector
来优化建图,并维护一个w
数组来记录点权
输入样例
n
个点,m
条边,每行输入两个整数a
,b
,表示a->b
连接一条有向边
最后一行输入n
个整数,表示n
个点的点权
4 3 1 2 2 3 3 4 1 2 3 4
C++
#include <bits/stdc++.h> using namespace std; const int N=1E5+10; vector<int>g[N]; //领接表的vector写法 仅适用于点权建图 int w[N]; void dfs(int u,int fa) //如果是有向图 就不需要fa这个变量 { //do things for(int &x:g[u]) //访问u的所有节点 { if(x==fa)continue; //无向边才需要这一句 保证每个节点只会被访问一次(不理解的可以直接背过) dfs(x,u); // do things } } int main(){ int n,m; //n个点,m条边 cin>>n>>m; for(int i=0;i<m;i++){ int a,b; cin>>a>>b; g[a].push_back(b); //a->b建立一条边 } for(int i=1;i<=n;i++)cin>>w[i]; return 0; }
Java
import java.util.Scanner; import java.util.ArrayList; public class Main { static final int N = 100010; static ArrayList<Integer>[] g = new ArrayList[N]; static int[] w = new int[N]; static void dfs(int u, int fa) { // Do things for (int x : g[u]) { if (x == fa) continue; dfs(x, u); // Do things } } public static void main(String[] args) { int n, m; Scanner scanner = new Scanner(System.in); n = scanner.nextInt(); m = scanner.nextInt(); for (int i = 1; i <= n; i++) { g[i] = new ArrayList<>(); } for (int i = 0; i < m; i++) { int a, b; a = scanner.nextInt(); b = scanner.nextInt(); g[a].add(b); // a->b建立一条边 } for (int i = 1; i <= n; i++) { w[i] = scanner.nextInt(); } } }
Python3
N = 100010 g = [[] for _ in range(N)] w = [0] * N def dfs(u, fa): # Do things for x in g[u]: if x == fa: continue dfs(x, u) # Do things n, m = map(int, input().split()) for i in range(m): a, b = map(int, input().split()) g[a].append(b) # a->b建立一条边 w[1:] = map(int, input().split())
领接表边权建图
权值在边上,对应点有权值,对于C++选手来说,可以使用vector
来优化建图,不过需要使用pair<int,int>
这个类型来分别存储节点和边权
输入样例
n
个点,m
条边,每行输入三个整数a,b,c 表示a->b
连接一条边权为c
的无向边
4 3 1 2 3 2 3 2 3 4 2
C++
#include<bits/stdc++.h> using namespace std; typedef pair<int,int>PII; #define x first #define y second typedef long long ll; const int N=1E5+10; vector<PII>g[N]; int n,f[N]; void dfs(int u,int fa){ for(auto &[x,w]:g[u]){ if(x==fa)continue; dfs(x,u); //代码逻辑 } } int main(){ cin>>n; for(int i=1;i<n;i++){ int a,b,c; cin>>a>>b>>c; g[a].push_back({b,c}); g[b].push_back({a,c}); } return 0; }
Java
import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class Main { static List<int[]>[] g; static int n; static int[] f; public static void main(String[] args) { Scanner scanner = new Scanner(System.in); n = scanner.nextInt(); g = new ArrayList[n + 1]; for (int i = 1; i <= n; i++) { g[i] = new ArrayList<>(); } f = new int[n + 1]; for (int i = 1; i < n; i++) { int a = scanner.nextInt(); int b = scanner.nextInt(); int c = scanner.nextInt(); g[a].add(new int[]{b, c}); g[b].add(new int[]{a, c}); } } static void dfs(int u, int fa) { for (int[] pair : g[u]) { int x = pair[0]; int w = pair[1]; if (x == fa) continue; dfs(x, u); //代码逻辑 } } }
Python3
from collections import defaultdict n = int(input()) g = defaultdict(list) f = [0] * (n + 1) def dfs(u, fa): global f for x, w in g[u]: if x == fa: continue dfs(x, u) #代码逻辑 for _ in range(1, n): a, b, c = map(int, input().split()) g[a].append((b, c)) g[b].append((a, c))
离散化建图
一般用于点的范围较大(),或者含有负数的点,一般会使用离散化建图,或者有的不是点,而是字符串与字符串之间存在相互转换的关系,就可以使用哈希表这种数据结构来实现离散化建图。
C++
#include<bits/stdc++.h> using namespace std; int main() { unordered_map<int, vector<pair<int, int>>> path; // 添加节点 1 到节点 2 的边权为 3 path[1].push_back({2, 3}); // 遍历节点 1 的所有能到的点和边权 int node = 1; for (auto &entry : path[node]) { int target = entry.first; int weight = entry.second; // 进行处理 cout << "From node " << node << " to node " << target << " with weight " << weight << endl; } return 0; }
Java
import java.util.*; public class Main { public static void main(String[] args) { int N = 100010; Map<Integer, List<Map.Entry<Integer, Integer>>> path = new HashMap<>(); // 添加节点 u 到节点 v 的边权为 w int u = 1, v = 2, w = 3; if (!path.containsKey(u)) { path.put(u, new ArrayList<>()); } path.get(u).add(new AbstractMap.SimpleEntry<>(v, w)); // 遍历节点 u 的所有能到的点和边权 int node = 1; if (path.containsKey(node)) { for (Map.Entry<Integer, Integer> entry : path.get(node)) { int target = entry.getKey(); int weight = entry.getValue(); // 进行处理 } } } }
Python3
from collections import defaultdict n = int(input()) g = defaultdict(list) f = [0] * (n + 1) def dfs(u, fa): global f for x, w in g[u]: if x == fa: continue dfs(x, u) #代码逻辑 for _ in range(1, n): a, b, c = map(int, input().split()) g[a].append((b, c)) g[b].append((a, c))
真题讲解
下面讲解一道24秋招米哈游的笔试真题,来为大家去看一下笔试中图论题目是如何考察的,以及遇到这种题目,我们如何去处理。
题目描述
塔子哥有一个 个节点的树,树根编号为
。
塔子哥可以在叶子节点上添加一个新的儿子节点,添加后,添加的节点变成了新的叶子节点。
若干次操作后,塔子哥想问你距离树根不超过 的节点最多可以有多少个。
输入描述
第一行,一个正整数 表示树中节点个数,
表示不超过树根的距离,
接下来 行,每行输入两个整数
和
,表示节点
和
之间有一条边。
输出描述
一个整数,表示若干次操作后距离树根不超过 的节点最大数量。
样例
输入
4 2 1 2 1 3 1 4
输出
7
说明若干次操作后,最终的树如下,此时有 个点距离
号点的距离小于等于
思路:树形DFS+贡献法计数考虑每一个节点对答案的贡献:如果当前节点距离根节点的距离d有,则答案+1
此外,如果当前节点还是叶子节点,则说明还可以再叶子结点下添加个点,这些点可以构成一条链。
因此,我们只需要对以1号点为根的树进行DFS遍历,在遍历的过程中,可以计算出每一个节点到根节点的距离,然后根据上述的规则进行计算,累加答案即可。
根据上述方式统计计数即可,注意本题题目给的是无向边,需要建两条有向边。
C++
#include <bits/stdc++.h> using namespace std; int main() { int n, k; cin >> n >> k; // 建图 vector<vector<int>> g(n); for (int i = 1; i < n; ++i) { int u, v; cin >> u >> v; u--; v--; g[u].emplace_back(v); g[v].emplace_back(u); } const int INF = 0x3f3f3f3f; long long ans = 0; vector<int> dist(n, INF); // 计算1号点到每个点的距离,1号点到自己的距离为 0 dist[0] = 0; function<void(int,int)> dfs = [&](int u, int fa) { // 如果1号点到u+1点的距离 <= k,则答案加1 if (dist[u] <= k) { ans += 1; } // 如果1号点到叶子的距离 < k,则还可以再这个叶子下加 k - dist[u] 个 if (u != 0 && g[u].size() == 1 && dist[u] < k) { ans += k - dist[u]; } // 继续遍历子树 v for (int v: g[u]) { if (v == fa) continue; dist[v] = dist[u] + 1; dfs(v, u); } }; dfs(0, -1); cout << ans << "\n"; return 0; }
Java
import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class Main { static List<List<Integer>> g; static int[] dist; static int n, k; static long ans; public static void main(String[] args) { Scanner scanner = new Scanner(System.in); n = scanner.nextInt(); k = scanner.nextInt(); // 建图 g = new ArrayList<>(); for (int i = 0; i < n; i++) { g.add(new ArrayList<>()); } for (int i = 1; i < n; i++) { int u = scanner.nextInt() - 1; int v = scanner.nextInt() - 1; g.get(u).add(v); g.get(v).add(u); } final int INF = 0x3f3f3f3f; ans = 0; dist = new int[n]; for (int i = 0; i < n; i++) { dist[i] = INF; } // 计算1号点到每个点的距离,1号点到自己的距离为 0 dist[0] = 0; dfs(0, -1); System.out.println(ans); } static void dfs(int u, int fa) { // 如果1号点到u+1点的距离 <= k,则答案加1 if (dist[u] <= k) { ans++; } // 如果1号点到叶子的距离 < k,则还可以再这个叶子下加 k - dist[u] 个 if (u != 0 && g.get(u).size() == 1 && dist[u] < k) { ans += k - dist[u]; } // 继续遍历子树 v for (int v : g.get(u)) { if (v == fa) { continue; } dist[v] = dist[u] + 1; dfs(v, u); } } }
Python
def dfs(u, fa): global ans # 如果1号点到u+1点的距离 <= k,则答案加1 if dist[u] <= k: ans += 1 # 如果1号点到叶子的距离 < k,则还可以再这个叶子下加 k - dist[u] 个 if u != 0 and len(g[u]) == 1 and dist[u] < k: ans += k - dist[u] # 继续遍历子树 v for v in g[u]: if v == fa: continue dist[v] = dist[u] + 1 dfs(v, u) n, k = map(int, input().split()) # 建图 g = [[] for _ in range(n)] for _ in range(1, n): u, v = map(int, input().split()) u -= 1 v -= 1 g[u].append(v) g[v].append(u) INF = int(1e9) ans = 0 dist = [INF] * n # 计算1号点到每个点的距离,1号点到自己的距离为 0 dist[0] = 0 dfs(0, -1) print(ans)
以上内容均来自:笔试刷题指南
#秋招##春招##笔试##大厂##面经##悬赏#分享互联网大厂笔试面试经验,笔试面试常见套路和模版