图的存储结构再总结

来源:互联网 发布:中韩农产品贸易数据 编辑:程序博客网 时间:2024/06/05 17:23

图的邻接矩阵存储在Prim算法这部分有提及。

http://blog.csdn.net/u011240016/article/details/52414818

//使用邻接矩阵存储#include<stdio.h>#include<stdlib.h>#define MAX_VERTEX_NUM 20#define INF 10000 // 表示顶点之间不可达#define FALSE 0#define TRUE 1typedef int bool;typedef struct ArcCell //若info用不到,可以直接定义AdjMatrix[20][20],这里用最难的方式实现{    int adj;//存储边之间的权值    // int info; //这里用不到} ArcCell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];// 定义图typedef struct{    int vexs[MAX_VERTEX_NUM];//顶点向量,存储顶点内容    AdjMatrix arcs;//存储顶点之间的关系    int vexnum, arcnum;//图的顶点数和边数}MGraph;int LocateElem(MGraph *G, int c){    for(int i = 0; i < G->vexnum;i++)    {        if(G->vexs[i] == c)            return i;    }    return MAX_VERTEX_NUM; //返回一个矩阵中不用的下标数字即可}void CreateGraph(MGraph *G)//构造图{    printf("请输入顶点数,边数:\n");    scanf("%d%d",&G->vexnum,&G->arcnum);//输入图的顶点数目和边数目    // printf("请输入顶点内容,顶点数为%d\n",G->vexnum);    for(int i = 0; i < G->vexnum; i++)//初始化图的顶点    {        // printf("输入结点%d: ",i);        scanf("%d",&G->vexs[i]); //输入顶点信息:a,b,c,d...,假设顶点值各不相同    }    printf("结点内容输入完毕!---\n");    //初始化邻接矩阵    for(int i = 0; i < G->vexnum; i++)    {        for(int j = 0; j < G->vexnum; j++)        {            G->arcs[i][j].adj = INF;//设置顶点间的距离都是INF        }    }    //构造邻接矩阵    printf("请输入边:\n");    for(int k = 0; k < G->arcnum; k++)    {        // printf("请输入边:\n");        int u,v;//u,v分别为顶点存储内容        int w; //w是边的权值        scanf("%d %d %d",&u,&v,&w); //输入边        // getchar();        //根据顶点内容找到结点的下标--在一维数组中寻找        int i = LocateElem(G,u);        int j = LocateElem(G,v);        G->arcs[i][j].adj = w;        G->arcs[j][i].adj = w; //无向,所以对称边也赋值    }}

简单总结就是用矩阵存储边的信息,一般一个int型数据就可以了,但是我们用了ArcCell这样的结构体去存储,不仅包含边的权值(假设0,1也称之为权值好了),利于以后的扩展。

需要打消的误区是,用了结构体好像马上就要用malloc,马上需要调出指针来一番堆存储了,这些不是必须的,而且,即使需要用到堆,那也是应该感到很自然的事情。

说到这里,开始感觉到为什么CS叫Math in Computer.
为什么数据结构如此重要了。

你空有数学的解法,转移不到计算机中来,那么算法是学不好的。
所以你需要能够灵活使用各种数据结构。

可以想象,最初是没有数据结构这种称呼的,只是在解决问题的过程中,人类发现了其中的pattern,我们总是擅长发现各种事物时间的pattern不是吗?

我知道开始很多人和我一样,学到一个高级的数据结构,觉得很了不得,以前学的那些基本数据结构都弱爆了什么的,要是问题都能用这些高级的数据结构解决,算法从根子上就比人家的 “高级”。比如最初学习图的存储,我觉得用邻接表就比邻接矩阵高级,因为邻接矩阵看着太像二维数组“而已”,邻接表呢,必须用到指针,成链,非顺序存储等等,这种狭义的偏见。

如果你也和我一样曾经有过甚至现在还有,我希望你也能和我一样尝试着深呼吸,然后仔细审查题目,思考,这些数据结构是什么。

不妨从结构体开始。

在邻接矩阵的边的关系中,我们用的是权值来表示,按理说用个数字就够了,我们偏偏搞出个大动作,非要存储结构体。那么结构体和一个数字有什么大的区别吗?

并没有。

是不是我们也只在结构体中存了一个数字而已,所以,把结构体看做一个盒子就可以了。

你想光秃秃的用个数字,我非要给它加个包装盒,还起了一个听着就知道什么东西的名字ArcCell,好像专门设计存放在矩阵中的一个一个格子中的。

一旦你吸收了这种思想,你会深深爱上结构体。

所以,不仅仅是只有邻接表,链表这种需要结点的地方才用结构体,只要你想把数据包装一下,尽情用好了!

而在单链表,邻接表这种非线性存储结构中,使用结构体+指针则是必备武器了。

好了,开始总结邻接表的设计。

我假设你已经知道:

设计思路:针对每一个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点vi的边(对于有向图,则是以顶点vi结尾的弧)。

