数据结构与算法基础-学习-23-图之邻接矩阵与邻接表

目录

一、定义和术语

二、存储结构

1、邻接矩阵

1.1、邻接矩阵优点

1.2、邻接矩阵缺点

2、邻接表

3、邻接矩阵和邻接表的区别和用途

3.1、区别

3.2、用途

三、宏定义

四、结构体定义

1、邻接矩阵

2、邻接表

3、网数据类型(造测试数据)

五、函数定义

1、使用邻接矩阵创建无向网

2、使用邻接表创建无向网

3、销毁使用邻接矩阵创建的无向网

4、销毁使用邻接表创建的无向网

六、Linux环境编译测试


一、定义和术语

名词描述
Graph = ( Vertex , Edge )
Vertex:顶点(数据元素)的有穷非空集合。
Edge:边的有穷集合。
无向图每条边都是无方向的。
有向图每条边都是有方向的。
完全图任意两个点都有一条边相连。
无向完全图:n个顶点,n * (n - 1) / 2条边。
有向完全图:n个顶点,n * (n - 1)条边。
稀疏图有很少的边或弧的图。e < nlog2(n)
稠密图有较多的边或弧的图。e >= nlog2(n)
边/弧带权的图。
邻接有边/弧相连的两个顶点之间的关系。
存在(vi, vj),则称vi和vj互为邻接点。
存在<vi, vj>,则称vi邻接到vj,vj邻接到vi。
关联(依附)边/弧与顶点之间的关系。
存在(vi, vj) / <vi, vj>,则称该边/弧关联于vi和vj。
顶点的度于该顶点相关联的边的数目,记作TD(v),在有向图中,顶点的度等于该顶点的入度于出度之和。
顶点v的入度是以v为终点的有向边的条数,记作ID(v)。
顶点v的出度是以v为始点的有向边的条数,记作OD(v)。
有向树当有向图中仅一个顶点的入度为0,其余顶点的入度为1.
路径接续的边构成的顶点序列。
路径长度路径上边或弧的数目/权值之和。
回路(环)第一个顶点和最后一个顶点相同路径。
简单路径除路径起点和终点可以相同外,其余顶点均不相同的路径。
简单回路(简单环)除路径起点和终点相同外,其余顶点均不相同的路径。
连通图(强连通图)在无(有)向图G = (V, {E})中,若对任何两个顶点v,u都存在从v到u的路径,则称G是连通图(强连通图)。
权与网图中边或弧所具有的相关数称为权。表明从一个顶点到另一个顶点的距离或耗费。
带权的图称为网。
子图设有两个图G = (V, {E})、G1 = (V1, {E1}),V1∈V,E1∈E,则称G1是G的子图。
连通分量(强连通分量)无向图G的极大连通子图称为G的连通分量。
极大连通子图该子图是G的连通子图,将G的任何不在该子图中的顶点加入,子图不再是连通。
强连通分量有向图G的极大强连通子图称为G的强连通分量。
极大强连通子图该子图是G的强连通子图,将G的任何不在该子图中的顶点加入,子图不再是强连通。
极小连通子图该子图是G的连通子图,在该子图中删除任何一条边,子图不再连通。。
生成树包含无向图G所有顶点的极小连通子图。
生成森林对非连通图,由各个连通分量的生成树的集合。

二、存储结构

图的逻辑结构是多对多的,具体分类如下:

分类名描述
数组存储结构1、邻接矩阵。(本文介绍)
链式存储结构1、邻接表。(本文介绍)
2、邻接多重表。
3、十字链表。

 这里以无向网为例,我们要画一个这样的。

测试数据:

举例说明一下{0,3,10},A->D且权值为10。

{'A','B','C','D','E'};
{{0,3,10},{0,1,20},{0,2,30},{2,3,10},{1,4,50},{3,4,20}};

1、邻接矩阵

建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间关系)。

生成的邻接矩阵如下:

VertexArray    : [A ,B ,C ,D ,E ]
ArcArray       :
[32767 ,20    ,30    ,10    ,32767 ]
[20    ,32767 ,32767 ,32767 ,50    ]
[30    ,32767 ,32767 ,10    ,32767 ]
[10    ,32767 ,10    ,32767 ,20    ]
[32767 ,50    ,32767 ,20    ,32767 ]

