🚇

图是什么:邻接矩阵

地铁图就是一张「图」:站点是节点,线路是边。编程里用一张 n×n 的 0/1 大表记录「谁和谁直接相连」:g[i][j] = 1 表示 i 到 j 有直达边——这就是邻接矩阵,图的「座位图」。

💡 打个比方

把城市地铁图想象成一张座位图:每个站点是一个座位号(节点),两条站之间的直达线路是一条边。双向可走的线(比如友谊:你好友我、我好友你)是无向边;单行道(比如「关注」:我关注你,你没关注我)是有向边。一个节点连着几条边叫它的度——像站点的换乘线路数、一个人的好友数。邻接矩阵就是 n×n 的大表:第 i 行第 j 格 = 1 表示「i 到 j 有直达边」,否则 0。无向图里「i→j 通」必然「j→i 通」,所以矩阵对称(g[i][j] = g[j][i])——加一条边要写两个格子,一行里的 1 的个数正好就是这个节点的度。

术语:节点/顶点(vertex,点)、边(edge,线)、有向/无向(单向/双向)、度(相连边数)、入度(进来的边)、出度(出去的边)、邻接矩阵(n×n 的 0/1 表,g[i][j]=1 ⇔ i→j 有边)、稠密图/稀疏图(边多/边少)。

🖼️ 大图看明白

图 = 节点 + 边;邻接矩阵 = n×n「座位图」(g[i][j] = 1 ⇔ i 到 j 有直达边) 边(线路) 节点 = 站点 A B C D E 一条边 ↔ 表里 2 个格子(对称) X Y 无向边:双向(友谊相互) X Y 有向边:单行(关注单向) A B C D E A B C D E 0 1 0 0 0 1 0 1 1 0 0 1 0 0 0 0 0 0 1 1 0 0 0 1 0 第 i 行 1 的个数 = 节点 i 的度 deg:A=1 B=3 C=1 D=2 E=1 无向图 → 矩阵对称(g[i][j] = g[j][i]),1 的总数 = 2 × 边数

🧩 子任务 1:节点与边、有向/无向、度

🎯 小目标:分清节点/边、有向/无向;会数度(无向)和入度/出度(有向),并记住「度数之和 = 2 × 边数」。
// 图(graph)= 节点(vertex,点)的集合 + 边(edge,线)的集合
// - 无向边:两端平等,双向可走(友谊、双向道路)
// - 有向边:有方向 i → j,不能反过来(关注、单行道)
// 度(无向图):与节点直接相连的边数(好友数、线路数)
// 入度/出度(有向边):进来的边数 / 出去的边数(被关注数 / 关注数)
//
// 例子(地铁图,5 站,无向):边 A-B、B-C、B-D、D-E(4 条边,5 个节点)
//   A — B — C
//       |
//       D
//       |
//       E
// 度:deg(A)=1,deg(B)=3,deg(C)=1,deg(D)=2,deg(E)=1
// 规律:所有节点度数之和 = 2 × 边数(每条边有两个端点,各记 1 次)
//   本例:1+3+1+2+1 = 8 = 2 × 4 ✓
✅ 小检查:① 为什么「度数之和 = 2 × 边数」?② 有向图里加一条边 C → D:C 的出度、D 的入度各怎么变?③ n 个节点两两相连(完全图,无向)共有几条边?
看看答案 ① 数度时每条边的两个端点各被记 1 次 → 每条边对总和贡献 2 → 总和 = 2m(m 是边数)。 ② C 的出度 +1(多了一条出去的边),D 的入度 +1(多了一条进来的边);C 的入度、D 的出度不变。 ③ n(n-1)/2——每个节点连其他 n-1 个,每条边被两个节点各数一次,除以 2。n=5 时是 10 条边。
⚠️ 易错:度数只数直接相连的边,不是「能走到的节点数」。A 到 C 要走 A→B→C 中转,C 对 A 的度不加 1。

🧩 子任务 2:邻接矩阵——n×n 的 0/1 大表

