数据结构——图详解及代码实现

简介: 数据结构——图详解及代码实现

引言

大家都玩过微博和微信吧,微博的互关和微信互相添加好友,你知道如何存储这些关系吗?没错,就是我们今天要聊的数据结构“图”。

如何理解图

图跟二叉树一样是一种非线性的数据结构,但是比二叉树要复杂。二叉树中的元素我们称为节点,图中的元素我们称为“顶点”,如下图,图中的每个顶点都可以与其他顶点建立关系,我们把这种关系称为“”。

我们以微信举例,每个用户可以就可以看做一个顶点。如果两个用户添加好友,就建立一条边。每个用户有多少好友,该用户节点就有多少条边,对应图中的概念就是“”。这种关系跟微博中互相关注一样,但是,微博还允许单向关注,也就是A关注了B,但是B没有关注A。这种关系用有向图描述,如果用户A关注了用户B,我们就在图中画一条从A指向B的边,我们把这种有方向的图叫做“有向图”,类似,没有方向的叫“无向图”。

前面提到的“度”的概念,在有向图中,对应“入度”和“出度”。顶点的入度表示有多少个顶点指向它,顶点的出度表示有多少条从该顶点指出到其他顶点的边。对应到微博,入度就是你有多少粉丝,出度就是你关注了多少人。

不知道大家有没有留意过,QQ如果两个人聊天频繁,就会有个友谊的巨轮标识,QQ空间的亲密度同理,这种关系用“带权图”描述。带权图中的每条边都有一个权重,可以通过权重来标识两个用户的亲密度。

关于图的概念还有很多,这篇文章就先介绍这几个。对于图有了基本的认识之后,我们来看看,如何存储这种数据结构呢?

二、图的存储

2.1 领接矩阵

领接矩阵的顶层实际上是个二维数组。对于无向图来说,如果顶点A和顶点B有边,那么A[i][j]和A[j][i]都需要置为1;对于有向图,如果是顶点A指向顶点B,那么只需要将A[i][j]置为1。如果是带权图,对应的位置保存的是权重值。

领接矩阵的优点是直观、设计简单,基于数组,获取两个顶点的关系非常高效;其次,由于是基于二维数组,可以将图的很多运算转为矩阵之间的运算。缺点是浪费空间。对于无向图来说,如果顶点A和顶点B有边,那么A[i][j]和A[j][i]都需要置为1,实际上只需要让A[i][j]置为1就可以描述这个关系了,对角线的另一半空间就浪费了。而且大部分图都是稀疏图,空间浪费会更严重。

2.2 领接表

下图是一张有向图的领接表。领接表乍一看有点像哈希表,每个顶点对应数组的下标元素,每个元素对应一个链表,链表里存储的是这个顶点指向的其他的顶点的集合。

优点:节省空间,但是,确定顶点之间的关系就比较耗时。比如,想查看顶点2和顶点4是否存在一条边,就需要遍历顶点2对应的链表。

只使用领接表存储是不够的,因为查找某个用户关注了那些用户非常容易,但是,想要知道这个用户被那些用户关注,就没哪么容易了,此时需要逆领接表。

三、代码实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#define MAX_GRAPH_VERTEX (1 << 8)
struct vertex;
struct vertex_adjs {
  struct vertex *v;
  struct vertex_adjs *next;
};
struct vertex {
  int data;
  struct vertex_adjs *adj;
};
struct graph {
  struct vertex *vxs[MAX_GRAPH_VERTEX];
};
void InitGraph(struct graph *graph)
{
  for (int i = 0; i < MAX_GRAPH_VERTEX; i++){
    graph->vxs[i] = NULL;
  }
  return;
}
struct vertex* CreateVertex(int data)
{
  struct vertex *vtx;
  vtx = malloc(sizeof(struct vertex));
  if (!vtx) {
    printf("malloc struct vertex failed!\n");
    return NULL;
  }
  vtx->data = data;
  vtx->adj = NULL;
  return vtx;
}
struct vertex_adjs *CreateVertexAdj(struct vertex *v)
{
  struct vertex_adjs *v_adj;
  v_adj = malloc(sizeof(struct vertex_adjs));
  if (!v_adj){
    printf("malloc struct vertex_adjs failed!\n");
    return NULL;
  }
  v_adj->v = v;
  v_adj->next = NULL;
  return v_adj;
}
void InsertAdj(struct vertex *v, struct vertex *adj)
{
  struct vertex_adjs **v_adj;
  v_adj = &v->adj;
  while (*v_adj){
    v_adj = &(*v_adj)->next;
  }
  *v_adj = CreateVertexAdj(adj);
}
void printGraph(struct graph *graph)
{
  for (int i = 0; i < MAX_GRAPH_VERTEX; i++) {
    struct vertex *v = graph->vxs[i];
    struct vertex_adjs *adj;
    if (v == NULL)
      continue;
    printf("Vertex[%02d]: %4d --->", i, v->data);
    adj = v->adj;
    while (adj) {
      printf(" [%2d] ", adj->v->data);
      adj = adj->next;
    }
    printf("\n");
  }
  return ;
}
void test_graph(struct graph *graph)
{
  InitGraph(graph);
  for (int i = 0; i < 5; i++){
    graph->vxs[i] = CreateVertex(i+1);
  }
  /* connect 1 -> 2, 1 -> 4 */
  InsertAdj(graph->vxs[0], graph->vxs[1]);
  InsertAdj(graph->vxs[0], graph->vxs[3]);
  /* connect 2 -> 1, 2 -> 3, 2 -> 5, 2 -> 4 */
  InsertAdj(graph->vxs[1], graph->vxs[0]);
  InsertAdj(graph->vxs[1], graph->vxs[2]);
  InsertAdj(graph->vxs[1], graph->vxs[4]);
  InsertAdj(graph->vxs[1], graph->vxs[3]);
  /* connect 3 -> 2, 3 -> 5 */
  InsertAdj(graph->vxs[2], graph->vxs[1]);
  InsertAdj(graph->vxs[2], graph->vxs[4]);
  /* connect 4 -> 1, 4 -> 2, 4 -> 5 */
  InsertAdj(graph->vxs[3], graph->vxs[0]);
  InsertAdj(graph->vxs[3], graph->vxs[1]);
  InsertAdj(graph->vxs[3], graph->vxs[4]);
  /* connect 5 -> 4, 5 -> 2, 5 -> 3 */
  InsertAdj(graph->vxs[4], graph->vxs[3]);
  InsertAdj(graph->vxs[4], graph->vxs[1]);
  InsertAdj(graph->vxs[4], graph->vxs[3]);
  return ;
}
int main()
{
  struct graph *tg = (struct graph*)malloc(sizeof(struct graph));
    if (!tg){
        printf("malloc struct graph failed!\n");
        return -1;
    }
  test_graph(tg);
  printGraph(tg);
  return 0;
}