[32767 ,20    ,30    ,10    ,32767 ]第一行表示:A->A不通,A->B通,A->C通,A->D通,A->E不通。

特性1:无向网的邻接矩阵是对称的。

特性2:顶点i的度等于第i行中非极大值(32767)的个数。

特别:完全网的邻接矩阵中,对角元素为32767,其余皆为非32767。

1.1、邻接矩阵优点

(1)直观好理解。

(2)容易判断两个顶点间是否存在边或弧。

(3)容易查找任意顶点的所有邻接点。

(4)方便计算任意顶点的度。

1.2、邻接矩阵缺点

(1)添加和删除顶点不方便。

(2)存储稀疏图时浪费空间。

(3)统计稀疏图一共有多少条边浪费时间。

2、邻接表

顶点:按编号顺序将顶点数据存储在一维数组中。

关联同一顶点的边(以顶点为尾的弧):用线性链表存储。

生成的邻接表如下:

A : [ (2, 30, 0x15ac8b0),(1, 20, 0x15ac870),(3, 10, (nil))]
B : [ (4, 50, 0x15ac8d0),(0, 20, (nil))]
C : [ (3, 10, 0x15ac910),(0, 30, (nil))]
D : [ (4, 20, 0x15ac950),(2, 10, 0x15ac890),(0, 10, (nil))]
E : [ (3, 20, 0x15ac990),(1, 50, (nil))]

A : [ (2, 30, 0x15ac8b0),(1, 20, 0x15ac870),(3, 10, (nil))]第一行表示:A->C通、权值30、下一个结点指针,A->B通、权值20、下一个结点指针,A->D通、权值10、下一个结点指针。

特性1:邻接表不唯一。

特性2:若无向网中有n个顶点、e条边,则其邻接表需要n个头结点和2e个表结点。存储稀疏图比较合适。

特性3:无向网中顶点Vi的度为第i个单链表中的结点数。

特性4:邻接表出度易找,入度难找。逆邻接表出度难找,入度易找。

3、邻接矩阵和邻接表的区别和用途

3.1、区别

(1)对于任一确定的无向网,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一。

(2)邻接矩阵的空间复杂度是O(n^2),邻接表为O(n + e)。

(3)邻接矩阵的时间复杂度是O(n^2),邻接表为O(elog2(e))。

3.2、用途

邻接矩阵多用于稠密图。

邻接表多用于稀疏图。

三、宏定义

#define MAX_INT_TYPE_NUM      32767 //网中代表无穷大,也代表顶点个数。
#define MAX_VERTEX_NUM        10000 //顶点数组中存放顶点的最大个数。
#define NET_DIRECTION_FLAG    0     //有向网的标志
#define NET_UNDIRECTION_FLAG  1     //无向网的标志

四、结构体定义

1、邻接矩阵

