[数据结构]图的基本概念、存储结构(通俗易懂)

一、图的定义

图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

前面我们学习了线性表、树,到现在学图的相关知识,针对图的定义,简单地了解一下图的几个特点:

(1)线性表中数据元素叫元素;树中数据元素叫结点;在图中的数据元素,我们则称之为顶点。

(2)线性表中可以没有数据元素,称为空表;树中可以没有结点,叫做空树;但是在图结构中,不允许没有顶点。

(3)在线性表中,相邻的数据元素之间具有线性关系;树结构中,相邻两层的结点具有层次关系;在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以为空。

1、各种图定义

无向边:若顶点vi到vj之间的边没有方向,可用有序偶对(vi,vj)来表示。

有向边(弧):若从顶点vi到v之间j的边有方向,可用有序偶对<vi,vj>表示。

无向完全图:任意两个顶点之间都存在边。n个顶点的无向完全图有n*(n-1)/2条边。

有向完全图:任意两个顶点之间都存在方向互为相反的两条弧。n个顶点的有向完全图有n*(n-1)条边。

注意:无向边用小括号“( )”表示,有向边则是用尖括号“< >”表示

--此图摘取数据结构PPT中

路径:如无向图顶点1到顶点5的路径为v1,v2,v5。

路径长度:路径上的边或弧的数目。

回路或环:路径的起点和终点相同。

网路或网:带权的图。

二、图的存储方式

1、邻接矩阵:表示顶点之间的相邻关系的矩阵。无向图的邻接矩阵一定对称。

#define N ?///最大顶点
typedef struct{
   char vex[N];///顶点名
   int  e[N][N];///邻接矩阵
}Mgraph;

void creat_Mgraph(Mgraph *G,int n){
        for(int i=0;i<n;i++) scanf("%c",&G->vex[i])///输入顶带你
        for(int i=0;i<n;i++)
            for(int j=0j<n;j++)
               G->e[i][j]=0;///初始化
        for(int k=0;k<n;k++){///带权无向图
            scanf("%d%d",&a,&b);
            G->e[a][b]=1;
            G->e[b][a]=1;
        }
}

注意:邻接矩阵比较浪费内存,一般只适用边数比较少时;当数据较大时,往往会超时间或者超内存。

2、邻接表:数组与链表相结合的存储方法。  

(1)邻接表的链式实现:它的存储由顶点表和边表这两部分组成。

      1)顶点表的各个结点由datafirstedge两个域组成。

      2)边表结点由adjvexnext两个域组成。

#define N 100
typedef struct EdgeNode{///边表结点
    int adjvex;///邻接点域,存储该顶点对应的下标
    int weight;///权值
    EdgeNode *next;///链域,指示下一条边或弧,指向下一个邻接点
}EdgeNode;

typedef struct{///顶点结点
  char data;///顶点信息
  struct EdgeNode *firstedge;///指向第一个邻接点,边表头指针
}AdjList[N];

typedef struct ALGraph{
   AdjList adj;
   int n,m;///顶点数和边数
}ALGraph;

void GreatALGraph(ALGraph *G){///图的邻接表的结构
   EdgeNode *p,*q;
   cin>>G->n>>G->m;
   getchar();
   for(int i=0;i<G->n;i++){
    cin>>G->adj[i].data;///字符类型的顶点
    G->adj[i].firstedge=NULL;
   }
   getchar();
   for(int k=0;k<G->m;k++){
     char u,v;
     EdgeNode *p,*q;
    cin>>u>>v;
    int i,j;
    for(int s=0;s<G->n;s++){///找输入顶点的下标
        if(u==G->adj[s].data)
                i=s;
        if(v==G->adj[s].data)
                j=s;
   }

   p=(EdgeNode *)malloc(sizeof(EdgeNode));
   p->adjvex=j;
   p->next=G->adj[i].firstedge;
   G->adj[i].firstedge=p;

   q=(EdgeNode *)malloc(sizeof(EdgeNode));///从链表头插入
   q->adjvex=i;
   q->next=G->adj[j].firstedge;
   G->adj[j].firstedge=q;
 }
}

(2)邻接表的边集数组(链式前向星)实现,不用链表,但是类似链表的原理,非常容易实现;此方法一般用于数据类型为int的顶点,较为简便。它主要由firstnext这两个数组组成。first存储的时第一条边的编号(下标),next存储下一条边的编号(下标)。

前向星是一种特殊的边集数组,边集数组中的每一条边按起点从小到大排序,如果起点相同按终点从大到小排序,并记录下以某个点为起点的所有边在数组中的起始位置和存储长度。

///通俗写法。
struct EdgeNode
{
    int to;///这条边到达的点
    int w;///边权值
    int next;///下一条边的编号
}e[N];

///memset(head,-1,sizeof(head));初始化head
void add(int i,int j,int w)///添加边
{
    e[k].w = w;///边权值
    e[k].to = j;///下一条边的编号
    e[k].next = head[i];
    head[i] = k++;
}

void traver(int m)///遍历边集合
{
    int i,p;
    for(p=1;p<=m;p++)
    for(i=head[p];i!=-1;i=e[i].next)///遍历的关键
    {
        printf("%d %d %d\n",p,e[i].to,e[i].w);
    }
}

我们可以理解一下《啊哈算法》中实现此方法(都是一样的道理,比较简易),存储执行过程如下:

1)逐一输入特定无序边。

2)对于有多个邻接点的顶点,先next存储从输入编号低到输入编号高的,最后一条边first存储。比如顶点1有2个邻接点2、3.。

它的遍历顺序是先邻接点2后邻接点3。

///核心伪代码
for(int i<=1;i<=n;i++) first[i]=-1;///n为顶点数,初始化数组first
for(int i=1;i<=m;i++){///边数
    scanf("%d%d%d",&u[i],&v[i],&w[i])///输入边和权值
    next[i]=first[u[i]];///i是输入时的顺序,先找出下一边,然后才能更新first,先进先考虑的原则
    first[u[i]]=i;///因为当前顶点可能有多个邻接点,更新first;否则first就是唯一的
}
///怎么去遍历这些边呢
for(int i=1;i<=n;i++){
    k=first[i];
    while(k!=-1){///遇到-1,当前顶点的邻接点遍历结束
        printf("%d %d %d\n",u[k],v[k],w[k]);
        k=next[k];
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章