数据结构图的总结

图中数据元素叫做顶点(vertex),如上图中的A、B、C等,顶点之间的连线叫做弧(arc),如<a,b>可以表示从A点到B点的弧,如果是A指向B的弧,那么A为弧尾或初始点,B为弧头或终端点,这种有弧方向的图是有向图(digraph),若两个顶点之间只是用有无连接关系表示的话,就可以用边来表示,此时的图是无向图(undigraph)。假若弧或边上带有权值信息,那么图就称为网,所以图也可以分为无向图、有向图、无向网和有向网四类。对于无向图,相邻的两个顶点互为邻接点,顶点A的度是指和顶点A相关联的边的数目。对于有向图,度分为出度和入度,A的出度就是以A为弧尾的弧的个数,A的入度就是以A为弧头的弧的个数,常见的图中名词有以上这些。
在这里插入图片描述

   图的表示法有数组法、邻接表法和十字链表法。当然这些只是常见的,具体的图的存储结构还要跟实际情况结合起来,可以组合搭配使用,
   不能拘泥于一种方法,领悟其精华,学会变通即可。下面具体讲一下他们的表示方法和创建图的过程。

图的数组表示法

   图的存储结构用数组表示的话,C语言版本的结构如下所示:

#define MAX_VERTEX_NUM 10 //最大顶点vertex数
#define MAX_NAME 5 //顶点向量字符最长数+1
#define MAX_INIT 65535 //对于网,将值设为无限大表示没有弧存在
typedef int VRType; //表示顶点的关系类型,对于无权图,有0和1,对网可以表示其权值
typedef char VertexType[MAX_NAME]; //顶点名字信息
typedef enum{DG, DN, UDG, UDN} GraphKind;//图的类型
typedef struct ArcCell{
VRType adj;
//这里可以添加一些其他信息,用来表示弧相关其他信息
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct {
VertexType vexs[MAX_VERTEX_NUM];//顶点向量,就是顶点的名字,可以用字符串表示
AdjMatrix arcs; //邻接矩阵
int vexnum,arcnum; //顶点数和弧数
GraphKind kind; //图的类型
}Graph;
上面都详细的描述了定义的结构体中各个的含义,其实用数组表示图最主要的就是那个矩阵,矩阵中存放着“权值”的信息,其他的就是顶点个数、弧个数、顶点名字等,用一个结构体也是可以的。创建图的话就要根据图的类型进行操作了,我主要创建了无向图和有向网,这两个可以的话剩下两个应该都不难。无向图的表示也最为简单,创建过程如下程序所示:

void CreateUDG(Graph *G)
{
int i, j, k;
VertexType va, vb; //临时变量,存储顶点A和B

printf("Input the number of the vertex and arc:\n");
scanf("%d %d",&G->vexnum, &G->arcnum);
printf("input the name of the vertex:\n");
for(i=0; i<G->vexnum; i++)
	scanf("%s",G->vexs[i]);
for (i = 0; i < G->vexnum; ++i)      // 初始化邻接矩阵  
    for (j = 0; j < G->vexnum; ++j)  
    {  
        G->arcs[i][j].adj  = 0;      // 无向图,所以都初始值为0  
    }  
//根据顶点向量的信息判断两点之间有无连接
for (k = 0; k < G->arcnum; ++k)  
{  
printf("\nInput the first vertex:\n");
    scanf("%s",va);
    i = Locate(G, va);               //找出va在图中的位置  
    printf("\nInput the last vertex:\n");
scanf("%s",vb);
j = Locate(G, vb);  
    G->arcs[i][j].adj = G->arcs[j][i].adj = 1;    // 无向图 ,两个顶点是一样的
}  
G->kind = UDG;  

}
上述程序就是创建无向图的过程,这个只是简单的示范,所以比较简单。对于有向网,稍微复杂一点,就是加了权值,定了弧的方向,实现方式如下所示:

void CreateDN(Graph *G){
int i,j,k;
VertexType va,vb;
printf(“Please input the vertexnum and the arcnum:\n”);
scanf("%d %d",&G->vexnum,&G->arcnum);
for(i=0;ivexnum;i++){
for(j=0;jvexnum;j++){
G->arcs[i][j].adj = MAX_INIT; //初始化为大数,表示达不到或者说没有A到B顶点的弧
}
}
printf("\nInput the name of the vertex:\n");
for(i=0;ivexnum;i++){
scanf("%s",G->vexs[i]);
}
for(k=0;karcnum;k++){
printf("\nInput the first vertex:\n");
scanf("%s",va);
i = Locate(G, va);
printf("\nInput the second vertex:\n");
scanf("%s",vb);
j = Locate(G, vb);
printf("\nInput the weight of the vector:\n");
VRType m;
scanf("%d",&m);
G->arcs[i][j].adj = m;
}
G->kind = DN;
}
需要指出的是,这两个创建过程都有用到locate函数,该函数是为了实现查找到顶点在图中的位置。具体实现如下:

int Locate(Graph *g, VertexType v){
int i;
for(i=0; ivexnum; i++){
if(strcmp(v, g->vexs[i]) == 0)
return i;
}
return -1;
}
具体的实现方法,可以参见我的源码:https://github.com/clarkzhang56/useful-data-structure/blob/master/Graph/graphwitharray.c

图的邻接表表示法

   邻接表是图的一种链式存储结构。邻接表中,对每个顶点建一个单链表,单链表中的结点表示依附于顶点的边或
   弧。每个结点包括三部分,该顶点的位置、所指向的下一个结点和弧相关的信息,比如权值。每个链表都有一个
   表头结点,表头结点包含指向链表的第一个结点的链域和存储顶点名的数据域,具体的结构如下所示:

#define MAX_VEXTEX 10
#define MAX_LEN 5
typedef enum{UDG,UDN,DG,DN} GraphKind;
typedef char VertexType[MAX_LEN];
typedef struct ArcNode{
int adjvex; //弧指向的顶点的位置信息
struct ArcNode *nextArc; //下一个结点
int weight; //权值,当然也可以是其他信息
}ArcNode;
typedef struct VNode{ //表头结点
VertexType data; //数据域,存储顶点名称
ArcNode *firstArc; //链域,指向第一个结点
}VNode,AdjList[MAX_VEXTEX];
typedef struct Graph{
int vextexnum,arcnum;
AdjList vertices;
GraphKind kind;
}Graph;

 具体的创建有向网的程序如下所示:

void CreateDN(Graph G){
printf(“Create the Digraph Net:\n”);
int i,j,k;
VertexType vf,vl;
printf(“Please input the vextexnum and the arcnum:\n”);
scanf("%d %d",&G->vextexnum,&G->arcnum);
printf(“Please input the name of the vextexnum:\n”);
for(i=0;ivextexnum;i++){ //初始化
scanf("%s",G->vertices[i].data);
G->vertices[i].firstArc = NULL;
}
for(k=0;karcnum;k++){
printf(“Please input the first vertex:\n”);
scanf("%s",vf);
printf(“Please input the last vertex:\n”);
scanf("%s",vl);
i = Locate(G,vf); //寻找顶点的位置,和数组表示图那里一样
j = Locate(G,vl);
if(i != -1 && j != -1){
ArcNode tmp = (ArcNode) malloc (sizeof(ArcNode));
tmp->adjvex = j;
printf(“Please input the weight:\n”);
scanf("%d",&tmp->weight);
/
以下实现方法比较方便,易懂还方便 */
tmp->nextArc = G->vertices[i].firstArc;
G->vertices[i].firstArc = tmp;
}
}
G->kind = DN;
}
有向网就创建成功了。

图的十字链表表示法

   虽然数组和邻接表都可以作为图的存储结构,但是它们都有一些弊端:数组表示法要浪费很多的空间;邻接表可以容易查找到某一顶点的出度,但是很难找到该顶点的入度。为了解决这个问题,可以使用十字链表。十字链表其实就是邻接表的升级版。它只针对有向图(因为无向图的话没有入度-_-!),每一条弧都有一个结点,对应每一个顶点也有一个结点,弧结点包括:弧头、弧尾、同一弧头的弧、同一弧尾的弧和弧信息(比如权值),顶点结点包括:顶点名字、以该顶点为弧头的第一个结点和以该顶点为弧尾的第一个结点。具体结构定义如下所示:

#define MAX_NUM 5
#define MAX_VEX_NUM 20
typedef char VexType[MAX_NUM];
typedef struct AcrBox{
int tailvex,headvex; //弧尾和弧头的位置
struct AcrBox *hlink; //相同弧头的下一结点
struct AcrBox *tlink;
int weight;
}ArcBox;
typedef struct VexNode{
VexType data;
ArcBox *firstin; //指向以该顶点为弧头的第一个结点
ArcBox *firstout;
}VexNode;
typedef struct {
VexNode xlist[MAX_VEX_NUM];
int vexnum, arcnum;
}GraphDN;
以有向网为例子,因为有向网的稍微复杂一点,创建有向网的难点也在于增加结点的部分,所以这里也采用了和邻接表法相同的方法,即加在头部而不是尾部的方法。具体创建有向网的过程如下:

void CreateDNgraph(GraphDN *G){
int i,j,k;
VexType tail,head;
printf(“Creating the Diagraph Net with orthogonal list…\n”);
printf(“Input the num of the vextex and arc:\n”);
scanf("%d %d",&G->vexnum, &G->arcnum);
printf(“Input the vextex name:\n”);
for(i=0; ivexnum; i++){ //初始化
scanf("%s",G->xlist[i].data);
G->xlist[i].firstin = G->xlist[i].firstout = NULL;
}
for(k=0; karcnum; k++){
printf(“Input the tailvex name:\n”);
scanf("%s",tail);
printf(“Input the headvex name:\n”);
scanf("%s",head);
i = Locate(G, tail);
j = Locate(G, head);
if(i != -1 && j != -1){
ArcBox *arcbox = (ArcBox )malloc(sizeof(ArcBox));
arcbox->tailvex = i;
arcbox->headvex = j;
printf(“Input the weight:\n”);
scanf("%d",&arcbox->weight);
/
难点和精华,搞清楚头和尾很重要 */
arcbox->tlink = G->xlist[i].firstout;
G->xlist[i].firstout = arcbox;
arcbox->hlink = G->xlist[j].firstin;
G->xlist[j].firstin = arcbox;
}else{
printf(“Not the vextex name.\n”);
–k;
}
}
}

图的遍历

   图的遍历有深度优先搜索(Deepth first search)和广度优先搜索(Breadth first search),二者效率是一样的,时间复杂度为O(n+e)。

深度优先搜索

   顾名思义,就是一步一步先搜“深”的,再回过头来搜“浅”的,这就要用到迭代了。搜索就是遍历整个图,把所有顶点遍历完,这就需要先定义一个数组,用来表示该顶点是否访问过。如果没访问过,就进行搜索,可以通过打印顶点名字表示搜索了,搜索后还要把该顶点表示为搜索过了。数组表示法有向网的深度优先搜索如下所示:

#define true 1
#define false 0
typedef int bool;
bool visited[MAX_VERTEX_NUM];
void DFS(Graph *g, int v){
int i;
visited[v] = true;
printf("%s ",g->vexs[v]);
for(i=0; ivexnum; i++){
if(g->arcs[v][i].adj != 65535 && visited[i] == false) //为最大数表示没有弧存在
DFS(g, i);
}
}
void DFSgraph(Graph *G){
int i;
for(i=0; ivexnum; i++){
visited[i] = false;
}
for(i=0; ivexnum; i++){
if(!visited[i]) DFS(G, i);
}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值