数据结构(17.1)图之邻接矩阵存储

前言

图是一种比线性表和树更复杂的数据结构。

图中的数据元素称为顶点,并且在图中,是不允许没有顶点存在的:图是由顶点的有穷非空集合和顶点之间边的集合组成。

顶点与顶点之间的逻辑关系用边来表示,图中任何的两个顶点都可能存在边,边的集合允许为空。

假如顶点与顶点之间的边没有方向,则称其为无向边,用“()”表示;反之则为有向边,用"<>"表示。

当图中所有的边都为无向边时,称该图为无向图;反之为有向图。

我们主要实现的是无向图。

图的存储结构

在之前的数据结构中,都可以有两种不同的存储结构:顺序存储于链式存储。由于图的结构比较复杂,任意两个顶点都可能存在关系,因此无法用数据元素在储存区中的物理位置来表示元素之间的关系。这就说明,用顺序存储结构来存储图很困难,但是可以使用数组来记录顶点与顶点之间的关系。

图的链式存储结构中,由于结点的设计有不同,会有不同的存储结构,常用的有邻接表、十字链表和邻接多重表。

邻接矩阵(数组表示法)

图中顶点与顶点之间的关系(即边的状态)可以通过矩阵来表示:

img_3

但是,二维数组只能记录边的状态,却没有记录顶点本身。因此在图的结点设计中,还需要一个列表来记录顶点本身。同时,需要记录一下图的最大顶点数量和当前顶点数量、边的数量,结点设计如下:

typedef struct GraphMtx{
   
    //最大的顶点数
    int MaxVertices;
    //现有的顶点数
    int NumVertices;
    //现有的边数
    int NumEdges;
    
    //顶点列表
    char* VerticesList;
    //边
    int** Edge;
}GraphMtx;
邻接表

在邻接表存储中,每个顶点的存储类似于一个有头结点的单链表。

首先,设计一个顶点结点,其数据域存储顶点的数据,指针域指向它的边结点列表;这就类似于单链表的头结点。

在边结点中,数据域存储这条边所指向的顶点的位置(在数组中为下标),指针域指向本顶点的下一个边结点;这实际上就是一个单链表。

最后,所有的顶点需要一个列表来存储,多使用一维数组。

img_7

因此,我们需要设计三个结构体,分别来表示顶点结点、边结点和图本身。

//边的结构
typedef struct Edge{
   
    //顶点的下标
    int dest;
    //下一条边
    struct Edge* Link;
}Edge;

//顶点的结构
typedef struct Vertex{
   
    //数据域-顶点的信息
    char data;
    //指针域-指向边
    Edge *adj;
}Vertex;

//图
typedef struct GraphLnk{
   
    //最大顶点数量
    int MaxVertices;
    //当前顶点数量
    int NumVertices;
    //当前边的数量
    int NumEdges;
    //顶点的列表
    Vertex *NodeTable;
}GraphLnk;
十字链表

十字链表是用于存储有向图的一种链式结构。在有向图中,边分为出边和入边;因此在顶点结点的设计中会有两个指针域,分别指向本顶点的入边列表和出边列表;同样,在边结点中,有两个数据域,分别存储边的头部和尾部的位置(在数组中即下标),也有两个指针域,分别指向本顶点的下一条入边和下一条出边。

img_8

//边结点
typedef struct Edge{
   
  //弧尾
  int tail;
  //弧头
  int head;
  //下一条入边
  int hLink;
  //下一条出边
  int tLink;
}Edge;

//顶点结点
typedef struct Vertex{
   
    //数据域-顶点的信息
    char data;
    //入边列表
    Edge *EdgeIn;
    //出边列表
  	Edge *EdgeOut;
}Vertex;

//图
typedef struct GraphLnk{
   
    //最大顶点数量
    int MaxVertices;
    //当前顶点数量
    int NumVertices;
    //当前边的数量
    int NumEdges;
    //顶点的列表
    Vertex *NodeTable;
}GraphLnk;
邻接多重表

十字链表是用于存储无向图的一种链式结构。观察邻接表可以发现,若使用邻接表来存储无向图,每一条边会生成两个边结点,分别存储到两个顶点的边链表中,这样给边的删除带来麻烦。因此,可以考虑只生成一个边结点,让两个顶点同时指向这条边。这样,边的结点就类似于十字链表的结点,有两个数据域和两个指针域。而顶点结点结构不变。

//边顶点
typedef struct Edge{
   
  //第一个顶点i的位置
  int Idest;
  //第二个顶点j的位置
  int Jdest;
  
  //下一个i顶点的边
  struct Edge* ILink;
  //下一个j顶点的边
  struct Edge* JLink;  
}Edge;

//顶点结点
typedef struct Vertex{
   
    //数据域-顶点的信息
    char data;
    //指针域-指向边
    Edge *adj;
}Vertex;

//图
typedef struct GraphLnk{
   
    //最大顶点数量
    int MaxVertices;
    //当前顶点数量
    int NumVertices;
    //当前边的数量
    int NumEdges;
    //顶点的列表
    Vertex *NodeTable;
}GraphLnk;

img_9img_10
我们主要讲的是邻接矩阵和邻接表。

图的初始化

初始化主要包括两个部分:一个是对数据域进行初始化,另一部分是对需要的空间进行开辟。

//初始化
void InitGraph(GraphMtx *g){
   
    //数据初始化
    g->MaxVertices = DEFAULT_VERTEX_SIZE;
    g->NumVertices = g->NumEdges = 0;
    
    //开辟储存顶点的空间
    g->VerticesList = (T*)malloc(sizeof(T) * g->MaxVertices);
    assert(g->VerticesList != NULL);
    
    //开辟储存边的空间
    g->Edge = (int **)malloc(sizeof(int*) * g->MaxVertices);
    assert(g->Edge != NULL);
    for (int i = 0; i < g->MaxVertices; i ++) {
   
        g->Edge[i] = (int *)malloc(sizeof(int) * g->MaxVertices);
        assert(g->Edge[i] != NULL);
    }
    
    //初始化边
    for (int i = 0; i < g->MaxVertices; i ++) {
   
        for (int j = 0; j < g->MaxVertices; j ++) {
   
            g->Edge[i][j] = 0;
        }
    }
}

数据初始化没什么好说的,主要讲一下空间的开辟。这里开辟了两个空间,一个列表用于存放顶点,一个矩阵用于储存边;如图所示,它们实质上是一个一维数组和一个二维数组。

img_4
img_5

顶点和边的插入

插入线即在两个顶点之间插入一条边,这要求先有顶点,因此需要实现插入顶点的方法。

插入顶点

插入顶点非常简单,已知储存顶点的列表实际上是一个一维数组,直接将数据存入即可。

void InsertVertex(GraphMtx *g,T v){
   
    if (g->NumVertices == g->MaxVertices) {
   
        printf("顶点已满,无法插入\n");
        return;
    }
    g->VerticesList[g->NumVertices ++] = v;
}
插入边

插入边时,由于矩阵本身只表示边,它并不知道哪一行哪一列表示的是哪个顶点,因此我们需要去获取顶点在矩阵中的位置。顶点在矩阵中的位置就是它在顶点列表中的下标,我们写一个方法去获取。

//获取顶点位置
int GetVertexPos(Gra
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值