数据结构 图的定义及其四种存储结构(邻接矩阵法、邻接表法、十字链表法存储有向图、邻接多重表)

8、图

8.1、图的概念和定义

图G由顶点集V边集E组成,记为G=(V,E),其中V(G)表示图G中定点的有限非空集;E(G)表示图G中顶点之间的关系(边)的集合。若$V = \({\)V_1,V_2,V_3...V_n},则用==|V|==表示图G的==顶点个数==,也称==图G的阶==,E=\({\)(u,v)|u属于V,v属于V$},用|E|表示图G边的条数

注意:图不可以是空图

有向图、无向图

无向图

若E是无向边(简称边)的有限集合时,则图G是无向图,边是顶点的无序对,记为(w,v)或者(v,w),因为(w,v)=(v,w),其中w,v是顶点。可以说w,v互为邻接点。边(v,w)依附于顶点w和v,或者说边(w,v)和顶点w、v相关联。

有向图

若E是有向边(简称弧)的有限集合时,则图G是有向图。弧是顶点的有序对,记为<w,v>,其中w、v是顶点,w称为弧尾,v称为弧头,<w,v>称为从顶点w到顶点v的弧,也称w邻接到v,或者w邻接自v。<w,v>!=<v,w>

简单图、多重图

简单图 —— 不存在重复的边,不存在顶点到自身的边

多重图 —— 图G中某两个结点之间的边数多余一条,有允许顶点通过一条边和自己关联

顶点的度、入度、出度

对于无向图,顶点v的度指依附于该顶点的边的条数,记为TD(v)。

如边的顶点数n、数量为e:i=1nTD(vi)=2e

对于有向图:

  • 入度:是以顶点v为终点的有向边的数目,记为ID(v)
  • 出度:是以顶点v为起点的有向边的数目,记为OD(v)
  • TD(v) = OD(v) + ID(v)

如边的边数为n、数量为e:i=1nOD(vi)=i=1nID(vi)=e

顶点与顶关系的描述

  • 路径:顶点v到顶点w之间的一条路径是指顶点序列
  • 回路:第一个顶点和最后一个顶点相同的路径称为回路或环
  • 简单路径:在路径序列中,顶点不重复出现的路径称为简单路径
  • 简单回路:除了第一个顶点和最后一个顶点外,其余顶点不重复出现的回路称为简单回路
  • 路径长度:路径上边的数目
  • 点到点的距离:从顶点u出发到顶点v的最短路径若存在,则次路径的长度称为u到v的距离,若u到v不存在路径,则距离为无穷
  • 在无向图中,若顶点v到顶点w路径存在,则称v和w连通
  • 在有向图中,若顶点v到顶点w和顶点w到顶点v之间都有路径,则称这两个顶点是强连通

对于n个顶点的无向图G:

若G是连通图,则最少有n-1条边

若G是非连通图,则最多有Cn12条边

对于n个顶点的有向图G:

若G是强连通图,则最少有n条边(形成回路)。

连通图的生成树:包含图中全部顶点的一个极小连通子图

边的权、带权图/网

  • 带权边:一个图中,在每条边都可以标上具有某种含义的数值,数值称为该边的权值
  • 带权图/网:边上带有权值的图称为带权图,也称为网
  • 带权路径长度:当图是带权图时,一条路径上所有边的权值之和,称为该路径的带权路径长度

无向完全图:无向图中任意两个顶点之间都存在边

有向完全图:有向图中任意两个顶点之间都存在方向相反的两条弧

8.2、邻接矩阵法

顶点数为n,使用n*n的矩阵来表示边的信息,1表示相连,0表示不相连

#define MaxSize 100
typedef struct MGraph{
char Vex[MaxSize];//顶点信息
int Edge[MaxSize][MaxSize];//邻接矩阵
int vexnum,arcnum;//顶点数和边数
}MGraph;

