0

0

C++如何使用STL容器实现图形数据结构

P粉602998670

P粉602998670

发布时间:2025-09-17 09:25:01

|

455人浏览过

|

来源于php中文网

原创

STL容器通过vector、map等提供高效内存管理,支持邻接矩阵(O(V²)空间)和邻接表(O(V+E)空间)实现图结构,前者适合稠密图且边查询O(1),后者节省稀疏图空间并优化遍历性能;带权图可用vector或自定义结构体存储权重,有向图仅单向添加边;BFS用queue、DFS用stack、Dijkstra用priority_queue结合vector实现高效算法操作。

c++如何使用stl容器实现图形数据结构

STL容器在C++中是实现图数据结构的核心工具,无论是选择邻接矩阵还是邻接表,

std::vector
std::map
std::unordered_map
都能提供高效且灵活的内存管理和访问机制,让图的构建和操作变得相对直接。

在C++中实现图数据结构,我们通常有两种主流思路:邻接矩阵(Adjacency Matrix)和邻接表(Adjacency List)。STL容器为这两种方法提供了强大的支持,让我们可以专注于图的逻辑而非底层的内存管理。

邻接矩阵

邻接矩阵是一个二维数组,

matrix[i][j]
的值表示节点
i
到节点
j
是否存在边,或者边的权重。在C++中,
std::vector>
是实现邻接矩阵最直观的方式。例如,对于一个包含N个节点的图,我们可以声明一个
N x N
vector

立即学习C++免费学习笔记(深入)”;

// 无权图的邻接矩阵
std::vector> adjMatrix(numNodes, std::vector(numNodes, false));

// 添加边 (u, v)
void addEdgeMatrix(int u, int v, std::vector>& matrix) {
    if (u >= 0 && u < matrix.size() && v >= 0 && v < matrix.size()) {
        matrix[u][v] = true;
        // 如果是无向图,还需要 matrix[v][u] = true;
    }
}

使用

std::vector>
是个不错的选择,因为
std::vector
有特化优化,能节省空间。

邻接表

邻接表则是更常用于稀疏图的表示方法。它是一个数组,数组的每个元素都是一个链表(或

vector
),存储与该节点相邻的所有节点。在C++中,
std::vector>
std::vector>
是典型的实现方式。

// 无权图的邻接表
std::vector> adjList(numNodes);

// 添加边 (u, v)
void addEdgeList(int u, int v, std::vector>& list) {
    if (u >= 0 && u < list.size() && v >= 0 && v < list.size()) {
        list[u].push_back(v);
        // 如果是无向图,还需要 list[v].push_back(u);
    }
}

对于带权图,邻接表可以存储

std::pair
(目标节点,权重)或自定义结构体。

在我看来,选择哪种实现方式,很大程度上取决于图的密度和我们主要进行的图操作。

邻接表与邻接矩阵:STL容器如何权衡性能与空间?

在图数据结构的选择上,邻接表和邻接矩阵各有千秋,而STL容器的运用直接影响了它们在性能和空间上的表现。在我日常处理图问题时,这往往是我首先会思考的问题。

邻接矩阵,用

std::vector>
std::vector>
实现时,它的空间复杂度是O(V^2),V是图中节点的数量。这意味着无论图中有多少边,它都会占用固定的V*V大小的空间。这对于节点数量不大的稠密图(边数接近V^2)来说非常高效,因为每个存储单元都被充分利用。但对于节点很多、边很少的稀疏图,大部分空间会是空的,造成显著的浪费。性能上,判断两个节点之间是否存在边是O(1)操作,这得益于数组的随机访问特性。添加或删除边也是O(1)。然而,遍历一个节点的所有邻居则可能需要O(V)时间,因为它需要扫描整个行。

邻接表,通常用