运行结果:

文章参考于<零声教育>的C/C++linux服务期高级架构

相关文章
|
3月前
|
算法 安全 大数据
揭秘!Python堆与优先队列:数据结构的秘密武器,让你的代码秒变高效战士!
【7月更文挑战第8天】Python的heapq模块和queue.PriorityQueue提供堆与优先队列功能,助你提升算法效率。堆用于快速找大数据集的第K大元素,如示例所示,时间复杂度O(n log k)。PriorityQueue在多线程中智能调度任务,如模拟下载管理器,按优先级处理任务。掌握这些工具,让代码运行更高效!
60 1
|
4月前
|
存储 算法
数据结构===图
数据结构===图
|
26天前
|
存储 算法 C语言
数据结构基础详解(C语言):单链表_定义_初始化_插入_删除_查找_建立操作_纯c语言代码注释讲解
本文详细介绍了单链表的理论知识,涵盖单链表的定义、优点与缺点,并通过示例代码讲解了单链表的初始化、插入、删除、查找等核心操作。文中还具体分析了按位序插入、指定节点前后插入、按位序删除及按值查找等算法实现,并提供了尾插法和头插法建立单链表的方法,帮助读者深入理解单链表的基本原理与应用技巧。
|
26天前
|
存储 C语言 C++
数据结构基础详解(C语言) 顺序表:顺序表静态分配和动态分配增删改查基本操作的基本介绍及c语言代码实现
本文介绍了顺序表的定义及其在C/C++中的实现方法。顺序表通过连续存储空间实现线性表,使逻辑上相邻的元素在物理位置上也相邻。文章详细描述了静态分配与动态分配两种方式下的顺序表定义、初始化、插入、删除、查找等基本操作,并提供了具体代码示例。静态分配方式下顺序表的长度固定,而动态分配则可根据需求调整大小。此外,还总结了顺序表的优点,如随机访问效率高、存储密度大,以及缺点,如扩展不便和插入删除操作成本高等特点。
|
26天前
|
存储 C语言
数据结构基础详解(C语言): 栈与队列的详解附完整代码
栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。
151 3
|
26天前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
3月前
|
算法 计算机视觉 开发者
燃爆全场!Python并查集:数据结构界的网红,让你的代码炫酷无比!
【7月更文挑战第16天】并查集,Python中的效率明星,处理不相交集合合并与查询。用于社交网络分析、图像处理、图论算法等领域。优雅实现结合路径压缩和按秩合并
30 1
|
3月前
|
算法 Python
逆袭之路!用 Python 玩转图的 DFS 与 BFS,让数据结构难题无处遁形
【7月更文挑战第12天】图的遍历利器:DFS 和 BFS。Python 中,图可表示为邻接表或矩阵。DFS 沿路径深入,回溯时遍历所有可达顶点,适合找路径和环。BFS 层次遍历,先近后远,解决最短路径问题。两者在迷宫、网络路由等场景各显神通。通过练习,掌握这些算法,图处理将游刃有余。
52 3
|
3月前
|
存储 算法 Python
“解锁Python高级数据结构新姿势:图的表示与遍历,让你的算法思维跃升新高度
【7月更文挑战第13天】Python中的图数据结构用于表示复杂关系,通过节点和边连接。常见的表示方法是邻接矩阵(适合稠密图)和邻接表(适合稀疏图)。图遍历包括DFS(深度优先搜索)和BFS(广度优先搜索):DFS深入探索分支,BFS逐层访问邻居。掌握这些技巧对优化算法和解决实际问题至关重要。**
28 1
|
4月前
|
存储 算法
【单向链表】数据结构——单向链表的介绍与代码实现&笔记
【单向链表】数据结构——单向链表的介绍与代码实现&笔记