图形结构的存储

本文深入探讨了图数据结构的两种常见存储形式——邻接矩阵和邻接表,详细解释了它们的原理、特点以及应用场景。包括如何通过邻接矩阵和邻接表表示图,以及它们在查找边、计算顶点度等方面的优势和局限。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

图是一种复杂的数据结构,任意两个顶点之间都有可能存在关系,这就构成了多对多的关系所以不能通过顶点保存在内存中的位置表示各顶点之间关系,但是可以用数组保存顶点信息,然后使用链式结构或二维数组保存顶点之间关系。

图的保存形式常用的两种是:邻接矩阵和邻接表。

1.邻接矩阵:

邻接矩阵是表示顶点之间相邻关系的矩阵,对于有N个顶点的图,使用两个数组来表示,其中一个是具有N个元素的一维数组,用来保存顶点的信息,另一个是n*n的二维数组(一个矩阵),保存顶点之间相邻信息。在该矩阵中,每一行对应图中一个顶点,每一列也对应一个顶点,对于矩阵元素A[i][j]的值,可以为1,也可以为0;其中可以规定:1表示(Vi,Vj)构成一条边,0表示(Vi,Vj)不构成一条边。在二维数组中,两个下标的值由各顶点在一维数组中的下标序号决定。

用下无向图,讨论用邻接矩阵保存的形式:



用一维数组V保存5个顶点的名:数组元素序号:1           2          3             4          5

12345

在V[1]中保存顶点1的信息,在V[2]中保存顶点2的信息。。

对于保存顶点之间相邻的信息的二维数组A,首先使所有元素请0,即各顶点没有边,然后对有边的两个顶点,在对应的矩阵元素中填1,如V1和V2有一条边,则在A[1][2]中填1,对于无向图V2到V1有边则也填入1 ,而对于有向图若V2到V1没有边,则填入0。

对于上图的邻接矩阵如下图:




采用邻接矩阵保存图,可以方便查找图中任一边或边的权,例如查边(Vi,Vj),只要查找邻接矩阵中第i行第j列的元素A[i][j],若该元素为0,表示没有边,否则有边;使用邻接矩阵存储图,还可以方便的得到每个顶点的度,对于无向图,每个顶点的度等于该顶点相应的行或列中非0元素的个数,对于有向图,每行非0的个数等于出度OD,每列非0元素个数等于该点的入度ID。

用邻接矩阵保存图:

创建邻接矩阵程序:"linjiejuzhen.c"

#define VERTEX_MAX 26   //图的最大顶点数   
#define MAXVALUE 32767 //最大值(可设为一个最大整数) 表示两个顶点之间不构成边
typedef struct
{
    char Vertex[VERTEX_MAX]; //保存顶点信息(序号或字母)
    int Edges[VERTEX_MAX][VERTEX_MAX]; //保存边的权 
    int isTrav[VERTEX_MAX]; //遍历标志,当某顶点访问过时,对应元素将设置为1 
    int VertexNum; //顶点数量 
    int EdgeNum;//边数量 
    int GraphType; //图的类型(0:无向图,1:有向图)    
}MatrixGraph; //定义邻接矩阵图结构 

void CreateMatrixGraph(MatrixGraph *G);//创建邻接矩阵图 
void OutMatrix(MatrixGraph *G); //输出邻接矩阵
定义顶点信息时,顶点信息可能是一个序号或者一个字母,所以定义为一个字符型,只能保存一个字母,所以将该常量设为26,

创建图的邻接矩阵函数,由用户输入各顶点,边,边的权值等信息:

void CreateMatrixGraph(MatrixGraph *G)//创建邻接矩阵图 
{
    int i,j,k,weight;
    char start,end; //边的起始顶点 
    printf("输入各顶点信息\n");
    for(i=0;i<G->VertexNum;i++) //输入顶点 
    {
        getchar();  //暂停输入
        printf("第%d个顶点:",i+1);
        scanf("%c",&(G->Vertex[i])); //保存到各顶点数组元素中 
    }
    printf("输入构成各边的两个顶点及权值(用逗号分隔):\n"); 
    for(k=0;k<G->EdgeNum;k++)  //输入边的信息 
    {
        getchar(); //暂停输入 
        printf("第%d条边:",k+1);
        scanf("%c,%c,%d",&start,&end,&weight);
        for(i=0;start!=G->Vertex[i];i++); //在已有顶点中查找始点 
        for(j=0;end!=G->Vertex[j];j++); //在已有顶点中查找结终点 
        G->Edges[i][j]=weight; //对应位置保存权值,表示有一条边
        if(G->GraphType==0)  //若是无向图
            G->Edges[j][i]=weight;//在对角位置保存权值  
    }
}

函数中参数G是一个指向前面定义的图结构指针,在主函数中先定义图的结构,然后将指针传入本函数。。在顶点数组Vertex中查找两个顶点对应的序号,用这两个序号定位邻接矩阵中的元素,然后将权值保存到邻接矩阵对应的数组元素中,如果是无向图,还将边的权值保存到对角位置

然后输出邻接矩阵内容:

void OutMatrix(MatrixGraph *G)//输出邻接矩阵 
{
    int i,j;
    for(j=0;j<G->VertexNum;j++)
        printf("\t%c",G->Vertex[j]);          //在第1行输出顶点信息 
    printf("\n");
    for(i=0;i<G->VertexNum;i++) 
    {
        printf("%c",G->Vertex[i]);
        for(j=0;j<G->VertexNum;j++)
        {
            if(G->Edges[i][j]==MAXVALUE) //若权值为最大值 
                printf("\t∞");          //输出无穷大符号 
            else
                printf("\t%d",G->Edges[i][j]); //输出边的权值 
        }
        printf("\n");
    }             
}