🎯 小目标:会建 n 个节点的邻接矩阵:g[i][j] = 1 ⇔ i 到 j 有直达边;记住无向图「对称、一行两格、行和 = 度」。
// 邻接矩阵:n×n 的表 g[1..n][1..n]
//   g[i][j] = 1:i 到 j 有「直达」边
//   g[i][j] = 0:没有直达边
// 规则:
//   - 无向图:加边 i-j → g[i][j] 和 g[j][i] 都置 1(矩阵对称)
//   - 有向图:加边 i→j → 只置 g[i][j](矩阵不一定对称)
//   - 第 i 行 1 的个数 = i 的出度(有向)/ 度(无向)
//   - 第 j 列 1 的个数 = j 的入度(有向)
//   - 主对角线 g[i][i] = 自环(节点自己连自己,一般图为 0)
//
// 例子(地铁图 A-B、B-C、B-D、D-E):
//       A B C D E
//    A [ 0 1 0 0 0 ]  行和 1 = deg(A)
//    B [ 1 0 1 1 0 ]  行和 3 = deg(B)
//    C [ 0 1 0 0 0 ]  行和 1 = deg(C)
//    D [ 0 1 0 0 1 ]  行和 2 = deg(D)
//    E [ 0 0 0 1 0 ]  行和 1 = deg(E)
// 对称性:g[i][j] = g[j][i](如 g[A][B] = g[B][A] = 1)
✅ 小检查:① 为什么无向图的邻接矩阵一定对称?② g[i][i] 表示什么?什么时候它是 1?③ 上例矩阵里 1 的总数是多少?和边数什么关系?
看看答案 ① 无向边 i-j 双向可走:「i 到 j 通」必然「j 到 i 通」→ g[i][j] 与 g[j][i] 同生同灭(同 1 或同 0)。 ② 自环(节点到自己的边)。一般无向简单图为 0;图里允许「自己关注自己」这类边时才是 1。 ③ 8 个 1 = 2 × 4 条边——每条无向边占两个格子(对称位置各一个)。
⚠️ 易错:无向图加边要写两个格子(g[i][j] = g[j][i] = 1)。只写一个,矩阵就不对称了,「行和 = 度」也失效。

🧩 子任务 3:手推建图——5 人朋友圈的邻接矩阵

🎯 小目标:给定 5 个人的好友关系,手填完整邻接矩阵;用「对称、对角 0、1 总数 = 2×边数」三条自查。
// 5 人朋友圈(无向图):1=小明 2=小花 3=小强 4=小芳 5=小科
// 好友关系:小明-小花,小明-小强,小花-小芳,小芳-小科
// 边:1-2、1-3、2-4、4-5(共 4 条)
//
// 逐条加边(无向:对称两格都写 1):
// 边 1-2:g[1][2] = g[2][1] = 1
// 边 1-3:g[1][3] = g[3][1] = 1
// 边 2-4:g[2][4] = g[4][2] = 1
// 边 4-5:g[4][5] = g[5][4] = 1
//
//       1 2 3 4 5
//    1 [ 0 1 1 0 0 ]  deg(1) = 2
//    2 [ 1 0 0 1 0 ]  deg(2) = 2
//    3 [ 1 0 0 0 0 ]  deg(3) = 1
//    4 [ 0 1 0 0 1 ]  deg(4) = 2
//    5 [ 0 0 0 1 0 ]  deg(5) = 1
// 自查:1 的总数 = 8 = 2 × 4 条边 ✓;对称 ✓;主对角全 0 ✓
// 小明和小科是好友吗?g[1][5] = 0 → 不是直接好友,
// 但能经中转到达:1 → 2 → 4 → 5(「直达」和「可达」是两回事!)
✅ 小检查:① 小明和小科是「直接好友」吗?能「相互到达」吗?② 若新增好友关系「小强—小科」,矩阵哪些格子变化?度数怎么变?③ 只靠邻接矩阵一眼,能判断「i 到 j 是否可达(可中转)」吗?
看看答案 ① 不是直接好友(g[1][5] = 0);能相互到达:1→2→4→5 有路径(无向图里可达是相互的)。 ② g[3][5] 和 g[5][3] 变成 1(一条无向边 = 两个格子);deg(3) = 2、deg(5) = 2,其余不变。 ③ 不能一眼看出——矩阵只记录「直达」。可达性要沿边一路搜下去(DFS/BFS,后面两课的主角),搜到 j 才算可达。
⚠️ 易错:把「可达」当「直达」。g[i][j] = 0 只说明没有直达边,不代表 i 到 j 走不到——可达性靠遍历算法回答。

🧩 子任务 4:空间 O(n²) 的账——稠密合适,稀疏浪费

