zoukankan      html  css  js  c++  java
  • 图的简介与创建

    一、基本术语
     
    图:由有穷、非空点集和边集合组成,简写成G(V,E);
    Vertex:图中的顶点;
     
    无向图:图中每条边都没有方向;
    有向图:图中每条边都有方向;
     
    无向边:边是没有方向的,写为(a,b)
    有向边:边是有方向的,写为
    有向边也成为弧;开始顶点称为弧尾,结束顶点称为弧头;
     
    简单图:不存在指向自己的边、不存在两条重复的边的图;
     
    无向完全图:每个顶点之间都有一条边的无向图;
    有向完全图:每个顶点之间都有两条互为相反的边的无向图;
     
    稀疏图:边相对于顶点来说很少的图;
    稠密图:边很多的图;
     
    权重:图中的边可能会带有一个权重,为了区分边的长短;
    网:带有权重的图;
     
    度:与特定顶点相连接的边数;
    出度、入度:对于有向图的概念,出度表示此顶点为起点的边的数目,入度表示此顶点为终点的边的数目;
     
    环:第一个顶点和最后一个顶点相同的路径;
    简单环:除去第一个顶点和最后一个顶点后没有重复顶点的环;
     
    连通图:任意两个顶点都相互连通的图;
    极大连通子图:包含竟可能多的顶点(必须是连通的),即找不到另外一个顶点,使得此顶点能够连接到此极大连通子图的任意一个顶点;
    连通分量:极大连通子图的数量;
    强连通图:此为有向图的概念,表示任意两个顶点a,b,使得a能够连接到b,b也能连接到a 的图;
     
    生成树:n个顶点,n-1条边,并且保证n个顶点相互连通(不存在环);
    最小生成树:此生成树的边的权重之和是所有生成树中最小的;
     
    AOV网:结点表示活动的网;
    AOE网:边表示活动的持续时间的网;
    --------------------------------------------------------------------------------
    二、图的创建存储:
     
    1、邻接矩阵法:
     //图边数多时,有向图/无向图/有向网/无向网都可用其来存储。
     //不过当边数较少时不适合用此方法,会造成空间浪费
     

    图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。

        设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:

        

        看一个实例,下图左就是一个无向图。

        

        从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。

        从这个矩阵中,很容易知道图中的信息。

        (1)要判断任意两顶点是否有边无边就很容易了;

        (2)要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和;

        (3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点;

        而有向图讲究入度和出度,顶点vi的入度为1,正好是第i列各数之和。顶点vi的出度为2,即第i行的各数之和。

        若图G是网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:

        

        这里的wij表示(vi,vj)上的权值。无穷大表示一个计算机允许的、大于所有边上权值的值,也就是一个不可能的极限值。下面左图就是一个有向网图,右图就是它的邻接矩阵。

        

    下面是其代码实现:
     
    #include
    #include
    #include
    #define NUM 20;
    #define BIG 22365;
     
    typedef enum{DG,UNG,DN,UDN} graphtype;   //图的类型
    typedef int vrtype;      //顶点类型
    typedef int *infotype;    //边的相关信息类型
     
    //定义顶点关系类型
    typedef struct Arccell{
      int arckink;      //边的类型,即顶点关系类型,无向图为1/0,表是否相邻。无向网为权值
     //infotype info;   //指向该边所有相关信息
    }arccell;
     
    //定义无向图/网
    typedef struct{
       graphtype gkind;  //图的类型
       vrtype vr[NUM];   //储存所有顶点
       arccell arc[NUM][NUM];  //储存所有边
       int vrnum,arcnum;  //顶点和边的个数
    }graph;
     
    ---------------------------------
     //点位某个顶点在数组中的位置
    int locate(graph G,vrtype v){
       int i;
       for(i=0;ivrnum;i++)
          if(G->vr[i]==v)
             break;
        if(i>=G->vrnum)
            return -1;
        else
            return i;
    }
    --------------------------------
     //创建无向图/网
    int  createUDN(graph G){
       scanf("%d %d ",&vrnum,&arnum );  // 输入顶点和边数量以及边相关信息数量
       for(i=0;i
          scanf("%d",&vr[i]);
       for(i=0;i
         for(j=0;j
            arc[i][j]={BIG,NULL};
        for(i=0;i
            scanf("%d %d %d",&vr1,&vr2,&arckink);  //输入顶点关系类型
            j=locate(G,vr1);  //定位俩顶点
            k=locate(G,vr2);  //输入顶点关系类型
            if(i!=-1&&j!=-1){  //俩顶点均存在
                G->arc[j][k]->arckink;
             
                G->arc[j][i]->artype=G->arc[i][j]->artype;  //将信息复制到其对称边上
                G->arc[j][i]->info=G->arc[i][j]->info;
            }
        }
        return 0;
    }
    ----------------------------
     //创建有向图/网。
    与创建无向图/网类似,不过最后不用复制信息到其对称弧上。再次不作重复创建
     
     int createDN(graph G){
     
        return 0;
    }
     
    -------------------------
    int creategraph(graph G){   //选择要储存的图的类型
       switch(G->gtype){
          case DG:createNG(G);
          case UNG:createUNG(G);
          case DN:createDN(G);
          case UDN:createUDN(G);
          default:return ERROR;
       }
    }
     
    int main()    //主函数。
    {
        graph G;
        creategraph(G);
        return 0;
    }
     
    --------------------------------------------------------------------------------------------------
     
    2、邻表法:
    //邻表法解决了邻接矩阵的缺点,但对于每个顶点而已,查找其出度容易,而要找入读却得遍历整个图才能找到。故邻表法不适合有向图/网存储。无向图/网可以其来存储,比十字链表法简单
     

        邻接表的处理方法是这样的:

        (1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。

        (2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。

        例如,下图就是一个无向图的邻接表的结构。

        

        从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。

        对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。如下图所示。

        

    对于邻接表结构,图的建立代码如下。

    #define BIG 26655;
    #define NUM 20;
     
    typedef char vrtype; //顶点为字符时不能是换行符,因为输出字符要换行
    typedef int *infotype;
     
     
    //定义顶点关系类型
    typedef struct Arccell{
      int tail;
      int weight;      //无向网为权值,无向图不需要。
     //infotype info;   //指向该边所有相关信息
      struct Arccell *next;
    }arccell;
     
    //定义顶点类型
    typedef struct {
       vrtype data;
       arccell *firstarc;
    }vrcell;
     
     
    //定义无向图/网类型
    typedef struct{
       vrcell vr[NUM];   //储存所有顶点
       int vrnum,arcnum;  //顶点和边的个数
    }graph;
     
     
    void CreateGraph(Graph g)
    {
        int i, j, k;
        EdgeNode *e;
        EdgeNode *f;
      //  printf("输入顶点数和边数: ");
        scanf("%d,%d", &g->numVertexes, &g->numEdges);
     
        for(i = 0; i < g->numVertexes; i++)    //输入顶点信息
        {
           // printf("请输入顶点%d: ", i);
            g->adjList[i].data = getchar();
            g->adjList[i].firstedge = NULL;     //将边表置为空表
     
            while(g->adjList[i].data == ' ')
            {
                g->adjList[i].data = getchar();
            }
        }
     
        //建立边表
        for(k = 0; k < g->numEdges; k++)
        {
            printf("输入边(vi,vj)上的顶点序号: ");
            char p, q;
            p = getchar();
            while(p == ' ')
            {
                p = getchar();
            }
            q = getchar();
            while(q == ' ')
            {
                q = getchar();
            }
            int m, n;
            m = Locate(g, p);
            n = Locate(g, q);
            if(m == -1 || n == -1)
            {
                return;
            }
            //向内存申请空间,生成边表结点
            e = (EdgeNode *)malloc(sizeof(EdgeNode));
            if(e == NULL)
            {
                fprintf(stderr, "malloc() error. ");
                return;
            }
            //邻接序号为j
            e->adjvex = n;
            //将e指针指向当前顶点指向的结构
            e->next = g->adjList[m].firstedge;
            //将当前顶点的指针指向e
            g->adjList[m].firstedge = e;
     
     
            f = (EdgeNode *)malloc(sizeof(EdgeNode));  //有向图/网则不需要进行这一步
            if(f == NULL)
            {
                fprintf(stderr, "malloc() error. ");
                return;
            }
            f->adjvex = m;
            f->next = g->adjList[n].firstedge;
            g->adjList[n].firstedge = f;
        }
    }
     
    ----------------------------------------------------------------------------------------
     
    3、十字链表法:
     
    十字链表(Orthogonal List)是有向图的一种存储方法,它实际上是邻接表与逆邻接表的结合,即把每一条边的边结点分别组织到以弧尾顶点为头结点的链表和以弧头顶点为头顶点的链表中。在十字链表表示中,顶点表和边表的结点结构分别如图8.13 的(a)和(b)所示。
     
            在弧结点中有五个域:其中尾域(tailvex)和头(headvex)分别指示弧尾和弧头这两个顶点在图中的位置,链域hlink 指向弧头相同的下一条弧,链域tlink 指向弧尾相同的下一条弧,info 域指向该弧的相关信息。弧头相同的弧在同一链表上,弧尾相同的弧也在同一链表上。它们的头结点即为顶点结点,它由三个域组成:其中vertex 域存储和顶点相关的信息,如顶点的名称等;firstin 和firstout 为两个链域,分别指向以该顶点为弧头或弧尾的第一个弧结点。例如,图8.14(a)中所示图的十字链表如图8.14(b)所示。若将有向图的邻接矩阵看成是稀疏矩阵的话,则十字链表也可以看成是邻接矩阵的链表存储结构,在图的十字链表中,弧结点所在的链表非循环链表,结点之间相对位置自然形成,不一定按顶点序号有序,表头结点即顶点结点,它们之间而是顺序存储。有向图的十字链表存储表示的形式描述如下:
     
    #define MAX_VERTEX_NUM 20
    typedef struct ArcBox {
    int tailvex,headvex;
    struct ArcBox * hlink, tlink; /分别为弧头相同和弧尾相财的弧的链域*/
    InfoType info;
    }ArcBox;
    typedef struct VexNode {
      VertexType vertex:
      ArcBox fisrin, firstout;
    }VexNode;
    typedef struct {
       VexNode xlist[MAX_VERTEX_NUM];
       int vexnum,arcnum;
    }OLGraph;
     
     
           下面给出建立一个有向图的十字链表存储的算法。通过该算法,只要输入n 个顶点的信息和e 条弧的信息,便可建立该有向图的十字链表,其算法内容如下。
    void CreateDG(LOGraph **G)
    {
           scanf (&(*G->brcnum),&(*G->arcnum),&IncInfo);
           for (i=0;i<*G->vexnum;++i){
         scanf(&(G->xlist[i].vertex));
        *G->xlist[i].firstin=NulL;*G->xlist[i].firstout =NULL;
               }
    for(k=0;k
    {   scanf(&v1,&v2);
    i=LocateVex(*G,v1); j=LocateVex(*G,v2);
    p=(ArcBox*) malloc (sizeof(ArcBox));
    *p={ i,j,*G->xlist[j].fistin,*G->xlist[i].firstout,NULL}
    *G->xlist[j].fisrtin=*G->xlist[i].firstout=p;
    if (IncInfo) Input( p->info);
    }
    }
     
    在十字链表中既容易找到以为尾的弧,也容易找到以vi 为头的弧,因而容易求得顶点的出度和入度(或需要,可在建立十字链表的同时求出)。同时,由算法8.3 可知,建立十字链表的时间复杂度和建立邻接表是相同的。在某些有向图的应用中,十字链表是很有用的工具。
  • 相关阅读:
    【Canal源码分析】Canal Instance启动和停止
    【Canal源码分析】Canal Server的启动和停止过程
    【Canal源码分析】parser工作过程
    【源码分析】Canal之Binlog的寻找过程
    otter代码在IDEA远程DEBUG方法
    【源码】otter工程结构
    一个Java程序员的2018年展望与2017年总结
    【源码解析】Sharding-Jdbc的执行过程(一)
    IntelliJ IDEA 调试 Apache RocketMQ 源码
    [源码分析]HashSet 和LinkedHashSet
  • 原文地址:https://www.cnblogs.com/yujon/p/5467611.html
Copyright © 2011-2022 走看看