数据结构—图

一、图的基本概念

    图(graph)是一种数据结构。

    抽象类型定义:

1、数据对象V:V是具有相同特性的数据元素的集合,称为顶点集。

2、数据关系R:R={VR}   VR={<v,w>|v,w属于集合V,p(v,w)或序偶p<v,w>表示从顶点v,到w的弧}。

无向图:边有向,例如有向图序偶p<v1,v2>

有向图:边无向   p(v1,v2)=p(v2,v1)

完全图:任意两个顶点之间都有联通。1/2*n(n-1)。即每个顶点可以发出n-1条边,*n,去重。

有向完全图:任意连个顶点相连,但是p<v1,v2>!=p<v2,v1>,固有n(n-1)条边。

网:边带权的值得图称为网。

度:例如顶点v的度是和顶点v相连的边树,在有限图中,根据边的方向,又分为出度和入度。

路径:无向图从顶点v到w的路径是一个顶点序列。

回图:从顶点v再到顶点v的顶点序列。

连通图:任意两个顶点想通。

强连通图:有向图的任意两个顶点相通。

二、图的存储结构

       数组表示法:由于图任意两个顶点之间都可能存在联系,因此无法以数据元素在存储区中的物理位置来表示数据元素之间的关系,即图没有顺序映像的存储方式。但可以借助数组(二维数组)的数据类型表示元素之间的关系。

       链式存储方式:用多重链表表示图,即由一个数据域和多个指针域组成的结点表示图中的一个结点。数据域表示结点的信息,指针域指向与它相邻的结点。

(1)数组表示法:

用两个数组分别存储数据元素的顶点信息和数据元素之间的关系。

各自对应的连个矩阵。其中横纵分别排列V1,V2......。顶点之间相连为1,否则为0。

无相对图的邻接矩阵是对称性的,在存储过程中一般采用压缩存储的方式,存储下三角型。

类似的,可以推测网的存储方式。

(2)链式存储方式

   其一,邻接表

从图中可以看出,在邻接表中,建立一个一维数组,存储结点,对图中每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点vi的边。

每个结点由3个域组成,其中邻接点域(adjvex)指示与顶点vi邻接的点在图中的位置,链域nextarc指示下一条边或弧的结点;数据域info存储和边或弧相关的信息,如权值等。每个链表附设一个表头结点。在表头结点中,除了设有链域(firstarc)指向链表中的一个结点外,还设有存储结点vi的名或其它有关信息的数据域(data)。

编码数据结构定义,图的构造,首先输入图标识,边数、结点数,够着一位数组头结点信息,先令头结点指针域为空,之后分类讨论对每个头结点引出的单链表的构造。

Status CreateGraph(ALGraph *G)
{ /* 采用邻接表存储结构,构造没有相关信息的图G*/
  int i,j,k;
  int w; /* 权值 */
  VertexType va,vb;
  ArcNode *p;
  printf("请输入图的类型(有向图:0,有向网:1,无向图:2,无向网:3): ");
  scanf("%d",&(*G).kind);
  printf("请输入图的顶点数,边数: ");
  scanf("%d,%d",&(*G).vexnum,&(*G).arcnum);
  printf("请输入%d个顶点的值(<%d个字符):\n",(*G).vexnum,MAX_NAME);
  for(i=0;i<(*G).vexnum;++i) /* 构造顶点向量 */
  {
    scanf("%s",(*G).vertices[i].data);
    (*G).vertices[i].firstarc=NULL;
  }
  if((*G).kind==1||(*G).kind==3) /* 网 */
    printf("请顺序输入每条弧(边)的权值、弧尾和弧头(以空格作为间隔):\n");
  else /* 图 */
    printf("请顺序输入每条弧(边)的弧尾和弧头(以空格作为间隔):\n");
  for(k=0;k<(*G).arcnum;++k) /* 构造表结点链表 */
  {
    if((*G).kind==1||(*G).kind==3) /* 网 */
      scanf("%d%s%s",&w,va,vb);
    else /* 图 */
      scanf("%s%s",va,vb);
    i=LocateVex(*G,va); /* 弧尾 */
    j=LocateVex(*G,vb); /* 弧头 */
    p=(ArcNode*)malloc(sizeof(ArcNode));
    p->adjvex=j;
    if((*G).kind==1||(*G).kind==3) /* 网 */
    {
      p->info=(int *)malloc(sizeof(int));
      *(p->info)=w;
    }
    else
      p->info=NULL; /* 图 */
    p->nextarc=(*G).vertices[i].firstarc; /* 插在表头 */
    (*G).vertices[i].firstarc=p;
    if((*G).kind>=2) /* 无向图或网,产生第二个表结点 */
    {
      p=(ArcNode*)malloc(sizeof(ArcNode));
      p->adjvex=i;
      if((*G).kind==3) /* 无向网 */
      {
        p->info=(int*)malloc(sizeof(int));
        *(p->info)=w;
      }
      else
        p->info=NULL; /* 无向图 */
      p->nextarc=(*G).vertices[j].firstarc; /* 插在表头 */
      (*G).vertices[j].firstarc=p;
    }
  }
  return OK;
}