std::vector>
std::vector>
来实现,其空间复杂度是O(V+E),V是节点数,E是边数。这对于稀疏图来说是巨大的优势,它只存储实际存在的边,极大地节省了空间。例如,一个有1000个节点但只有2000条边的图,邻接表会比邻接矩阵节省很多内存。性能方面,添加边通常是O(1)(
push_back
vector
末尾)或O(logD)(如果用
std::set
来保证邻居唯一性并排序,D是该节点的度数)。遍历一个节点的所有邻居是O(D)操作,其中D是该节点的度数,这比邻接矩阵的O(V)通常要快得多。但是,判断两个节点之间是否存在边,如果只是简单
vector
,最坏情况需要O(D)来线性扫描,如果用
std::set
则可以达到O(logD)。

我个人觉得,在大多数实际应用中,尤其是处理大规模图时,邻接表因其更优的空间效率和在遍历邻居时的性能优势,往往是更受欢迎的选择。只有在V很小,或者需要频繁进行O(1)的边存在性检查时,邻接矩阵才会显得更有吸引力。

如何用STL容器实现带权图和有向图?

实现带权图和有向图,STL容器同样提供了非常灵活的方案,基本上是在前面无权无向图的基础上进行一些数据结构上的扩展。这并不是什么特别复杂的事情,更多的是一种设计上的选择。

带权图 (Weighted Graphs)

对于带权图,我们需要在表示边的同时,存储一个权重值。

BlackBox AI
BlackBox AI

AI编程助手,智能对话问答助手

下载
  • 邻接矩阵实现: 我们可以将

    std::vector>
    替换为
    std::vector>
    (或
    double
    等),其中
    matrix[u][v]
    存储的是边
    (u,v)
    的权重。为了表示两个节点之间没有边,我们需要约定一个特殊的“无限大”值(例如
    INT_MAX
    -1
    ),避免与实际权重混淆。

    // 带权图的邻接矩阵
    const int INF = std::numeric_limits::max();
    std::vector> weightedAdjMatrix(numNodes, std::vector(numNodes, INF));
    
    void addWeightedEdgeMatrix(int u, int v, int weight, std::vector>& matrix) {
        if (u >= 0 && u < matrix.size() && v >= 0 && v < matrix.size()) {
            matrix[u][v] = weight;
            // 如果是无向图,matrix[v][u] = weight;
        }
    }
  • 邻接表实现: 这是我个人觉得更优雅的方式。我们可以让邻接表存储

    std::pair
    ,其中
    first
    是目标节点,
    second
    是权重。或者,定义一个简单的结构体来封装这些信息,让代码更具可读性。

    // 带权图的邻接表
    struct Edge {
        int to;
        int weight;
    };
    std::vector> weightedAdjList(numNodes);
    
    void addWeightedEdgeList(int u, int v, int weight, std::vector>& list) {
        if (u >= 0 && u < list.size() && v >= 0 && v < list.size()) {
            list[u].push_back({v, weight});
            // 如果是无向图,list[v].push_back({u, weight});
        }
    }

    这种方式在遍历邻居时能直接获取权重,非常方便。

有向图 (Directed Graphs)

有向图的实现实际上比带权图更简单,它主要体现在边的添加逻辑上。

  • 邻接矩阵实现: 当添加一条从

    u
    v
    的有向边时,我们只设置
    matrix[u][v] = true
    (或权重),而不需要设置
    matrix[v][u]
    。这与无向图的区别仅在于
    addEdge
    函数内部的逻辑。

  • 邻接表实现: 同样,当添加一条从

    u
    v
    的有向边时,我们只在
    adjList[u]
    中添加
    v
    (或
    (v, weight)
    ),而不需要在
    adjList[v]
    中添加
    u

    // 有向带权图的邻接表(基于上面的Edge结构体)
    // std::vector> directedWeightedAdjList(numNodes);
    
    void addDirectedWeightedEdgeList(int u, int v, int weight, std::vector>& list) {
        if (u >= 0 && u < list.size() && v >= 0 && v < list.size()) {
            list[u].push_back({v, weight}); // 只添加一个方向
        }
    }

    所以说,有向图的实现更多的是一种约定,而不是对底层数据结构的大幅改动。