当两个顶点不构成边时,用无穷大表示没有这条边


最后测试上面创建的邻接矩阵保存的图:

#include <stdio.h>
#include "linjiejuzhen.c"
int main()
{
    MatrixGraph G; //定义保存邻接矩阵结构的图 
    int i,j;
    printf("输入生成图的类型(0:无向图,1:有向图):");
    scanf("%d",&G.GraphType); //图的种类
    printf("输入图的顶点数量和边数量:");
    scanf("%d,%d",&G.VertexNum,&G.EdgeNum); //输入图顶点数和边数 
    for(i=0;i<G.VertexNum;i++)  //清空矩阵 
        for(j=0;j<G.VertexNum;j++)
            G.Edges[i][j]=MAXVALUE; //设置矩阵中各元素的值为最大值         
    CreateMatrixGraph(&G); //创建用邻接表保存的图 
    printf("邻接矩阵数据如下:\n");
    OutMatrix(&G);
    getch();
    return 0;
}
对下带权无向图进行测试:


测试结果:



2.邻接表:

邻接表是由邻接矩阵改进来的链接结构,特点是只考虑邻接矩阵中的非0元素,即构成边的元素,因此比用邻接矩阵保存图更加节省存储空间。

邻接表是对图中每个顶点建立一个邻接关系的单链表,邻接矩阵中的每一行对应一个单链表,将每个单链表的表头指针保存到一个数组中,以方便随机访问任意一个顶点的链表。


对于上图所示无向图,顶点V1的邻接点有V2,V3,V5,在顶点V1的单链表中,这3个顶点的链接次序是任意的,并不像树结构中有次序要求,只是表示顶点V1有这3个邻接点,如下图是上图的邻接表:


在邻接表上,可以方便的找到任意顶点的第一个邻接点和下一个邻接点,但是在邻接表中不方便判断任意两个顶点之间是否有边,需要找到地i个或第j个邻接表才能判断。

创建邻接表:"linjiebiao.c"

#define VERTEX_MAX 20   //图的最大顶点数   
typedef struct edgeNode   
{
    int Vertex; //顶点信息(序号或字母) 
    int weight; //权值
    struct edgeNode *next; //指向下一个顶点指针 (当前顶点和指向的下一顶点构成一条边) 
}EdgeNode; //邻接表边结构 

typedef struct   
{
    EdgeNode *AdjList[VERTEX_MAX]; //指向每个顶点的指针
    int VextexNum,EdgeNum; //图的顶点的数量和边的数量  
    int GraphType; //图的类型(0:无向图,1:有向图)
}ListGraph;  //图的结构 

void CreateGraph(ListGraph *G); //生成图的邻接表   
void OutList(ListGraph *G); //输出邻接表
AdjList为一个数组,在该数组序号表示起始顶点的序号,数组元素为一个指针,指向与该顶点构成边的顶点,例如:第一个元素表示编号为1的顶点,而第一个数组元素指针指向顶点2,表示顶点1和顶点2构成一条边

下面是创建图的函数:

void CreateGraph(ListGraph *G)  //构造邻接表结构图
{
    int i,weight;
    int start,end;
    EdgeNode *s;
    for(i=1;i<=G->VextexNum;i++)//将图中各顶点指针清空 初始状态设置各顶点没有边
        G->AdjList[i]=NULL;
    for(i=1;i<=G->EdgeNum;i++) //输入各边的两个顶点 
    {
        getchar();
        printf("第%d条边:",i); 
        scanf("%d,%d,%d",&start,&end,&weight); //输入边的起点和终点
        s=(EdgeNode *)malloc(sizeof(EdgeNode)); //申请保存一个顶点的内存 
        s->next=G->AdjList[start]; //插入到邻接表中 
        s->Vertex=end; //保存终点编号
        s->weight=weight; //保存权值 
        G->AdjList[start]=s; //邻接表对应顶点指向该点 
        if(G->GraphType==0) //若是无向图,再插入到终点的边链中
        {
            s=(EdgeNode *)malloc(sizeof(EdgeNode)); //申请保存一个顶点的内存 
            s->next=G->AdjList[end];
            s->Vertex=start;
            s->weight=weight;
            G->AdjList[end]=s;
        }
    }   
} 
申请保存顶点的内存空间后,将新建顶点的next指针指向起始顶点原来指向的位置,然后使起始顶点指向刚创建的顶点。。在处理无向图中,因为无向图是双向路径,还需要创建一个顶点结构,将输入的起始顶点信息保存起来,并链接到结束顶点的链表中。

创建输出函数:

void OutList(ListGraph *G)
{
    int i;
    EdgeNode *s;
    for(i=1;i<=G->VextexNum;i++)
    {
        printf("顶点%d",i); 
        s=G->AdjList[i];
        while(s)
        {
            printf("->%d(%d)",s->Vertex,s->weight); 
            s=s->next;
        }
        printf("\n");
    }
}
输出时只需循环处理邻接表中各顶点即可。

最后测试上面创建邻接表的程序:

#include <stdio.h>
#include "linjiebiao.c"
int main()
{
    ListGraph G; //定义保存邻接表结构的图 
    printf("输入生成图的类型(0:无向图,1:有向图):");
    scanf("%d",&G.GraphType); //图的种类
    printf("输入图的顶点数量和边数量:");
    scanf("%d,%d",&G.VextexNum,&G.EdgeNum); //输入图顶点数和边数 
    printf("输入构成各边的两个顶点及权值(用逗号分隔):\n"); 
    CreateGraph(&G); //生成邻接表结构的图
    printf("输出图的邻接表:\n");
    OutList(&G); 
    getch();
    return 0;
}
同样以下图无向图做测试:



结果如下:







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值