C语言数据结构与算法--------图论全面总结(附有详细动态图解)

目录

一、图的定义和术语

二、图的存储结构

1.邻接矩阵表示法

2.邻接表表示法

三、图的遍历

1.广度优先搜索BFS

2.深度优先搜索DFS

四、图的连通性

1.Prime算法最小生成树

2.克鲁斯卡尔算法最小生成树


一、图的定义和术语

  • 图:顶点和边组成的多对多线性关系
  • 完全图:图中每个顶点和剩余的 n-1个顶点直接相连,其中无向图最大边数n*(n-1)/2,有向图最大边数n*(n-1)
  • 连通图:一条线把所有顶点连接起来,其中无向图和有向图最小边数n-1
  • 出度和入度:无向图只有度(与该点相连的边数),有向图的出度(箭头指出去)、入度(箭头指向自己)
  •  回路:第一个顶点和最后一个顶点相同
    • 简单回路:回路的儿子

二、图的存储结构

1.邻接矩阵表示法

        简单说:用二维数组存储图,横纵坐标表示顶点,数组的值为0表示不连通,1表示连通。

无向图存储
有向图存储

 特点:

  • 无向图对称,可压缩,压缩后存储空间为n*(n+1)/2
  • 有向图不对称,存储空间为n*n
  • 邻接矩阵适合无向稠密图
  • 对于带有权值的无向网,把1改为距离,0改为无穷大,表示不可通行
  • 时间复杂度O(n*n+n*e) , n为顶点的数目,e为边的数目