其二、十字链表

。。。

三、图的遍历

(1)深度优先搜索:类似于树的先根遍历,例如

v1>v2>v4>v8>v5        > v3>v6>v7   显然,必须对遍历过的结点需要设置一个标识。例如当遍历到v5时,走v2,因为v2已被访问,回退到v8,访问v4,显然v根据标识已被访问,依次回溯到v1,再进行v3结点的访问。

(2)广度优先搜索

遍历上图的为   v1>v2>v3>v4>v5>v8     回退   在从v3的邻接点开始   v6   >v7

四、图部分常考题型

(1)网的最小生成树常用算法   普里姆算法和克鲁斯卡尔算法

普里姆算法  从任意一个结点出发,选择权值最小的边,(v1)相连的最小的边为1,(v1,v3)在所有相邻最小的边为4,(v1,v3,v6),相邻最小的边为2,(v1,v3,v6,v4),相邻最小的边为5,(v1,v3,v6,v4,v2)相邻最小的边为3。注意,去找未进入集合中顶点的最小边。

最小生成树的边数一定等于n-1。

克鲁斯卡尔算法  从另外一个角度求网的最小生成树,先从权值最小的边开始。例如在所有的边中选择最小的边1,得(v1,v3),依次2,得(v4,v6),次之3,得(v2,v5)....。

(2)拓扑排序

例如(a)v1>v2>v3>v4 或v1>v3>v2>4   一般答案不唯一。

          (b)v1>v2>v3>v4  必须走v2在能有走v3。

(3)关键路径

顶点:表事件

弧:表活动

权:活动持续的时间

关键问题:

完成整个工程需要的最少时间?即关键路径    答:求最长路径a1>a4>a7>a10    得18

哪些活动是影响进度的关键:关键路径上的活动都是关键活动。非关键活动并不影响工程进度。

还有一半情况下,在考试中,往往会要求某个活动的最迟开始时间。例如求a5的最迟开始时间。用关键路径,即18-2-9-1=6。即活动a5必须在第6(单位自己定义)开始。否则影响工期。

可以优化关键路径上的活动,缩短工期。

(4)最短路径

这类题,在考试中,往往要求从任意的某个订单出发,求它到达其余结点的最短路径。采用的算法有迪杰斯特拉算法和弗洛伊德算法。

迪杰斯特拉算法,求得某个顶点到其余顶点的最短路径。

首先,在i=1列列出v0与其余各个结点的是否相连,连接则标出权值,否则假定为无穷大,vi行表示v0到达的结点,s行表示它最短的路径。

当i=2时,同理v1到v5记录它到达的路径及最小权值,注意是在集合i=1列,添加判断是否能到达。例如i=1时,可以看出v0与v3不直接相连,但是到达v3可以从(v0,v2),(v0,v4),考虑与其结点相连结点的边。即它是在已求得最短的路径之后再一步一步扩大顶点的范围。

弗洛伊德算法

求得任意两个顶点的最短距离

关键点:对于每个顶点v,和任意一个顶点对(i,j),i  != j,v   !=  i, v  !=   j,如果A[i][j]>A[i][v]+A[v][j],则A[i][j]=A[i][v]+A[v][j]。

D-1为图的初始化邻接矩阵,D(0)根据初始化计算而来,逐步计算到D(2)。

P矩阵上部分D(n)矩阵。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值