//邻接矩阵图
typedef struct AdjacencyMatrixGraph
{
    VertexType VertexArray[MAX_VERTEX_NUM];
    ArcType    ArcArray[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
    int        CurVertexNum; //顶点数组中当前的顶点数。
    int        CurArcNum;    //弧数组中当前的弧个数。
}AMGraph;   

2、邻接表

//邻接表
typedef struct ArcNode
{
    VertexIndexType EndVertexIndex;
    WeightType      Weight;
    struct ArcNode* NextNodePtr;
}ArcNode;

typedef struct VertexNode
{
    VertexType Vertex;
    ArcNode*   FirstArcNodePtr;
}VertexNode;

typedef struct AdjacencyGragh
{
    VertexNode* Vertices;
    int         VertexNum;        //邻接表中的顶点数。
    int         ArcNum;           //邻接表中的弧个数。        
}AGraph;

3、网数据类型(造测试数据)

//网数据类型
typedef struct NetArcDataType
{
    VertexIndexType StartVertexIndex;//起始顶点。
    VertexIndexType EndVertexIndex;  //结束顶点。
    WeightType      Weight;          //权值。
}NetArcDataType;                     //数据为网的弧或边的信息。

// NetArcDataType  NetArcDataArray[MAX_VERTEX_NUM * (MAX_VERTEX_NUM - 1)]。
// 问题简记,如果MAX_VERTEX_NUM为1000时,在malloc NetDataType*类型内存时,会出现段错误。
typedef struct NetDataType
{
    VertexType*     VertexArray;
    NetArcDataType* NetArcDataArray;  //有向图完全图的边最多,顶点n,边最多为n*(n-1)。
    int             VertexNum;        //顶点数据数组中的顶点数。
    int             ArcNum;           //弧数据数组中的弧个数。
    int             DirectionFlag;    //无向图还是有向图的标志。
}NetDataType;                         //数据为网的信息。

五、函数定义

1、使用邻接矩阵创建无向网

Status CreateUndirectionNetUseAMGraph(AMGraph** AMG, NetDataType NDT)
{
    *AMG = (AMGraph*)MyMalloc(sizeof(AMGraph));    

    int i,j;

    (*AMG)->CurVertexNum = NDT.VertexNum;  //顶点数复制。
    (*AMG)->CurArcNum    = NDT.ArcNum * 2; //边数复制,由于无向图是双向的,所以需要乘2。
        
    //顶点数组复制。
    memcpy((*AMG)->VertexArray, NDT.VertexArray, sizeof(VertexType) * ((*AMG)->CurVertexNum));

    //初始化网中弧或边数组,统一初始化为无穷大,这里设置为int的最大值32767。
    for(i = 0; i < (*AMG)->CurVertexNum; i++)
    {
        for(j = 0; j < (*AMG)->CurVertexNum; j++)
        {   
            (*AMG)->ArcArray[i][j] = MAX_INT_TYPE_NUM;
        }
    }

    //把网数据中的弧或边数据填充到邻接矩阵图中。
    for(i = 0; i < NDT.ArcNum; i++)
    {
        (*AMG)->ArcArray[NDT.NetArcDataArray[i].StartVertexIndex][NDT.NetArcDataArray[i].EndVertexIndex] = NDT.NetArcDataArray[i].Weight;
        (*AMG)->ArcArray[NDT.NetArcDataArray[i].EndVertexIndex][NDT.NetArcDataArray[i].StartVertexIndex] = NDT.NetArcDataArray[i].Weight;
    }
    Log("Create Undirection Net Use AMGraph : OK\n",Info);
    return SuccessFlag;
}

2、使用邻接表创建无向网

Status CreateUndirectionNetUseAGraph(AGraph** AG, NetDataType NDT)
{
    (*AG) = (AGraph*)MyMalloc(sizeof(AGraph));

    (*AG)->VertexNum = NDT.VertexNum;
    (*AG)->ArcNum    = NDT.ArcNum * 2;

    //init adjacency graph
    (*AG)->Vertices = (VertexNode*)MyMalloc(sizeof(VertexNode) * (*AG)->VertexNum);
    int i;
    for(i = 0; i < (*AG)->VertexNum; i++)
    {
        (*AG)->Vertices[i].Vertex          = NDT.VertexArray[i];
        (*AG)->Vertices[i].FirstArcNodePtr = NULL;
    }

    //add arc to adjacency graph
    ArcNode* TmpArcNode  = NULL;
    for(i = 0; i < NDT.ArcNum; i++)
    {
        //printf("%d, %d, %d\n",NDT.NetArcDataArray[i].StartVertexIndex,NDT.NetArcDataArray[i].EndVertexIndex,NDT.NetArcDataArray[i].Weight);
        ArcNode* NewArcNode        = (ArcNode*)MyMalloc(sizeof(ArcNode));
        NewArcNode->EndVertexIndex = NDT.NetArcDataArray[i].EndVertexIndex;
        NewArcNode->Weight         = NDT.NetArcDataArray[i].Weight;
        NewArcNode->NextNodePtr    = NULL;

        TmpArcNode = (*AG)->Vertices[NDT.NetArcDataArray[i].StartVertexIndex].FirstArcNodePtr;
        if(TmpArcNode == NULL)//说明弧结点链表头部为空
        {
            (*AG)->Vertices[NDT.NetArcDataArray[i].StartVertexIndex].FirstArcNodePtr = NewArcNode;
        }
        else
        {
            NewArcNode->NextNodePtr                                                  = TmpArcNode;
            (*AG)->Vertices[NDT.NetArcDataArray[i].StartVertexIndex].FirstArcNodePtr = NewArcNode; //头插法,效率高。
        }

        ArcNode* NewArcNode1        = (ArcNode*)MyMalloc(sizeof(ArcNode));
        NewArcNode1->EndVertexIndex = NDT.NetArcDataArray[i].StartVertexIndex;
        NewArcNode1->Weight         = NDT.NetArcDataArray[i].Weight;
        NewArcNode1->NextNodePtr    = NULL;

        TmpArcNode = (*AG)->Vertices[NDT.NetArcDataArray[i].EndVertexIndex].FirstArcNodePtr;
        if(TmpArcNode == NULL)//说明弧结点链表头部为空
        {
            (*AG)->Vertices[NDT.NetArcDataArray[i].EndVertexIndex].FirstArcNodePtr = NewArcNode1;
        }
        else
        {
            NewArcNode1->NextNodePtr                                               = TmpArcNode;
            (*AG)->Vertices[NDT.NetArcDataArray[i].EndVertexIndex].FirstArcNodePtr = NewArcNode1;
        }
    }
    Log("Create Undirection Net Use AGraph  : OK\n",Info);
    return SuccessFlag;
}

3、销毁使用邻接矩阵创建的无向网

Status DestoryUndirectionNetUseAMGraph(AMGraph** AMG)
{
    JudgeAllNullPointer(AMG);
    JudgeAllNullPointer(*AMG);
    free(*AMG);
    *AMG = NULL;
    Log("Destory Undirection Net Use AMGraph: OK\n",Info);
    return SuccessFlag;
}

4、销毁使用邻接表创建的无向网

Status DestoryUndirectionNetUseAGraph(AGraph** AG)
{
    JudgeAllNullPointer(AG);
    JudgeAllNullPointer(*AG);

    ArcNode* TmpArcNode = NULL;
    ArcNode* CurArcNode = NULL;   

    //free All ArcNode
    int i;
    for(i = 0; i < (*AG)->VertexNum; i++)
    {
        (*AG)->Vertices[i].Vertex = '\0';
        TmpArcNode = (*AG)->Vertices[i].FirstArcNodePtr;
        CurArcNode = TmpArcNode;
        while(CurArcNode != NULL)
        {
            TmpArcNode = CurArcNode->NextNodePtr;
            free(CurArcNode);
            CurArcNode = TmpArcNode;
        }
    }
    
    //free VertexNode
    free((*AG)->Vertices);
    (*AG)->Vertices = NULL;

    //free AGraph
    (*AG)->VertexNum = 0;
    (*AG)->ArcNum    = 0;
    free(*AG);    
    *AG              = NULL;

    Log("Destory Undirection Net Use AGraph : OK\n",Info);
    return SuccessFlag;
}

六、Linux环境编译测试

[gbase@czg2 Graph]$ make
gcc -Wall -Wextra -g ../Log/Log.c ../PublicFunction/PublicFunction.c ../PublicFunction/SqQueue/SqQueue.c Graph.c main.c -o TestGraph -I ../Log/ -I ../PublicFunction/ -I ../Select/ -I ../PublicFunction/SqQueue/

[gbase@czg2 Graph]$ ./TestGraph 
[2023-5]--[ Info  ]--Create Net Data                    : OK
[2023-5]--[ Info  ]--Create Undirection Net Use AMGraph : OK
[2023-5]--[ Debug ]--Printf AMGraph                     :
VertexArray    : [A ,B ,C ,D ,E ]
ArcArray       :
[32767 ,20    ,30    ,10    ,32767 ]
[20    ,32767 ,32767 ,32767 ,50    ]
[30    ,32767 ,32767 ,10    ,32767 ]
[10    ,32767 ,10    ,32767 ,20    ]
[32767 ,50    ,32767 ,20    ,32767 ]
CurVertexNum   : 5
CurArcNum      : 12
[2023-5]--[ Info  ]--Create Undirection Net Use AGraph  : OK
[2023-5]--[ Debug ]--Printf AGraph                      :
A : [ (2, 30, 0xe078b0),(1, 20, 0xe07870),(3, 10, (nil))]
B : [ (4, 50, 0xe078d0),(0, 20, (nil))]
C : [ (3, 10, 0xe07910),(0, 30, (nil))]
D : [ (4, 20, 0xe07950),(2, 10, 0xe07890),(0, 10, (nil))]
E : [ (3, 20, 0xe07990),(1, 50, (nil))]
VertexNum      : 5
ArcNum         : 12

  • 8
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值