在图遍历算法中,STL容器如何提升效率?

图遍历算法,比如广度优先搜索(BFS)和深度优先搜索(DFS),以及一些最短路径算法(如Dijkstra),STL容器简直是它们的“最佳搭档”,极大简化了实现并提升了效率。

广度优先搜索 (BFS)

BFS的核心思想是层层推进,这天然需要一个队列来存储待访问的节点。

std::queue
就是为这个目的而生的。它提供了
push
front
pop
等O(1)操作,完美契合BFS的需求。

// BFS伪代码
std::queue q;
std::vector visited(numNodes, false); // 跟踪访问状态

q.push(startNode);
visited[startNode] = true;

while (!q.empty()) {
    int u = q.front();
    q.pop();
    // 处理节点 u

    // 遍历 u 的所有邻居
    for (int v : adjList[u]) { // adjList 是 std::vector>
        if (!visited[v]) {
            visited[v] = true;
            q.push(v);
        }
    }
}

这里,邻接表

adjList
std::vector
部分,使得遍历一个节点的所有邻居非常高效,因为它只迭代实际存在的边,而不是像邻接矩阵那样遍历整个行。
std::vector
作为
visited
数组,提供了O(1)的访问和修改效率。

深度优先搜索 (DFS)

DFS可以使用递归实现(利用函数调用栈),也可以显式使用

std::stack
std::stack
同样提供O(1)的
push
top
pop
操作。

// DFS显式栈实现伪代码
std::stack s;
std::vector visited(numNodes, false);

s.push(startNode);
visited[startNode] = true;

while (!s.empty()) {
    int u = s.top();
    s.pop();
    // 处理节点 u

    for (int v : adjList[u]) {
        if (!visited[v]) {
            visited[v] = true;
            s.push(v);
        }
    }
}

和BFS一样,

std::vector
作为邻接表和
visited
数组,都在这里扮演了关键角色。

Dijkstra算法 (最短路径)

Dijkstra算法需要不断选择当前距离源点最近的未访问节点,这正是优先队列

std::priority_queue
的拿手好戏。

// Dijkstra伪代码 (使用邻接表)
std::priority_queue, std::vector>, std::greater>> pq; // pair: {distance, node}
std::vector dist(numNodes, INF); // 存储最短距离

dist[startNode] = 0;
pq.push({0, startNode});

while (!pq.empty()) {
    int d = pq.top().first;
    int u = pq.top().second;
    pq.pop();

    if (d > dist[u]) continue; // 已经找到更短路径

    for (const auto& edge : weightedAdjList[u]) { // weightedAdjList 是 std::vector>
        int v = edge.to;
        int weight = edge.weight;
        if (dist[u] + weight < dist[v]) {
            dist[v] = dist[u] + weight;
            pq.push({dist[v], v});
        }
    }
}

std::priority_queue
在这里以O(logN)的复杂度提供了高效的提取最小元素操作,而
std::vector
作为
dist
数组和邻接表,则保证了O(1)的距离更新和高效的邻居遍历。

说实话,STL容器在图算法中的作用,我觉得就像是给算法装上了高效的齿轮。正确地选择和使用它们,不仅能让代码更简洁、可读,还能显著提升算法的运行效率,这对于处理大规模图数据来说至关重要。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

220

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

192

2025.07.04

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

463

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

544

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

93

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

200

2025.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

93

2025.08.29

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

102

2025.10.23

clawdbot ai使用教程 保姆级clawdbot部署安装手册
clawdbot ai使用教程 保姆级clawdbot部署安装手册

Clawdbot是一个“有灵魂”的AI助手,可以帮用户清空收件箱、发送电子邮件、管理日历、办理航班值机等等,并且可以接入用户常用的任何聊天APP,所有的操作均可通过WhatsApp、Telegram等平台完成,用户只需通过对话,就能操控设备自动执行各类任务。

8

2026.01.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 6.8万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 19.1万人学习

第二十二期_前端开发
第二十二期_前端开发

共119课时 | 12.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号