数据结构 第六章学习小结

数据结构第六章学习小结

    • 6.1 图的定义和基本术语
      • 6.1.1 图的定义 
      • 6.1.2 图的基本术语
    • 6.2 案例引入
    • 6.3 图的类型定义
      View Code
    • 6.4 图的存储结构
      • 6.4.1 邻接矩阵
        • 1.图的邻接矩阵存储表示
          #define Maxint 32767 //表示极大值, 即 ∞
          #define MVNum 100 //最大顶点数
          typedef char VerTexType; //假设顶点的数据类型为字符型
          typedef int ArcType; //假设边的权值类型为整型
          typedef struct
          { 
          VerTexType vexs [MVNum] ; //顶点表
          ArcType arcs[MVNu] [MVNum]; //邻接矩阵
          int vexnum,arcnum; //图的当前点数和边数
          ) AMGraph; 
          View Code
        • 2.算法:采用邻接矩阵表示法创建无向网 (时间复杂度是O(n^2))
          Status CreateUDN(AMGraph &G) 
          {//采用邻接矩阵表示法,创建无向网G
             cin>>G.vexnum>>G.arcnum;  //输人总顶点数,总边数
             for(i=0;i//依次输入点的信息
                cin>>G.vexs[i); 
             for(i=0;i//初始化邻接矩阵,边的权值均置为极大值Maxint
                for (j =0; j j) 
                   G.arcs[i) [j)=Maxint;
             for(k=0;k//构造邻接矩阵
             {
                 cin>>vl>>v2>>w; //输人一条边依附的顶点及权值
                 i=LocateVex(G,vl);j=LocateVex(G,v2);   //确定vl和v2在G中的位置,即顶点数组的下标
                G.arcs[i) [j)=w;   //的权值置为w
                G.arcs[j] [i]=G.arcs[i] [j]; //的对称边的权值为w
              }
             return OK;
          }
          View Code
        • 3.优缺点
          (1)优点
          1.便于判断两个顶点之间是否有边, 即根据A[z][j] = 0或1来判断。
          2.便于计算各个顶点的度。对千无向图,邻接矩阵第凶于元素之和就是顶点l的度;对于有向图,第i行元素之和就是顶点 i 的出度,第i 列元素之和就是顶点l 的入度。
          (2) 缺点
          1.不便于增加和删除顶点。
          2.不便于统计边的数目,需要扫描邻接矩阵所有元素才能统计完毕,时间复杂度为O(n^2)
          3.空间复杂度高。如果是有向图,n个顶点需要n2个单元存储边。如果是无向图,因其邻接矩阵是对称的,所以对规模较大的邻接矩阵可以采用压缩存储的方法,仅存储下三角(或上三角)的元素,这样需要n(n-1)/2个单元即可。但无论以何种方式存储,邻接矩阵表示法的空间复杂度均为O(n^2),这对于稀疏图而言尤其浪费空间。
          View Code
      • 6.4.2 邻接表
        • 1.图的邻接表存储表示
          #define MVNum 100  //最大顶点数
          typedef struct ArcNode  //边结点
          int adjvex;   //该边所指向的顶点的位置
             struct ArcNode * nextarc;  //指向下一条边的指针
             Otherinfo info;  ///和边相关的信息
            }ArcNode; 
          
          typedef struct VNode  //顶点信息
          { 
             VerTexType data;
             ArcNode *firstarc;  ///指向第一条依附该顶点的边的指针
           } VNode,AdjList[MVNum];   //AdjList表示邻接表类型
          
          typedef struct //邻接表
           {
             AdjList vertices; 
             int vexnum,arcnum;  //图的当前顶点数和边数
           }ALGraph;
          View Code
        • 2.算法:采用邻接表表示法创建无向图 (时间复杂度是O(n + e))
          Status CreateUDG(ALGraph &G) 
          {//采用邻接表表示法, 创建无向图 G
            cin>>G.vexnum>>G.arcnum;  //输入总顶点数, 总边数
            for(i=O;i//输入各点,构造表头结点表
              {
                cin»G.vertices[i) .data; //输入顶点值
                G.vertices[i) .firstarc=NULL;//初始化表头结点的指针域为NULL
              }
            for(k=O;k//输入各边, 构造邻接表
             {
                cin>>vl>>v2; //输入 一条边依附的两个顶点
                i=LocateVex(G,vl); j =LocateVex(G,v2); //确定vl和 v2在G中位置, 即顶点在G.vertices中的序号
                pl=new ArcNode; //生成一个新的边结点*pl
                pl->adjvex=j; //邻接点序号为j
                pl->nextarc=G. vertices [ i] . firstarc; G. vertices [ i] . firstarc= pl; //将新结点*pl插入顶点Vi的边表头部
                p2=new ArcNode;  //生成另一个对称的新的边结点*p2
                p2->adjvex=i; //邻接点序号为1
                p2->nextarc=G.vertices[j] .firstarc; G.vertices[j] .firstarc=p2; //将新结点*p2插入顶点Vj的边表头部
              } 
          return OK;
          }
          View Code
        • 3.优缺点
          (1) 优点
          1.便于增加和删除顶点。
          2.便于统计边的数目, 按顶点表 顺 序扫描 所 有边表可得到边的数目,时间复杂度为 O(n + e)
          3.空间效率高。对于一个具有n个顶点e条边的图 G, 若 G 是无向图,则在其邻接表表示中有 n 个顶点表结点和 2e 个边表结点;若 G 是有向图,则在它的邻接表表示或逆邻接表表示中均有 n 个顶点表结点和e个边表结点。因此,邻接表或逆邻接表表示的空间复杂度为 O(n + e), 适合表示稀疏图。对于稠密图,考虑到邻接表中要附加链域,因此常采取邻接矩阵表示法。
          (2) 缺点
          1.不便于判断顶点之间是否有边,要判定 V;和v丿之间是否有边,就需扫描第!个边表,最坏情况下要耗费 O(n)时间。
          2.不便于计算有向图各个顶点的度。对千无向图,在邻接表表示中顶点V;的度是第i个边表中的结点个数。 在有向图的邻接表中,第 i 个边表上的结点个数是顶点 V;的出度,但求 V的入度较困难,需遍历各顶点的边表。若有向图采用逆邻接表表示,则与邻接表表示相反,求顶点的入度容易,而求顶点的出度较难。
          View Code
      • 6.4.3 十字链表
        • 有向图的十字链表存储表示
          #define MAX_ VERTEX_NUM 20 
          typedef strut ArcBox 
          {
             int tailvext,headvex;//该弧的尾和头顶点的位置
             struct ArcBox *hlink, *tlink; //分别为弧头相同和弧尾相同的弧的链域
             InfoType *info; I//该弧相关信息的指针
          ) ArcBox; 
          
          typedef struct VexNode
          { 
            VertexType data; 
            ArcBox *firstin,*firstout;//分别指向该顶点第一条入弧和出弧 
          }VexNode; 
          
          typedef struct 
          { 
            VexNode xlist [MAX_VERTEX_NUM]; //表头向量
            int vexnnm, arcnum; //有向图的当前顶点数和弧数
          }OLGraph;
          View Code
      • 6.4.4 邻接多重表 
        • 无向图的邻接多重表存储表示
          #define MAX_VERTEX_NUM 20
          typedef enum{unvisited,visited} Visitlf;
          
          typedef struct EBox
          {
          Visitlf mark; //访问标记
          int ivex, jvex; //该边依附的两个顶点的位置
          struct EBox *ilink, *jlink; //分别指向依附这两个顶点的下一条边
          InfoType *info; //该边信息指针
          }Ebox; 
          
          typedef struct VexBox 
          { 
          VertexType data; 
          EBox *firstedge; //指向第一条依附该顶点的边
          }VexBox;
           
          typedef struct
          { 
          VexBox adjmulist [MAX_VERTEX_NUM]; 
          int vexnum, edgenum;//无向图的当前顶点数和边数
          }AMLGraph;
          View Code
    • 6.5 图的遍历
      • 6.5.1 深度优先搜索 
        • 算法:深度优先搜索遍历连通图 
          bool visited [MVNum) ; //访问标志数组, 其初值为 "false"
          void DFS(Graph G,int v) 
          {//从第 v 个顶点出发递归地深度优先遍历图G
              cout<true; 
              //访问第 v 个顶点, 并置访问标志数组相应分量值为 true
              for(w=FirstAdjVex(G,v);w>=O;w=NextAdjVex(G,v,w))
              //依次检查 v 的所有邻接点 w , FirstAdjVex (G, v)表示 v 的第一个邻接点
             //NextAdjVex(G,v,w)表示 v 相对千 w 的下一个邻接点, w匀表示存在邻接点
                 if(!visited[w])   DFS(G,w); //对 v 的尚未访问的邻接顶点 w 递归调用 DFS
          }
          View Code
        • 算法:深度优先搜索遍历非连通图 
          void DFSTraverse(Graph G) 
          {//对非连通图G做深度优先遍历
             for(v=O;vfalse; //访问标志数组初始化
             for(v=O;v//循环调用算法6.3
             if(!visited[v]) DFS(G,v);    //对尚未访问的顶点调用DFS
          }
          View Code
        • 算法 :采用邻接矩阵表示图的深度优先搜索遍历
          void DFS_AM(AMGraph G,int v) 
          {//图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
            cout<true; //访问第v个顶点,并置访问标志数组相应分量值为true
            for(w=O;w//依次检查邻接矩阵 v所在的行
                if((G.arcs[v] [w] !=O}&&(!visited[w]}} DFS(G,w}; 
               //G.arcs[v] [w] ! =0表示w是v的邻接点, 如果w未访问, 则递归调用DFS
          }
          View Code
        • 算法 :采用邻接表表示图的深度优先搜索遍历
        • void DFS_AL (ALGraph G,int v) 
          {//图G为邻接表类型, 从第v个顶点出发深度优先搜索遍历图G
              cout<true;  //访问第v个顶点,并置访问标志数组相应分量值为true
              p=G.vertices[v] .firstarc;   //p指向v的边链表的第一个边结点
              while(p!=NULL)    //边结点非空
              {
                w=p->adjvex;  //表示w是v的邻接点
                if(!visited[w]) DFS(G,w);  //如果w未访问, 则递归调用DFS
                p=p->nextarc;  //p指向下一个边结点
              }
          }
          View Code
        • 算法分析 
          • 当用邻接矩阵表示图时,查找每个顶点的邻接点的时间复杂度为 O(n2 ), 其中 n为图中顶点数。而当以邻接表做图的存储结构时,查找邻接点的时间复杂度为O(e), 其中e为图中边数。由此,当以邻接表做存储结构时,深度优先搜索遍历图的时间复杂度为 O(n + e)。
      • 6.5.2 广度优先搜索 
        • 算法:广度优先搜索遍历连通图 
          void BFS{Graph G,int v) 
          {//按广度优先非递归遍历连通图G
              cout<true;//访问第v个顶点,并置访问标志数组相应分量值为true
              InitQueue(Q); //辅助队列Q初始化, 置空
              EnQueue(Q,v);  //v进队
              while { ! QueueEmpty {Q)) //队列非空
              { 
                 DeQueue (Q, u);  //队头元素出队并置为u
                 for(w=FirstAdjVex(G,u);w>=O;w=NextAdjVex(G,u,w)) 
                //依次检查u的所有邻接点w, FirstAdjVex(G,u)表示u的第一个邻接点
                //NextAdjVex(G,u,w)表示u相对于w的下一个邻接点,w;;,.o表示存在邻接点
                if (!visited [w])     // w为u的尚未访问的邻接顶点
               {
                cout<true; //访问 w, 并置访问标志数组相应分扯值为true 
                EnQueue (Q, w) ; //w进队
               }
             }
          }
          View Code
        • 算法分析
          • 广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同,即当用邻接矩阵存储时,时间复杂度为O(n^2 ); 用邻接表存储时,时间复杂度为O(n+ e)。两种遍历方法的不同之处仅仅在于对顶点访问的顺序不同。
    • 6.6 图的应用
      • 6.6.1 最小生成树 
        • 1. 普里姆 Prim算法 
          • 算法实现
            struct 
            {
               VerTexType adjvex; //最小边在U中的那个顶点
               ArcType lowcost; //最小边上的权值
            ) closedge [MVNum) ;
            
            void MiniSpanTree_Prim(AMGraph G,VerTexType u) 
            {//无向网G以邻接矩阵形式存储, 从顶点u出发构造G的最小生成树T, 输出T的各条边
              k=LocateVex(G,u); Ilk 为顶点 u 的下标
              for(j=O;j//对v-u 的每一个顶点 Vj, 初始化 closedge[j]
                 if(j!=k) closedge[j]={u,G.arcs[k][j]}; //{adjvex, lowcost} 
              closedge[k].lowcost=O; //初始, U={u}
              for(i=l;ii) 
              {//选择其余 n-1 个顶点,生成 n-1 条边(n=G.vexnum)
                  k=Min(closedge); 
                  //求出 T 的下一个结点:第 K 个顶点, closedge[k]中存有当前最小边
                  u0=closedge[k] .adjvex; //u0 为最小边的一个顶点,u0∈U
                  v0=G.vexs[k]; //v0 为最小边的另一个顶点, v0∈V-U
                  cout<//输出当前的最小边(u0, v0) 
                  closedge[k] .lowcost=0; 第k个顶点并入u集
                  for(j=O;jj) 
                     if(G.arcs[k] [j]//新顶点并入u 后重新选择最小边
                        closedge [j l={G.vexs [kl ,G.arcs [kl [j l};
                }
            }
            View Code
          • 算法分析
            • 普里姆算法的时间复杂度为 O(n^2 ), 与网中的边数无关, 因此适用千求稠密网的最小生成树。
        • 2. 克鲁斯卡尔 Kruskal算法 
          • 算法实现
            struct 
            {
              VerTexType Head; //边的始点
              VerTexType Tail;   //边的终点
              ArcType lowcost;  //边上的权值
            } Edge [ arcnum] ;
            
            int Vexset[MVNum];
            
            void MiniSpanTree_ Kruskal(AMGraph G) 
            {//无向网G以邻接矩阵形式存储,构造G的最小生成树T, 输出T的各条边
               Sort (Edge); //将数组 Edge 中的元素按权值从小到大排序
               for(i=O;i//辅助数组,表示各顶点自成一个连通分量
                  Vexset[i]=i; 
               for(i=O;i//依次查看数组 Edge 中的边
               {
                  vl=LocateVex(G,Edge[i] .Head);  //vl 为边的始点 Head 的下标
                  v2=LocateVex(G,Edge[i] .Tail); //v2 为边的终点 Tail的下标
                  vsl=Vexset[vl];  //获取边 Edge[i]的始点所在的连通分量 vsl
                  vs2=Vexset[v2]; //获取边 Edge[i]的终点所在的连通分量 vs2
                  if(vsl!=vs2) //边的两个顶点分属不同的连通分量
                 {
                    cout«Edge[i] .Head «Edge[i] .Tail;//输出此边
                    for(j=O;j//合并 VS1 和 VS2 两个分益, 即两个集合统一编号
                    if(Vexset[ j] ==vs2) Vexset [ j] =vsl; / /集合编号为 vs2 的都改为 vsl
                  }
               }
            }
            View Code
          • 算法分析
            • 克鲁斯卡尔算法的时间复杂度为 O(elog泸),与网中的边数有关,与普里姆算法相比,克鲁斯卡尔算法更适合千求稀疏网的最小生成树。
      • 6.6.2 最短路径 
        • 从某个源点到其余各顶点的最短路径
          • 迪杰斯特拉 Dijkstra算法
            • 算法实现
              void   ShortestPath_DIJ(AMGraph G, int v0)
              {//用Dijkstra算法求有向网G的vO顶点到其余顶点的最短路径
                 n=G. vexnum; //n为G 中顶点的个数
                 for (v= O;v//n个顶点依次初始化
                 {
                     S[v]=false; //S初始为空集
                     D[v]=G.arcs[v0][v]; //将v0到各个终点的最短路径长度初始化为弧上的权值
                     if(D[v]//如果v0和v之间有弧, 则将v的前驱置为v0
                     else Path[v]=-1;  //如果v0 和v之间无弧, 则将v的前驱置为-1
                  }
                  S [v0] = true; //将v0加人 S
                  D [v0]=0; //源点到源点的距离为 0
              /*初始化结束, 开始主循环, 每次求得vO到某个顶点v的最短路径, 将v加到s集*/
                  for( i=l; i//对其余 n-1个顶点,依次进行计算
                  {
                     min= Maxlnt; 
                     for(w= 0;ww)
                      if (! S [w] &&D [w] <min)
                         {v=w;min=D[w];}    //选择一条当前的最短路径,终点为v
                     S[v]=true;  //将v加入S
                     for(w=0;w//更新从v。出发到集合v-s上所有顶点的最短路径长度
                         if (! S [w) && (D [v) +G. arcs [v) [w) <D [w])) 
                              { 
                                   D [w] =D [v] +G. arcs [v] [w];   //更新 D[w]
                                   Path[w]=v;   ////更改w的前驱为v
                               }
                    }
              }
              View Code
            • 算法分析
              • 求解最短路径的主循环共进行 n - 1 次, 每次执行的时间是 O(n), 所以算法的时间复杂度是O(n^2)。 如果用带权的邻接表作为有向图的存储结构, 则虽然修改D 的时间可以减少,但由于在D 向址中选择最小分量的时间不变, 所以时间复杂度仍为 O(n2)。
        • 每一对顶点之间的最短路径
          • 弗洛伊德 Floyd算法
            • 算法实现
              void ShortestPath_Floyd(AMGraph G)
              {//用Floyd算法求有向网G中各对顶点1和)之间的最短路径
                  for (i=O; i < G. vexnum; ++i) //各对结点之间初始已知路径及距离
                     for(j=O;j j)
                         {
                            D [ i J [ j J =G. arcs [ i J [ j J ;
                            if(D[i] [j]//如果 l.和]之间有弧,则将j的前驱置为l
                            else Path[i] [j]=-1;
                         }
                   for (k=O; k < G. vexnum; ++k) 
                      for (i=O; i i)
                         for(j=O;j j) 
                            if(D[i] [k]+D[k] [j] //从i经k到]的一条路径更短
                             {
                                  D[i] [j]=D[i] [k]+D[k] [j];//更新D[i) [j J
                                  Path[i] [j]=Path[k] [j];/更改]的前驱为K
                               }
              }
              View Code
      • 6.6.3 拓扑排序 
        • 算法实现
          Status TopologicalSort(ALGraph G,int topo[]) 
          {//有向图G采用邻接表存储结构
             //若 G 无回路,则生成 G 的一个拓扑序列 topo []并返回 OK, 否则 ERROR
                FindinDegree(G,indegree);  //求出各顶点的入度存入数组 indegree中
                InitStack(S);    //栈 s初始化为空
                for(i=O;ii) 
                    if (! indegree [i)) Push (S, i);  //入度为0者进栈
                m=0;   ////对输出顶点计数,初始为0
               while (! StackEmpty (S))   ////栈s非空
               {
                  Pop (S, i); //将栈顶顶点Vi出栈
                  topo[m)=i;   //将Vi保存在拓扑序列数组 topo中
                  ++m;  //对输出顶点计数 
                  p=G.vertices[i) .firstarc;   //p指向Vi的第一个邻接点
                  while (p ! =NULL) 
                 { 
                    k=p->adjvex;   //vk为 m 的邻接点
                    --indegree[k);   //vi的每个邻接点的入度减1
                    if(indegree[k)==0) Push(S,k); //若入度减为0, 则入栈
                    p=p->nextarc;   //p指向顶点Vi下一个邻接结点
                   }
              } 
              if(mreturn ERROR; //该有向图有回路
              else return OK;
          }
          View Code
        • 算法分析
          • 总的时间复杂度为O(n + e)。
      • 6.6.4 关键路径算法 
        • 算法实现
          Status CriticalPath(ALGraph G) 
          {//G为邻接表存储的有向网,输出G的各项关键活动
               if(!TopologicalOrder(G,topo)) return ERROR; 
               //调用拓扑排序算法,使拓扑序列保存在topo中,若调用失败, 则存在有向环, 返回ERROR
               n=G. vexnum; / /n为顶点个数
               for(i=0;i<n; 丘+) //给每个事件的最早发生时间置初值0
                 ve[i]=0;
          /*- - - - 按拓扑次序求每个事件的最早发生时间 ----*/
               for (i=O; i)
               {
                k=topo[i];   //取得拓扑序列中的顶点序号K
                p=G.vertices[k].firstarc; //p指向k的第一个邻接顶点
                while{p!=NULL)
                  {  //依次更新k的所有邻接顶点的最早发生时间
                     j=p->adjvex;   //j为邻接顶点的序号
                     if(ve[j]weight)  //更新顶点 J 的最早发生时间 ve[j]
                          ve[j]=ve[k]+p->weight; 
                     p=p->nextarc;//p指向k的下一个邻接顶点
                   }
               }
               for(i=O;i//给每个事件的最迟发生时间置初值 ve[n-1]
                    vl[i]=ve[n-1];
          /*-------------按逆拓扑次序求每个事件的最迟发生时间------------*/ 
               for(i=n-l;i>=O;i--)
              {
                 k=topo[i);    //取得拓扑序列中的顶点序号K
                 p=G.vertices[k) .firstarc;   //p指向k的第一个邻接顶点
                 while(p!=NULL)       //根据k的邻接点,更新k的最迟发生时间
                 {
                       j=p->adjvex;   //j为邻接顶点的序号
                       if(vl[k]>vl[j)-p->weight)  //更新顶点 K 的最迟发生时间 vi [k]
                              vl[k)=vl[j)-p->weight;
                       p=p->nextarc;   //p指向k的下一个邻接顶点
                 }
              }
          /*---------- - - - -- -判断每一活动是否为关键活动-------*/
            for(i=0;i//每次循环针对m为活动开始点的所有活动
          { 
               p=G.vertices[i) .firs七arc;  //p指向1的第一个邻接项点
               while (p ! =NULL) 
             { 
                   j=p->adjvex;      //j为l.的邻接顶点的序号
                  e=ve[i); //计算活动的最早开始时间
                  l=vl[j)-p->weight;  //计算活动的最迟开始时间
                  if (e==l) //若为关键活动,则输出
                    cout<G.vertices[j] .data;
                 p=p->nextarc;  //p指向 i 的下一个邻接顶点
          
              }
            }
          }
          
          
          
          
                     
          View Code
        • 算法分析
          • 求关键路径算法的时间复杂度为 O(n+e)。
    • 6.7 案例分析与实现 
      • 六度空间理论
    • 6.8 小结
  • 心得体会
    • 第六章主要学习图,有很多概念和算法学习,而且有些算法很容易造成混淆;总体感觉也觉得自己这章学的有点乱,但是经过知识构建,再仔细分析一下不同算法的不同点就可以挺好的区分开来。而且最近老师通过上课演示跟小测题目帮助我们更加的了解,更好的掌握DFS和BFS等算法。
    • 这章主要还是得分清什么情况最好用什么算法,然后每个算法的过程实现又是怎样的,这章内容比较多,需要多看多复习。
    • 有向图十字链表、无向图邻接多重表,最短路径Floyd算法,拓扑排序、关键路径老师都没有教学,自己大概看了看,比较乱比较难,然后就看不进去了,大概理解了一下。
    • 有些错题还是得记录一下
      • 小测:使用邻接矩阵a存储无向网络,若i号顶点与j号顶点之间不存在边,则a[i][j]值为多少?答案:若权值是一个正整数,可以设为0或负数。若权值是整数,则设为一个大于所有边权值的数(INT_MAX)
      • 小测:对于连通图,其连通分量是什么? 本身(注意审题)

你可能感兴趣的:(数据结构 第六章学习小结)