单链表中结点称之为表结点:有三个域。

  • 邻接点域(adjvex):指示与顶点vi邻接的点在图中的位置,所以是int型
  • 链域(next arc):指示下一条边或弧的结点
  • 数据域(info) :存储和边或者弧相关的信息,如权值等,这个可以不用。

头结点有两个域:

  • 链域(firstarc):指向链表中的第一个结点
  • 数据域(data): 存储顶点vi的名或者其他有关信息
// --------图的邻接存储表示#define MAX_VERTEX_NUM 20typedef struct ArcNode{    int adjvex; //该弧指向的顶点在顶点表中的位置    struct ArcNode *nextarc; //指向下一条弧的指针    InfoType *info; //该弧相关信息的指针};typedef struct VNode{    VertexType data; //顶点信息    ArcNode *firstarc; // 指向第一条依附于该顶点的弧的指针} VNode,AdjList[MAX_VERTEX_NUM];typedef struct {    AdjList vertices;    int vexnum,arcnum; //图的当前顶点数和弧数    int kind;//图的种类标志} ALGraph;

吃透细节以后,才发现这个定义的模板是很漂亮的。

我努力以一种人说的话讲出我的理解。

首先,为什么设计这种邻接表的结构?
你看在二叉树里面,我们只需要定义一种标准的结点形式,以后只需要针对结点里面的指针进行串联或者删除链接,更改链接等等操作即可。为什么在树可以,而在图不可以?到底是怎样的不同让他们变得如此迥异?
以我的理解,答案是:

我们常用的树的结点指向的结点数目是可预知的。
比如二叉树,一个结点最多指向下两个结点,左右孩子嘛。

图呢?不行。图的结点一个可以连向多少个其他结点?不知道。

因此,树中的存储策略,到这里就不行了。

但是核心仍然是结点的存储思路。

这么一想,你就明白,设计出邻接表这种结构的同学,也是挺酷的–居然可以描述图的结构。

那么看是怎么做到的。
首先,我们不看结点之间的关系。那么,一个结点承载的信息就和树没有什么大不同。所以,把这些朴素的家伙放在一个数组里。为啥这样做?因为顺序存储有很好的随机存取的属性嘛!同时,下面会说到,存储结点之间关系下标也是很重要的。
这样的结点我们称之为头结点

typedef struct VNode{    VertexType data; //顶点信息    ArcNode *firstarc; // 指向第一条依附于该顶点的弧的指针} VNode,AdjList[MAX_VERTEX_NUM];

真是奇奇怪怪的名字。
对了,data属性为什么用VertexType,这是啥?哈哈,严奶奶是一个很酷的人,她示范我们,做事情要做的漂亮,比如data的类型,现在存的是int型,突然改了,想变成char型呢?所以在头部定义一个:

`typedef int VertexType;`

int变char就是一行代码的事情:

`typedef int VertexType;`

对了,这是不是叫泛型的设计思路?

所以,都是为了更好的偷懒,才发明了更好的解法。
因此:

懒是人类的希望之光。
Laziness is the light of the future.

呵呵哒。

好的,还是回到正题上,既然结点设么设计,还这么简洁,data域不用多讲。为什么每个结点还伸出去个钩子?钩向一个看起来叫ArcNode类型的未知的东西!

脑海中有没有这样一个画面,在浴室中的挂毛巾的挂钩那一排钩子,对应着这个结点里的指针。挂钩用来挂一些常用的毛巾,那么这个突出的指针想挂着什么?

那么得探讨表结点的设计了。

邻接表存储的边的信息全靠单链表抗着呢。

无向图

在无向图中,顶点表(就是那个存储数据类型为VNode的顺序表啊)的每一个元素,通过挂钩挂着一个与此元素相连的结点,这个结点呢再往后挂一个结点,挂的是什么样的结点呢,还是与顺序表中的元素相连的结点。所以一个元素挂成了一个单链表。这个单链表表示的是与顺序表中的这个元素相邻的边。
因此,有多少个头就有多少个单链表。这下知道头结点的来历了吧!头结点指向的第一个结点,怎么开心怎么选择。

有向图

有向图的话,单链表的结点是顺序表中指向的结点。其他的没有什么不同。

typedef struct ArcNode{    int adjvex; //该弧指向的顶点在顶点表中的位置    struct ArcNode *nextarc; //指向下一条弧的指针    InfoType *info; //该弧相关信息的指针};

adjvex指向在顺序表中的数组下标。
nextarc指向下一个结点。

自顶向下的思考,自底向上的实现。
最终,我们需要的数据结构是有Node组装的图:

typedef struct {    AdjList vertices;    int vexnum,arcnum; //图的当前顶点数和弧数    int kind;//图的种类标志} ALGraph;

同样,这里有结点数目,边数目,还有头结点的顺序表。
问:怎么不关心单链表?答案是,你知道有多少单链表的结点么?
所以要动态生成,也即每次malloc一个ArcNode,并通过顺序表的那个钩子,钩起来!

所以生成过程应是怎样的?

了解一只青蛙的最好方式是自己造一个青蛙。

HOPE.

1 0
原创粉丝点击