邻接表与邻接矩阵

一、引言:图的表示方式

在图论算法中,高效地存储图结构是解决问题的关键。两种最常用的存储方式是邻接矩阵邻接表,它们各有优缺点,适用于不同的场景。本文将深入探讨这两种存储结构,帮助读者根据具体需求选择合适的表示方法。

二、邻接矩阵:直观的二维数组表示

1.基本概念

邻接矩阵是一个 V* V 的二维数组(V 为顶点数),其中:

  • 矩阵元素 matrix[i][j] = 1 表示顶点 i 和 j之间有边

  • matrix[i][j] = 0 表示无边

2.代码实现

#include <iostream>
using namespace std;

int main() {
    // 定义邻接矩阵(无向图)
    int neighbor[7][7] = {
        {0,0,0,0,0,0,0},
        {0,0,1,1,0,0,0},  // 顶点1连接2和3
        {0,1,0,1,0,1,0},  // 顶点2连接1、3和5
        {0,1,1,0,1,1,1},  // 顶点3连接1、2、4、5、6
        {0,0,0,1,0,0,0},  // 顶点4连接3
        {0,0,1,1,0,0,1},  // 顶点5连接3和6
        {0,0,0,1,0,1,0}   // 顶点6连接3和5
    };
    
    // 计算顶点n的邻居数量
    int n;
    cin >> n;
    int sum = 0;
    for(int i = 1; i <= 6; i++) {
        if(neighbor[n][i] == 1) {
            sum++;
        }
    }
    cout << "顶点" << n << "有" << sum << "个邻居";
    return 0;
}

3.特点分析

  1. 无向图:矩阵关于主对角线对称(matrix[i][j] == matrix[j][i]

  2. 有向图:非对称,matrix[i][j] 表示从 i到 j的边

  3. 空间复杂度:O(V^2)

  4. 查询效率:O(1) 判断任意两点是否相邻

4.优缺点

优点缺点
直观易懂空间占用大(O(V^2))
快速判断邻接关系添加/删除顶点效率低
方便计算度数不适合稀疏图(边数远小于V^2)

三、邻接表:高效的动态存储

1.基本概念

邻接表为每个顶点维护一个链表(或动态数组),存储该顶点的所有邻居:

  • 顶点 $i$ 的邻居列表包含所有与 $i$ 相邻的顶点

  • 无向图中每条边存储两次

  • 有向图中只存储出边或入边

2.代码实现

#include <iostream>
#include <vector>
using namespace std;

const int N = 100; // 最大顶点数

int main() {
    vector<int> neighbor[N]; // 邻接表
    
    // 构建无向图
    neighbor[1].push_back(2); // 1-2
    neighbor[2].push_back(1);
    
    neighbor[1].push_back(3); // 1-3
    neighbor[3].push_back(1);
    
    neighbor[2].push_back(5); // 2-5
    neighbor[5].push_back(2);
    
    // 计算顶点x的邻居数量
    int x;
    cin >> x;
    cout << "顶点" << x << "有" << neighbor[x].size() << "个邻居";
    return 0;
}

3.DFS遍历实现

#include <iostream>
#include <vector>
using namespace std;

vector<int> neighbor[8]; // 邻接表
bool visited[8];         // 访问标记数组

void dfs(int current) {
    cout << current << " ";    // 访问当前顶点
    visited[current] = true;   // 标记已访问
    
    // 递归访问所有未访问邻居
    for(int i = 0; i < neighbor[current].size(); i++) {
        int next = neighbor[current][i];
        if(!visited[next]) {
            dfs(next);
        }
    }
}

int main() {
    // 构建图结构(代码同前)
    // ...
    
    // 从顶点0开始DFS遍历
    dfs(0);
    return 0;
}

4.特点分析

  1. 空间复杂度:O(V + E)(E 为边数)

  2. 查询效率:O((v)) 获取顶点 v 的邻居

  3. 动态性:高效添加/删除边

5.优缺点

优点缺点
节省内存(稀疏图)判断两点是否相邻较慢
高效遍历邻接点实现相对复杂
易于添加/删除边存储有向图时需区分出/入边

四、’对比总结:如何选择存储结构

1.性能对比表

特性邻接矩阵邻接表
空间占用O(V^2)O(V + E)
检查邻接O(1)O(\text{degree}(v))
遍历邻居O(V)O(\text{degree}(v))
添加边O(1)O(1)
删除边O(1)O(\text{degree}(v))
添加顶点O(V^2)O(1)

2.适用场景建议

  1. 邻接矩阵适用场景

    • 稠密图(边数接近 V^2)

    • 需要频繁检查两点是否相邻

    • 图规模较小

    • 需要实现图论中的数学运算

  2. 邻接表适用场景

    • 稀疏图(边数远小于 V^2)

    • 需要遍历所有邻接点

    • 图规模较大

    • 需要动态添加/删除顶点

五、实战应用:DFS遍历比较

1.邻接矩阵DFS实现

int visited[8] = {};
int villager[8][8] = { 
    {0,1,0,1,1,0,0,0},
    {1,0,1,0,1,0,0,0},
    {0,1,0,0,0,1,0,0},
    {1,0,0,0,1,0,1,0},
    {1,1,0,0,0,0,1,0},
    {0,0,1,0,0,0,0,0},
    {0,0,0,1,1,0,0,1},
    {0,0,0,0,0,0,1,0}};

void dfs_matrix(int current) {
    visited[current] = 1;
    cout << current << " ";
    for(int j = 0; j < 8; j++) {
        if(villager[current][j] == 1 && visited[j] == 0)
            dfs_matrix(j);
    }
}

2.邻接表DFS实现

vector<int> neighbor[8];
bool visited[8];

void dfs_list(int current) {
    cout << current << " ";
    visited[current] = true;
    for(int j = 0; j < neighbor[current].size(); j++) {
        int next = neighbor[current][j];
        if(!visited[next])
            dfs_list(next);
    }
}

3.性能分析

在稠密图中,邻接矩阵的DFS效率更高;在稀疏图中,邻接表的DFS更优:

  • 邻接矩阵DFS:时间复杂度 O(V^2)

  • 邻接表DFS:时间复杂度 O(V + E)

六、总结与扩展

  1. 邻接矩阵适合稠密图小规模图

  2. 邻接表适合稀疏图大规模图

  3. 实际应用中常根据具体需求选择合适结构

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值