• 图的存储结构


    这个也就是怎么在内存中表示一张图

    第一种方式:采用邻接矩阵来表示一张图

            简单来说就是,采用二个数组来表示一张图

                    1.第一个数组:用一维数组来存储图中的顶点信息

                    2.第二个数组:用二维数组存储图中的边与弧的信息

            先来说无向图的邻接矩阵表示方法:

            

                    来说一下这个二维矩阵,横向与纵向都代表这个图中的顶点,这样做,有助于来分析两个顶点的关系,比如方向(v1,v2)这个就很符合二维数组arr[v1][v2]的一个表示,这个时候如果一张图是有向图,那么方向就是v1->v2,v1代表弧尾,v2代表弧头,也代表v1的出度,v2的入度,然后比如在一个图中,每一个顶点的编号是,不要说没有,你就算自己设置,也要设置为有编号 ,是从v0->v4,那么就表示在这个图中有四个顶点,然后我们就可以把v0设置为0这个位置,相对于后面的结点来说,v1就代表1,v2就代表2,依次类推

                    上面那个分段函数的表达意思就是,如果(v1,v2)这条边属于E(G),那么也就是说,v1与v2存在一条边,对于无向图来说,不用考虑顺序,只需要把(v1,v2)设置为一个数值就好了,比如就把这个邻接矩阵这个位置的值设为1,表示两者有联系 ,另外还要说一点的就是,因为邻接矩阵是二维数组,在这个数组中除了考虑(v1,v2)这个位置之外,还要考虑一个位置(v2,v1),这两个位置一个看横向,一个看纵向,因为对于无向图来说,这两个位置对应都是(v1,v2)这两个顶点的关系,所以,如果这两个顶点之间存在边,需要把这两个位置的值都设置为1。

            

             我们就来表示上面这张图的一种关系,下面直接上代码:

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. //最大顶点数
    5. #define MAX_VERTEX 30
    6. //定义图结构
    7. typedef struct _graph
    8. {
    9. //顶点数组,存储顶点名字
    10. char **vertex;
    11. //边的数组
    12. int edge[MAX_VERTEX][MAX_VERTEX];
    13. //顶点个数
    14. int vertex_num;
    15. //边的条数
    16. int edge_num;
    17. }graph;
    18. //算出用户输入的顶点在数组中的位置
    19. //这里就是比如输入一个v1,v2,那么对于无向图来说,(v1,v2)与(v2,v1)都代表的是同一条边
    20. int calc_vertex_pos(graph &g,char* v)
    21. {
    22. //遍历顶点数组
    23. for(int i = 0;i < g.vertex_num;i++) {
    24. //这里相当于就是字符串比较
    25. if(strcmp(v,g.vertex[i]) == 0) {
    26. return i;//找到直接返回这个位置
    27. }
    28. }
    29. return -1;
    30. }
    31. //创建一个图
    32. void create_graph(graph &g)
    33. {
    34. printf("请输入图的顶点数目与边数目:顶点 边\n");
    35. scanf("%d %d",&g.vertex_num,&g.edge_num);
    36. printf("请输入%d个顶点值\n",g.vertex_num);
    37. //开始循环给顶点数组赋顶点值
    38. //在赋值之前需要在堆上开辟空间存放字符串
    39. g.vertex = (char**)malloc(sizeof(char*) * g.vertex_num);
    40. for(int i = 0;i < g.vertex_num;i++) {
    41. char str[100] = {0};
    42. scanf("%s",str);
    43. //这个数组又指向了分配的一个空间
    44. g.vertex[i] = (char*)malloc(sizeof(char) * (strlen(str) + 1));
    45. strcpy(g.vertex[i],str);
    46. }
    47. //把边的二维数组也给初始化一下
    48. //以顶点数n,形成一个n*n的二维数组
    49. for(int i = 0;i < g.vertex_num;i++) {
    50. for(int j = 0;j < g.vertex_num;j++) {
    51. //把对应的边全部初始化为0表示不存在
    52. g.edge[i][j] = 0;
    53. }
    54. }
    55. //上面二个数组内容给初始化好了
    56. //下面来设置边与边的关系,就是是否有边
    57. printf("请输入%d条边和与之对应的顶点1,顶点2\n",g.edge_num);
    58. //设置两个有边的顶点之间的关系
    59. char v1[10];
    60. char v2[10];
    61. //有多少条边,就对应了有多少个顶点
    62. for(int i = 0;i < g.edge_num;i++) {
    63. scanf("%s %s",v1,v2);
    64. //然后我们计算顶点在数组中的位置
    65. //是0啊,1啊,还是2啊等等这样的关系
    66. int m = calc_vertex_pos(g,v1);//比如v1=0
    67. int n = calc_vertex_pos(g,v2);//v2 = 1 (0,1)就表示有边,就要把这个位置值设为1
    68. //同时(1,0)这个位置也要设置为1,毕竟是无向图
    69. g.edge[m][n] = 1;
    70. g.edge[n][m] = 1;
    71. }
    72. }
    73. //打印一张图
    74. void print_graph(graph& g)
    75. {
    76. //打印图也就是打印这个二维数组
    77. printf("\t");
    78. //循环水平的顶点表头
    79. for(int i = 0;i < g.vertex_num;i++) {
    80. printf("%s\t",g.vertex[i]);
    81. }
    82. //循环二维数组内容
    83. for(int i = 0;i < g.vertex_num;i++) {
    84. //打印水平的顶点表头
    85. printf("\n");//每次换一个行
    86. printf("%s\t",g.vertex[i]);
    87. //然后输出一行的内容
    88. for(int j = 0;j < g.vertex_num;j++) {
    89. printf("%d\t",g.edge[i][j]);
    90. }
    91. }
    92. printf("\t");
    93. }
    94. //构建图
    95. void test01()
    96. {
    97. graph g;
    98. create_graph(g);
    99. print_graph(g);
    100. }
    101. int main()
    102. {
    103. test01();
    104. return 0;
    105. }

    运行结果:

     下面说一下有向图,有向图,无非就是边带了方向对吧,比如对于无向图来说(v1,v2)和(v2,v1)其实啊都表示一个意思,什么意思呢,就代表这条边呗,那么有向图来说,如果这条边是v1->v2,也就是说v1表示狐尾,v2表示狐头,v1出度,v2入度,那么这个时候(v1,v2)与(v2,v1)就不是一个意思,(v1,v2)才能表示v1->v2,那么(v2,v1)这个坐标在邻接矩阵中怎么表示呢?这个位置其实我们就可以设置一个不可能的值,然后其他满足方向条件的,我们就设置一个权值,为什么你说方向不相关的位置为什么不设置为0,因为0也可能代表权值,一般权值就表示这两个点的距离啊,时间啊这些相关的一些东西。这就是有向图的设计思想,下面用一张图展示一下

    话不多说,直接上代码;

    directed_graph.cpp

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. #include <limits.h>
    5. //最大顶点数
    6. #define MAX_VERTEX 30
    7. //定义图结构
    8. typedef struct _graph
    9. {
    10. //顶点数组,存储顶点名字
    11. char **vertex;
    12. //边的数组
    13. int edge[MAX_VERTEX][MAX_VERTEX];
    14. //顶点个数
    15. int vertex_num;
    16. //边的条数
    17. int edge_num;
    18. }graph;
    19. //算出用户输入的顶点在数组中的位置
    20. //这里就是比如输入一个v1,v2,那么对于无向图来说,(v1,v2)与(v2,v1)都代表的是同一条边
    21. int calc_vertex_pos(graph &g,char* v)
    22. {
    23. //遍历顶点数组
    24. for(int i = 0;i < g.vertex_num;i++) {
    25. //这里相当于就是字符串比较
    26. if(strcmp(v,g.vertex[i]) == 0) {
    27. return i;//找到直接返回这个位置
    28. }
    29. }
    30. return -1;
    31. }
    32. //创建一个图
    33. void create_graph(graph &g)
    34. {
    35. printf("请输入图的顶点数目与边数目:顶点 边\n");
    36. scanf("%d %d",&g.vertex_num,&g.edge_num);
    37. printf("请输入%d个顶点值\n",g.vertex_num);
    38. //开始循环给顶点数组赋顶点值
    39. //在赋值之前需要在堆上开辟空间存放字符串
    40. g.vertex = (char**)malloc(sizeof(char*) * g.vertex_num);
    41. for(int i = 0;i < g.vertex_num;i++) {
    42. char str[100] = {0};
    43. scanf("%s",str);
    44. //这个数组又指向了分配的一个空间
    45. g.vertex[i] = (char*)malloc(sizeof(char) * (strlen(str) + 1));
    46. strcpy(g.vertex[i],str);
    47. }
    48. //把边的二维数组也给初始化一下
    49. //以顶点数n,形成一个n*n的二维数组
    50. for(int i = 0;i < g.vertex_num;i++) {
    51. for(int j = 0;j < g.vertex_num;j++) {
    52. //对于有向图来说,这些点应该初始化成一个不太可能的值
    53. g.edge[i][j] = INT_MAX;
    54. }
    55. }
    56. //上面二个数组内容给初始化好了
    57. //下面来设置边与边的关系,就是是否有边
    58. printf("请输入%d条边和与之对应的顶点1,顶点2\n",g.edge_num);
    59. //设置两个有边的顶点之间的关系
    60. char v1[10];
    61. char v2[10];
    62. //设置一个权值
    63. int weight;
    64. //有多少条边,就对应了有多少个顶点
    65. for(int i = 0;i < g.edge_num;i++) {
    66. scanf("%s %s %d",v1,v2,&weight);
    67. //然后我们计算顶点在数组中的位置
    68. //是0啊,1啊,还是2啊等等这样的关系
    69. int m = calc_vertex_pos(g,v1);//比如v1=0
    70. int n = calc_vertex_pos(g,v2);//v2 = 1 (0,1)就表示有边,就要把这个位置值设为1
    71. //现在这两个点是有方向的,所以都设置不可能啊,只有一个方向嘛
    72. g.edge[m][n] = weight;//直接变为权值
    73. }
    74. }
    75. //打印一张图
    76. void print_graph(graph& g)
    77. {
    78. //打印图也就是打印这个二维数组
    79. printf("\t");
    80. //循环水平的顶点表头
    81. for(int i = 0;i < g.vertex_num;i++) {
    82. printf("%s\t",g.vertex[i]);
    83. }
    84. //循环二维数组内容
    85. for(int i = 0;i < g.vertex_num;i++) {
    86. //打印水平的顶点表头
    87. printf("\n");//每次换一个行
    88. printf("%s\t",g.vertex[i]);
    89. //然后输出一行的内容
    90. for(int j = 0;j < g.vertex_num;j++) {
    91. printf("%d\t",g.edge[i][j]);
    92. }
    93. }
    94. printf("\t");
    95. }
    96. //构建图
    97. void test01()
    98. {
    99. graph g;
    100. create_graph(g);
    101. print_graph(g);
    102. }
    103. int main()
    104. {
    105. test01();
    106. return 0;
    107. }

     上面说了邻接矩阵的表示方法,这个方法呢,就是利用二维数组来做的,那么如果点特别多,边少的情况下,空间、时间是比较浪费的,所以,为了避免空间太过于浪费我们就可以用邻接表这样一种方法来做,它的意思就是类似于在内存上面开辟空间,就说当两个顶点之间有关系的时候,就开一个内存来指明这种关系,所以不会浪费空间,就是实现比较麻烦

    下面说一下用邻接表来实现无向图:

    先来分析一下数据结构:

            

    在来具体分析一下它的每一个顶点的插入思路:

             

    继续

     

     继续

     另外说一点;

    上无向图完整代码:

    undirected_graph1.cpp

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. //最多能存放的顶点数目
    5. #define MAX_VERTEX 100
    6. struct edge_node {
    7. int position;//这个表示vertex数组中的哪一个结点索引
    8. edge_node *next;//存放下一个邻接点的指针
    9. //这里权重可写可不写
    10. };
    11. struct vertex {
    12. char *name;//存放顶点名字,一级指针指向一个堆上面分配的空间
    13. //还有存放每一个与之相邻的第一个邻接点,其实这里它就相当于是头结点
    14. edge_node *first_node;
    15. };
    16. //最后需要整个图的结构
    17. //来存放相应图的信息
    18. struct graph {
    19. //存放顶点的所有信息
    20. vertex head[MAX_VERTEX];
    21. //顶点数与边数
    22. int vertex_num;
    23. int edge_num;
    24. };
    25. //这里还是来确定一下顶点位置
    26. int find_pos(graph &g,char *v)//这里v如果有指向就可以打印,我的意思就是scanf直接赋值是不行的
    27. {
    28. for (int i = 0; i < g.vertex_num; i++) {
    29. //循环存放顶点名字的那一片空间
    30. if (strcmp(g.head[i].name, v) == 0) {
    31. return i;
    32. }
    33. }
    34. return -1;
    35. }
    36. //先来创建一张图
    37. void create_graph(graph &g)
    38. {
    39. printf("请输入顶点数与边数:\n");
    40. scanf("%d %d", &g.vertex_num, &g.edge_num);
    41. //循环输入顶点的值
    42. printf("请输入%d个顶点值:\n", g.vertex_num);
    43. //循环给顶点赋值
    44. for (int i = 0; i < g.vertex_num; i++){
    45. char str[30] = { 0 };
    46. scanf("%s", str);
    47. //单纯scanf("%s",&g.head[i].name)肯定不行,这个name事一个二级指针
    48. g.head[i].name = (char*)malloc(sizeof(char) * (strlen(str) + 1));
    49. strcpy(g.head[i].name, str);//在哪一个索引位置存放顶点
    50. //除了赋值字符串之外,还需要初始化一个邻接点的地址
    51. g.head[i].first_node = NULL;//类似于每一个头顶点的next全是NULL
    52. }
    53. //下面开始来输入边,也就是两个顶点
    54. printf("请输入%d条边,顶点1,顶点2", g.edge_num);
    55. char v1[10];
    56. char v2[10];
    57. //循环给边赋值
    58. for (int i = 0; i < g.edge_num; i++) {
    59. scanf("%s %s", v1,v2);
    60. int m = find_pos(g, v1);
    61. int n = find_pos(g, v2);
    62. //在内存中找到了每个顶点的编号
    63. //然后比如说m是不是就是代表某个顶点的头部,这里可以
    64. //当成链表的头部处理
    65. //然后我们实现头插,表示某种联系
    66. //最后我们实现关系的一个交互,这是对于无向图来说v1与v2和v2与v1的关系一样
    67. //创建一个新的邻接点
    68. edge_node *p_new = (edge_node*)malloc(sizeof(edge_node));
    69. //然后开始在头部插入,这个头部也就是m点
    70. p_new->position = n;
    71. //这里p_new必须先指
    72. p_new->next = g.head[m].first_node;
    73. g.head[m].first_node = p_new;
    74. //然后实现v0与v1的一个交替,也就是一个意思
    75. edge_node *p_new1 = (edge_node*)malloc(sizeof(edge_node));
    76. //其实就是实现一个m与n的交替
    77. p_new1->position = m;
    78. p_new1->next = g.head[n].first_node;
    79. g.head[n].first_node = p_new1;
    80. }
    81. }
    82. //打印这张图
    83. void print_graph(graph &g)
    84. {
    85. for (int i = 0; i < g.vertex_num; i++) {
    86. printf("%s: ", g.head[i].name);
    87. //拿到头部结点进行一个单链表的遍历
    88. edge_node *p_node = g.head[i].first_node;
    89. while (p_node != NULL) {
    90. //拿到的是n,找它的m
    91. int index = p_node->position;
    92. printf("%s ", g.head[index].name);
    93. p_node = p_node->next;
    94. }
    95. //换行
    96. printf("\n");
    97. }
    98. }
    99. int main()
    100. {
    101. graph g;
    102. create_graph(g);
    103. print_graph(g);
    104. return 0;
    105. }

     运行结果:

       在来说有向图怎么来实现,比如下面这个有向图

         

            对于有向图来说,无非就是把上面代码需要处理反转的地方给删除,因为v0 v1与v1 v0已经不是一个意思了,直接上代码

            directed_graph1.cpp

            

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. //最多能存放的顶点数目
    5. #define MAX_VERTEX 100
    6. struct edge_node {
    7. int position;//这个表示vertex数组中的哪一个结点索引
    8. edge_node *next;//存放下一个邻接点的指针
    9. //这里权重可写可不写
    10. };
    11. struct vertex {
    12. char *name;//存放顶点名字,一级指针指向一个堆上面分配的空间
    13. //还有存放每一个与之相邻的第一个邻接点,其实这里它就相当于是头结点
    14. edge_node *first_node;
    15. };
    16. //最后需要整个图的结构
    17. //来存放相应图的信息
    18. struct graph {
    19. //存放顶点的所有信息
    20. vertex head[MAX_VERTEX];
    21. //顶点数与边数
    22. int vertex_num;
    23. int edge_num;
    24. };
    25. //这里还是来确定一下顶点位置
    26. int find_pos(graph &g,char *v)//这里v如果有指向就可以打印,我的意思就是scanf直接赋值是不行的
    27. {
    28. for (int i = 0; i < g.vertex_num; i++) {
    29. //循环存放顶点名字的那一片空间
    30. if (strcmp(g.head[i].name, v) == 0) {
    31. return i;
    32. }
    33. }
    34. return -1;
    35. }
    36. //先来创建一张图
    37. void create_graph(graph &g)
    38. {
    39. printf("请输入顶点数与边数:\n");
    40. scanf("%d %d", &g.vertex_num, &g.edge_num);
    41. //循环输入顶点的值
    42. printf("请输入%d个顶点值:\n", g.vertex_num);
    43. //循环给顶点赋值
    44. for (int i = 0; i < g.vertex_num; i++){
    45. char str[30] = { 0 };
    46. scanf("%s", str);
    47. //单纯scanf("%s",&g.head[i].name)肯定不行,这个name事一个二级指针
    48. g.head[i].name = (char*)malloc(sizeof(char) * (strlen(str) + 1));
    49. strcpy(g.head[i].name, str);//在哪一个索引位置存放顶点
    50. //除了赋值字符串之外,还需要初始化一个邻接点的地址
    51. g.head[i].first_node = NULL;//类似于每一个头顶点的next全是NULL
    52. }
    53. //下面开始来输入边,也就是两个顶点
    54. printf("请输入%d条边,顶点1,顶点2", g.edge_num);
    55. char v1[10];
    56. char v2[10];
    57. //循环给边赋值
    58. for (int i = 0; i < g.edge_num; i++) {
    59. scanf("%s %s", v1,v2);
    60. int m = find_pos(g, v1);
    61. int n = find_pos(g, v2);
    62. //在内存中找到了每个顶点的编号
    63. //然后比如说m是不是就是代表某个顶点的头部,这里可以
    64. //当成链表的头部处理
    65. //然后我们实现头插,表示某种联系
    66. //最后我们实现关系的一个交互,这是对于无向图来说v1与v2和v2与v1的关系一样
    67. //创建一个新的邻接点
    68. edge_node *p_new = (edge_node*)malloc(sizeof(edge_node));
    69. //然后开始在头部插入,这个头部也就是m点
    70. p_new->position = n;
    71. //这里p_new必须先指
    72. p_new->next = g.head[m].first_node;
    73. g.head[m].first_node = p_new;
    74. /*
    75. * 有向图把这个位置注释掉就行
    76. //然后实现v0与v1的一个交替,也就是一个意思
    77. edge_node *p_new1 = (edge_node*)malloc(sizeof(edge_node));
    78. //其实就是实现一个m与n的交替
    79. p_new1->position = m;
    80. p_new1->next = g.head[n].first_node;
    81. g.head[n].first_node = p_new1;
    82. */
    83. }
    84. }
    85. //打印这张图
    86. void print_graph(graph &g)
    87. {
    88. for (int i = 0; i < g.vertex_num; i++) {
    89. printf("%s: ", g.head[i].name);
    90. //拿到头部结点进行一个单链表的遍历
    91. edge_node *p_node = g.head[i].first_node;
    92. while (p_node != NULL) {
    93. //拿到的是n,找它的m
    94. int index = p_node->position;
    95. printf("%s ", g.head[index].name);
    96. p_node = p_node->next;
    97. }
    98. //换行
    99. printf("\n");
    100. }
    101. }
    102. int main()
    103. {
    104. graph g;
    105. create_graph(g);
    106. print_graph(g);
    107. return 0;
    108. }

    运行结果:

       有向图与无向图就都做完了。 

  • 相关阅读:
    cmmlu数据处理
    MyBatis基于XML的使用——动态sql
    【linux】基础IO+系统文件IO+文件描述符分配规则
    Leetcode—27.移除元素【简单】
    井冈山大学专属中秋月饼
    mysql left join查询慢
    天翎知识管理系统:强大的权限管理功能,保障知识安全
    html5+css简易实现图书网联系我们页面
    洛谷 P2763 试题库问题(最大流 EK算法)
    工具篇--分布式定时任务springBoot 整合 elasticjob使用(3)
  • 原文地址:https://blog.csdn.net/Pxx520Tangtian/article/details/125434097