代码实现:

    typedef struct
     {    
            VertexType  vexs[MAX_VERTEX_NUM]; // 顶点向量     
            ArcNode arcs [MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵  
            int  vexnum,arcnum; // 图的当前顶点数和弧(边)数   
     
     }AdjMatrix;  //邻接矩阵存储的图

2.邻接表表示法

为表中的每一个结点都建立一个单链表,其中单链表后面链接的元素为此结点到别的结点,如下图:

上图把a b c d e 作为表头结点,分别编号1~5,a能到达b和d,所以后面加上4 2,具体实现和单链表类似,向下的三角代表无别的结点了。 

 代码实现:

#define MAX_VERTEX_NUM    20 // 最大顶点个数
typedef   struct   ArcNode{
    int   adjvex;             //该弧所指向顶点的位置
    struct  ArcNode   *nextarc; //指示下一条边或弧的指针
    OtherInfo info; // 与该弧相关的信息
} ArcNode;
typedef   struct   VertexNode { 
    VertexData  data;      //顶点信息
    ArcNode    *firstarc; //指向第一条依附该顶点的弧的指针
} VertexNode;
typedef struct{
   VertexNode vertex[MAX_VERTEX_NUM]; // 邻接表  int   vexnum,   arcnum; // 图的当前顶点数和弧(边)数  GraphKind    kind; // 图的种类标志} AdjList;  //邻接表存储的图
  •  有向图的逆邻接表取反就行了,比如1->2,取反就是2->1
  • 邻接表适合有向稀疏图
  • 时间复杂度O(n+e)

三、图的遍历

1.广度优先搜索BFS

         就上图而言,广度遍历就是队列的熟练应用。首先,把与1相连的2 3存入队列,2出队,把和2相连的4 5存入队列,3出队,把和3相连的6 7存入队列,4出队,把和4相连的8存入队列,接下来5 6 7 8出队,没有和他们相连的,队列为空,遍历结束。出队的顺序,就是广度优先搜索的序列。

上面这个,按照队列方法可以自己试着写一下.

演示动态图:

编码实现:

void BFSTraverse(Graph G,Status(*Visit)(int v)){
//按广度优先非递归遍历图G。使用辅助队列Q和访问标志数组visited。
  for(v = 0;v<G.vexnum;++v) visited[v] = FALSE;
  InitQueue(Q);                    //置空的辅助队列Q
  for(v = 0;  v<G.vexnum;  ++v)
    if(!Visited[v]){                      //v尚未访问
      visited[v] = TRUE;  Visit(v);
      EnQueue(Q,v);                 //v入队列
      while(!QueueEmpty(Q)){
        DeQueue(Q,u);             //队头元素出队并置为u 
        for(w = FirstAdjVex(G,u);  w>=0;  w=NextAdjVex(G,u,w))
           if(!Visited[w]){     //w为u的尚未访问的邻接顶点
              Visited[w] = TRUE; Visit(w);
              EnQueue(Q,W);
            }   //if
       }   //while
     }   //if
}  //BFSTraverse           
  • 相当于树的层次遍历
  • 时间复杂度对于邻接表O(n+e),邻接矩阵O(n*n)

2.深度优先搜索DFS

                   对于上面的图片,深度优先遍历貌似一条道走到黑,撞到墙会回头的算法。从1开始,->2->4->8->5 ,之后发现5只能到2,然而2却走过了,所以慢慢往回退,退到8 4 2都没有发现其它的路径。当退到1时发现1->3没有访问过,于是1->3->6->7直到所有的结点被访问完成。

演示视频:

编码实现:

Boolean visited[MAX];     //访问标志数组
Status(*VisitFunc)(int v);   //函数变量
void DFSTraverse(Graph G,Status(*Visit)(int v)){
//对图G作深度优先遍历
  VisitFunc = Visit;  //使用全局变量VisitFunc,使DFS不必设函数指针参数
  for(v = 0;v<G.vexnum;++v)   visited[v] = FALSE; //访问标志数组初始化
  for(v = 0;v<G.vexnum;++v)
    if(!visited[v])   DFS(G,v);   //对尚未访问的顶点调用DFS
}             // 类似算法 7.3

void DFS(Graph G,int v){
//从第v个顶点出发递归地深度优先遍历图G。
  visited[v] = TRUE; VisitFunc(v); //访问第v个顶点
  for(w = FirstAdjVex(G,V); w>=0; w=NextAdjVex(G,v,w))
  if(!visited[w])    DFS(G,w); //对v的尚未访问的邻接顶点w递归调用DFS
}      
  • 相当于树的先序遍历
  • 时间复杂度对于邻接表O(n+e),邻接矩阵O(n*n)

总结:DFS就像一只老鼠走迷宫,碰到墙壁会返回,而DFS就像放一大堆老鼠从一个点向四面八方走迷宫,到了墙壁,老鼠撞死就没了。

四、图的连通性

1.Prime算法最小生成树

        从起点开始,找其相邻边中权值最小的边所连另一个顶点,再找与这两个顶点相邻边中权值最小的边所连第三个顶点,重复,扩展到所有顶点。

  • Prime算法和广度搜索有点像,如上图,与 a相连的有b g f,权值最小的是b,与b相连的g c,权值最小的是c,以此类推

代码实现: 

void MiniSpanTree_PRIM(MGraph G,VertexType u){
  //用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边。
  //记录从顶点集U到V-U的代价最小的边的辅助数组定义:
  //struct{
  //      VertxtType adjvex;
  //        VRType lowcost;
  // }closedge[MAX_VERTEX_NUM];
  k = LocateVex(G,u);
  for(j = 0;j<G.vexnum;++j)   //辅助数组初始化
    if(j! = K) closedge[j] = {u, G,arcs[k][j].adj}; //{adjvex, lowcost}
  closedge[k].lowcost = 0;       //初始,U = {u}
  for(i=1;i<G.vexnum;++i){    //选择其余G.vexnum-1个顶点
    k = minimum(closedge);    //求出T的下一个结点:第k顶点
    //此时closedge[k].lowcost =
    // MIN{closedge[vi].lowcost|closedge[vi].lowcost>0,vi∈V-U}
    printf(closedge[k].adjvex , G.vexs[k]);    //输出生成树的边
    closedge[k].lowcost = 0;    //第k顶点并入U集
    for(j = 0;j<G.vexnum;++j)
      if(G.arcs[k][j].adj<closedge[j].lowcost)   //新顶点并入U后重新选择最小边
          closedge[j] = {G.vexs[k], G.arcs[k][j].adj};
  }
}//MiniSpanTree

2.克鲁斯卡尔算法最小生成树

        先将所有顶点都看作无边的非连通图,选择各顶点间最小边做连通分量,直到所有定点都在同一个连通分量上。

  如上面的算法演示,从小到大找到权值最小的边,连接两个顶点,只要不形成回路,就行,算法不作要求。 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知心宝贝

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值