🎯 小目标:会算矩阵的空间开销,理解「稠密图适合矩阵、稀疏图浪费」,知道邻接表是稀疏图的正解(下节)。
// 算账(int 矩阵,1 个 int = 4 字节,存 n² 格):
//   n = 100:   100×100 = 1 万格 × 4B = 40 KB      (轻松)
//   n = 1000:  100 万格 × 4B = 4 MB               (可接受)
//   n = 5000:  2500 万格 × 4B = 100 MB             (紧张)
//   n = 10000: 1 亿格 × 4B = 400 MB                (爆炸)
//
// 稠密图(边多,接近 n(n-1)/2 条上界):矩阵大部分格子是 1,没浪费 → 适合
// 稀疏图(边少,比如只有 n-1 条的链):n² 格中只有 2(n-1) 个 1 → 大量 0 白占地方
//   例:n = 1000、m = 10 条边 → 100 万格里只有 20 个 1(0.002%)
//
// 权衡:
//   - 矩阵:查「i 到 j 有没有直达边」O(1)(直接看 g[i][j]);空间 O(n²)
//   - 邻接表(下节):只存边,空间 O(n + m);查边要扫 i 的链表 O(度)
//   结论:n 不大或图稠密 → 矩阵;n 大且边稀疏 → 邻接表
✅ 小检查:① n = 2000、只有 30 条边:矩阵要多少字节(int)?其中几个 1?② 用矩阵查「i 到 j 有没有边」要几步?用邻接表呢?③ n = 10000 时用 int 矩阵,至少多少 MB?
看看答案 ① 2000² × 4 = 16 MB;1 的个数 = 30 × 2 = 60(占比 0.0015%)。 ② 矩阵:O(1),看一眼 g[i][j] 即可;邻接表:要扫 i 的相邻链表,O(deg(i)),最坏 O(n)。 ③ 10000² × 4 = 400 MB——一般题目内存限制(如 256 MB)都扛不住,得用邻接表。
⚠️ 易错:别无脑开大矩阵——先看 n 和边的密度。n ≥ 10⁴ 的稀疏图,int g[10000][10000] 直接爆内存;反之 n ≤ 1000 的稠密图,矩阵查边 O(1) 又快又简单。

💻 完整代码:邻接矩阵建无向图 + 输出每个节点的度

// 题目:读入 n 个节点、m 条无向边,用邻接矩阵建图,输出每个节点的度(行和)。
// 输入:第 1 行 n m;接下来 m 行每行 u v(u 与 v 之间有一条无向边)。
#include <iostream>
using namespace std;

int g[1005][1005];   // 邻接矩阵:g[i][j] = 1 表示 i 到 j 有直达边

int main() {
    int n, m;
    cin >> n >> m;
    for (int k = 1; k <= m; k++) {
        int u, v;
        cin >> u >> v;
        g[u][v] = 1;    // 无向图:对称两格都要写
        g[v][u] = 1;
    }

    // 每个节点的度 = 对应行的 1 的个数
    for (int i = 1; i <= n; i++) {
        int deg = 0;
        for (int j = 1; j <= n; j++) deg += g[i][j];
        cout << i << " " << deg << endl;
    }
    return 0;
}

// 样例:n=5, m=4;边:1 2 / 1 3 / 2 4 / 4 5
// 输出:1 2 / 2 2 / 3 1 / 4 2 / 5 1(度 = 行和,总和 8 = 2 × 4 ✓)

✋ 你来试试

第 1 题:地铁图(A-B、B-C、B-D、D-E)里 g[B][E] = ?B 和 E 是直达吗? g[B][E] = 0,不是直达——但要经 D 中转(B→D→E)。「直达看矩阵,可达靠遍历」。
第 2 题:m 条边的无向图,邻接矩阵里 1 的总数 = ? 2m——每条无向边占对称的两个格子。(有向图则是 m 个 1,只占一个格子。)
第 3 题:有向图 n=4,边:1→2、1→3、2→3。节点 3 的入度、出度各是多少? 入度 2,出度 0——1→3 和 2→3 都「进」3,3 没有「出去」的边。矩阵里:第 3 列(进)有两个 1(第 1、2 行),第 3 行(出)全 0——有向图「行和 = 出度、列和 = 入度」。
第 4 题(加餐):完全图 K₅(5 个节点两两相连)有几条边?矩阵里 1 的总数 = ? 10 条边(5×4/2);20 个 1。矩阵除主对角线外全 1。度数全是 4,度数之和 20 = 2×10 ✓。
🎯 一句话记住:图 = 节点 + 边;邻接矩阵 g[i][j] = 1 ⇔ i 到 j 有直达边;无向图对称(一条边写两格、行和 = 度、1 总数 = 2×边数);矩阵空间 O(n²)——稠密合适、稀疏浪费(稀疏图下节课用邻接表)。