Edge[i][j]={1,(vi,vj)<vi,vj>E(G)0,(vi,vj)<vi,vj>E(G)

设图G的邻接矩阵为A(矩阵的元素为1\0),则An元素An[i][j]表示顶点i到顶点j的长度为n的路径的数目

代码测试

#include <stdio.h>
#include <stdlib.h>
#include<math.h>
#define MaxSize 100
#define ElemType int
#define boolean int
#define true 1
#define false 0
//邻接矩阵存储无向图的信息
typedef struct MGraph{
char Vex[MaxSize];//顶点信息
int Edge[MaxSize][MaxSize];//邻接矩阵
int vexnum,arcnum;//顶点数和边数
}MGraph;
//初始化一个邻接矩阵
boolean MG_Init(MGraph **G){
*G = (MGraph*)malloc(sizeof(MGraph));
if((*G) == NULL) return false;//申请空间失败
(*G)->vexnum = 0;//顶点数为0
(*G)->arcnum = 0;//边数为0
// for(int i = 0;i < MaxSize;i++){
// for(int j = 0;j < MaxSize;j++){
// (*G)->Edge[i][j] = 0;
// }
// }
return true;
}
//获取该结点v的索引
int GetV_Index(MGraph *G,char v){
for(int i = 0;i<G->vexnum;i++){
if(G->Vex[i] == v) return i;
}
return -1;
}
//判断图中是否有结点v
boolean IsVEmpty(MGraph *G,char v){
for(int i=0;i<G->vexnum;i++){
if(G->Vex[i] == v) return true;//存在
}
return false;//不存在
}
//判断图中是否存在该边;传入边
boolean Adjancent(MGraph *G,char v,char w){
int vi = GetV_Index(G,v);
int wi = GetV_Index(G,w);
return G->Edge[vi][wi];
}
//判断图中是否存在该边;传入边的索引
boolean Adjancent1(MGraph *G,int vi,int wi){
if(G->Edge[vi][wi] == 1) return true;
else return false;
}
//插入一个新的顶点
boolean InsertV(MGraph *G,char v){
if(G->vexnum >= MaxSize || IsVEmpty(G,v)){
printf("存储顶点数的空间满了或者改结点存在!\n");
return false;
}
G->Vex[G->vexnum++] = v;
return true;
}
//插入边(v,w)
boolean AddEdge(MGraph *G,char v,char w){
if(!IsVEmpty(G,v) || !IsVEmpty(G,w)){
printf("输入的边其中有一个顶点或者两个顶点不存在\n");
return false;
}
//获取结点的索引
int vi = GetV_Index(G,v);
int wi = GetV_Index(G,w);
if(Adjancent1(G,vi,wi)){
printf("该边已经存在\n");
return false;
}
//修改邻接矩阵
G->Edge[vi][wi] = 1;
G->Edge[wi][vi] = 1;
//修改边数
G->arcnum++;
return true;
}
//删除边(v,w)
boolean RemoveEdge(MGraph *G,char v,char w){
if(!IsVEmpty(G,v) || !IsVEmpty(G,w)){
//判断这两个点是否存在
printf("这两个点不存在\n");
return false;
}
int vi = GetV_Index(G,v);
int wi = GetV_Index(G,w);
if(Adjancent1(G,vi,wi) == true){//存在该边就删除
G->Edge[vi][wi] = 0;
G->Edge[wi][vi] = 0;
G->arcnum--;//边减1
return true;
}else{
printf("删除的边不存在\n");
return false;
}
}
//获取某个结点v的所有邻接边
boolean NeighBors(MGraph *G,char v,char ***res,int *length){
if(!IsVEmpty(G,v)){
printf("输入的顶点不存在\n");
return false;
}
int vi = GetV_Index(G,v);
for(int i = 0;i < G->vexnum;i++){
if(G->Edge[vi][i] == 1) {
(*length)++;
}
}
*res = (char **)malloc(sizeof(char)*(*length));
int index = 0;
for(int i = 0;i < G->vexnum;i++){
if(G->Edge[vi][i] == 1) {
(*res)[index] = (char *)malloc(sizeof(char)*2);
(*res)[index][0] = v;
(*res)[index][1] = G->Vex[i];
index++;
}
}
return true;
}
int main(){
MGraph *G;
MG_Init(&G);
printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum);
InsertV(G,'A');
InsertV(G,'B');
InsertV(G,'C');
InsertV(G,'D');
InsertV(G,'E');
printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum);
AddEdge(G,'A','B');
AddEdge(G,'C','B');
AddEdge(G,'C','D');
AddEdge(G,'A','D');
AddEdge(G,'A','E');
printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum);
char **p;
int length;
NeighBors(G,'A',&p,&length);
printf("A的边:");
for(int i = 0;i < length;i++){
printf("(%c,%c),",p[i][0],p[i][1]);
}
printf("\n");
RemoveEdge(G,'A','B');
printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum);
return 0;
}
//结果:
顶点数:0;边数:0
顶点数:5;边数:0
顶点数:5;边数:5
A的边:(A,B),(A,D),(A,E),
顶点数:5;边数:4

