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

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

引言

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

如何理解图

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

我们以微信举例,每个用户可以就可以看做一个顶点。如果两个用户添加好友,就建立一条边。每个用户有多少好友,该用户节点就有多少条边,对应图中的概念就是“”。这种关系跟微博中互相关注一样,但是,微博还允许单向关注,也就是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服务期高级架构

相关文章
|
1月前
|
算法 开发者 计算机视觉
燃爆全场!Python并查集:数据结构界的网红,让你的代码炫酷无比!
在编程的世界里,总有一些数据结构以其独特的魅力和高效的性能脱颖而出,成为众多开发者追捧的“网红”。今天,我们要介绍的这位明星,就是Python中的并查集(Union-Find)——它不仅在解决特定问题上大放异彩,更以其优雅的设计和强大的功能,让你的代码炫酷无比,燃爆全场!
36 0
|
4月前
|
算法 安全 大数据
揭秘!Python堆与优先队列:数据结构的秘密武器,让你的代码秒变高效战士!
【7月更文挑战第8天】Python的heapq模块和queue.PriorityQueue提供堆与优先队列功能,助你提升算法效率。堆用于快速找大数据集的第K大元素,如示例所示,时间复杂度O(n log k)。PriorityQueue在多线程中智能调度任务,如模拟下载管理器,按优先级处理任务。掌握这些工具,让代码运行更高效!
71 1
|
24天前
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
26 1
|
5月前
|
存储 算法
数据结构===图
数据结构===图
|
1月前
|
存储 算法 索引
HashMap底层数据结构及其增put删remove查get方法的代码实现原理
HashMap 是基于数组 + 链表 + 红黑树实现的高效键值对存储结构。默认初始容量为16,负载因子为0.75。当存储元素超过容量 * 负载因子时,会进行扩容。HashMap 使用哈希算法计算键的索引位置,通过链表或红黑树解决哈希冲突,确保高效存取。插入、获取和删除操作的时间复杂度接近 O(1)。
29 0
|
1月前
05(数据结构考研)树相关操作代码
05(数据结构考研)树相关操作代码
27 0
|
1月前
|
算法
04(数据结构考研)串相关操作代码
04(数据结构考研)串相关操作代码
18 0
|
1月前
03(数据结构考研)队列相关操作代码
03(数据结构考研)队列相关操作代码
39 0
|
1月前
02(数据结构考研)栈相关操作代码
02(数据结构考研)栈相关操作代码
12 0
|
1月前
01(数据结构考研)线性表相关操作代码
01(数据结构考研)线性表相关操作代码
59 0