8.3、邻接表法

//边\弧
typedef struct ArcNode{
int adjvex;//边或者弧指向的那个结点
struct ArcNode *next;//指向下一个弧的指针
//int info;//权值
}ArcNode;
//顶点
typedef struct VNode{
ElemType data;//顶点信息
ArcNode *first;//第一个边或弧
}VNode,AdjList[MaxSize];
//图
typedef struct MGraph{
AdjList vertices;//图的信息
int vexnum,arcnum;//顶点数和边数
}MGraph;

代码测试

#include <stdio.h>
#include <stdlib.h>
#include<math.h>
#define MaxSize 100
#define boolean int
#define true 1
#define false 0
//边\弧
typedef struct ArcNode{
int adjvex;//边或者弧指向的那个结点
struct ArcNode *next;//指向下一个弧的指针
//int info;//权值
}ArcNode;
//顶点信息
typedef struct ElemType{
char v;//顶点信息
int flag;//顶点是否被使用,1表示使用,0表示未使用
}ElemType;
//顶点
typedef struct VNode{
ElemType data;//顶点信息
ArcNode *first;//第一个边或弧
}VNode,AdjList[MaxSize];
//有向图的邻接表存储
typedef struct MGraph{
AdjList vertices;//图的信息
int vexnum,arcnum;//顶点数和边数
}MGraph;
//初始化图
boolean MG_Init(MGraph **G){
(*G) = (MGraph *)malloc(sizeof(MGraph));
if(*G == NULL){
printf("内存申请失败!\n");
return false;
}
(*G)->arcnum = 0;//边数
(*G)->vexnum = 0;//顶点数
for(int i = 0;i < MaxSize;i++){//把所有带使用的顶点信息数据使用标志全置为0
(*G)->vertices[i].data.flag = 0;
(*G)->vertices[i].first = NULL;
}
return true;
}
//判断顶点V是否存在,并获取边的索引Vi
boolean IsVEmpty(MGraph *G,ElemType V,int *Vi){
for(int i = 0;i < G->vexnum;i++){
if(G->vertices[i].data.v == V.v && G->vertices[i].data.flag == 1) {
*Vi = i;
return true;
}
}
return false;
}
//判断边是否存在:
boolean Adjancent(MGraph *G,ElemType V,ElemType W,int *Vi,int *Wi){
if(!IsVEmpty(G,V,Vi) || !IsVEmpty(G,W,Wi)){
printf("其中一个顶点不存在!!/\n");
return true;
}
ArcNode *node = G->vertices[*Vi].first;
while(node != NULL){
if(node->adjvex == *Wi){
// printf("边已经存在\n");
return true;
}
node = node->next;
}
return false;
}
//插入顶点
boolean InsertV(MGraph *G,ElemType V){
int *Vi;
if(IsVEmpty(G,V,Vi)){
printf("顶点已经存在!!\n");
return false;
}
V.flag = 1;
G->vertices[G->vexnum++].data = V;
return true;
}
//插入边
boolean AddArcNode(MGraph *G,ElemType V,ElemType W){
int *Vi = (int *)malloc(sizeof(int));
int *Wi = (int *)malloc(sizeof(int));
if(Adjancent(G,V,W,Vi,Wi)){//判断边是否存在
return false;
}
ArcNode * p = (ArcNode*)malloc(sizeof(ArcNode));
p->adjvex = *Wi;
p->next = NULL;
if(G->vertices[*Vi].first == NULL){//没有边,新增加边
G->vertices[*Vi].first = p;
G->arcnum++;
return true;
}
ArcNode *node = G->vertices[*Vi].first;
while(node->next != NULL){
node = node->next;
}
node->next = p;
G->arcnum++;
return true;
}
//获取某个结点v的所有邻接边;<v,wi>;以v为尾巴
boolean NeighBors(MGraph *G,ElemType V,ElemType ***res,int *length){
int *Vi = (int *)malloc(sizeof(int));
if(!IsVEmpty(G,V,Vi)){
return false;
}
ArcNode *node = G->vertices[*Vi].first;
while(node != NULL){
(*length)++;
node = node->next;
}
*res = (ElemType **)malloc(sizeof(ElemType)*(*length));
node = G->vertices[*Vi].first;
for(int i=0;i < (*length) ;i++){
(*res)[i] = (ElemType *)malloc(sizeof(ElemType)*2);
(*res)[i][0] = V;
(*res)[i][1] = G->vertices[node->adjvex].data;
node = node->next;
}
return true;
}
//移除该边<V,W>
boolean RemoveArcNode(MGraph *G,ElemType V,ElemType W){
int *Vi = (int *)malloc(sizeof(int));
int *Wi = (int *)malloc(sizeof(int));
if(!Adjancent(G,V,W,Vi,Wi)){//判断该边是否存在
return false;
}
ArcNode *delete = G->vertices[*Vi].first;//删除的结点
ArcNode *pre = NULL;//删除边的前一个结点
while(delete != NULL){//寻找删除的边
if(delete->adjvex == *Wi){
break;
}
pre = delete;
delete = delete->next;
}
if(pre == NULL){
G->vertices[*Vi].first = G->vertices[*Vi].first->next;
}else{
pre->next = delete->next;//修改指针
}
G->arcnum--;//修改边数
free(delete);//释放内存
return true;
}
//删除顶点
boolean DeleteV(MGraph *G,ElemType V){
int *Vi = (int *)malloc(sizeof(int));
if(!IsVEmpty(G,V,Vi)){
return false;
}
ArcNode *W = G->vertices[*Vi].first;
while(W != NULL){
RemoveArcNode(G,V,G->vertices[G->vertices[*Vi].first->adjvex].data);
W = G->vertices[*Vi].first;
}
int index = 0;
int sum = 0;
while(sum < G->vexnum){
if(G->vertices[index].data.flag==1){
W = G->vertices[index].first;
while(W != NULL){
if(W->adjvex == *Vi){
RemoveArcNode(G,G->vertices[index].data,V);
break;
}
W = W->next;
}
sum++;
}
index++;
}
G->vertices[*Vi].data.flag = 0;
G->vexnum--;
return true;
}
int main(){
MGraph *G;
MG_Init(&G);
printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum);
ElemType V1;
V1.v = 'A';
InsertV(G,V1);
ElemType V2;
V2.v = 'B';
InsertV(G,V2);
ElemType V3;
V3.v = 'C';
InsertV(G,V3);
ElemType V4;
V4.v = 'D';
InsertV(G,V4);
printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum);
AddArcNode(G,V1,V2);
AddArcNode(G,V1,V3);
AddArcNode(G,V2,V4);
AddArcNode(G,V4,V1);
AddArcNode(G,V4,V3);
AddArcNode(G,V4,V2);
printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum);
ElemType **p;
int length;
NeighBors(G,V1,&p,&length);
printf("%c为尾的边:",V1.v);
for(int i = 0; i < length;i++){
printf("<%c,%c>,",p[i][0].v,p[i][1].v);
}
printf("\n");
RemoveArcNode(G,V4,V2);
printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum);
DeleteV(G,V1);
printf("顶点数:%d;边数:%d\n",G->vexnum,G->arcnum);
return 0;
}
//结果:
顶点数:0;边数:0
顶点数:4;边数:0
顶点数:4;边数:6
A为尾的边:<A,B>,<A,C>,
顶点数:4;边数:5
顶点数:3;边数:2

8.4、十字链表法存储有向图

#define MaxSize 100
//弧结点
typedef struct ArcNode{
int tailvex,headvex;//弧尾号,弧头号
//int info;//权值
struct ArcNode *hlink,*tlink;//弧头的下一个弧,弧尾的下一个弧
}ArcNode;
//顶点结点
typedef struct VNode{
ElemType data;//顶点信息
ArcNode * fristin,*fristout;//弧头的第一个弧,弧尾的第一个弧
}VNode,AdjList[MaxSize];
//图
typedef struct MGraph{
AdjList vertices;//图的信息
int vexnum,arcnum;//顶点数和边数
}MGraph;

注意:十字链表法只能用于存储有向图

8.5、邻接多重表

#define MaxSize 100
//弧结点
typedef struct ArcNode{
int i,j;//顶点
//int info;//权值
struct ArcNode *ilink,*jlink;//依附于i的一条边,依附于j的第一条边
}ArcNode;
//顶点结点
typedef struct VNode{
ElemType data;//顶点信息
ArcNode * fristdge;//与顶点相邻的第一条边
}VNode,AdjList[MaxSize];
//图
typedef struct MGraph{
AdjList vertices;//图的信息
int vexnum,arcnum;//顶点数和边数
}MGraph;

注意:邻接多重表只能存储无向图

posted @   水三丫  阅读(278)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示