数据结构总结笔记6:图

一、考试内容:

1、图的定义及基本术语;

:记为  G=( V, E )   [V=vertex   E=edge]  其中:V 是G的顶点集合,是有穷非空集; E 是G的边集合,是有穷集。

术语:有向图:每条边都是有方向  弧  <v,w> ;无向图:每条边都是无方向  图G中的;完全图:图G任意两个顶点都有一条边相连接  边  (v,w);

若 n 个顶点的无向图有 n(n-1)/2 条边, 称为无向完全图  ;若 n 个顶点的有向图有n(n-1) 条边, 称为有向完全图

其他术语:稀疏图、稠密图、子图、带权图、网络、连通图、强连通图、邻接点、度、入度、出度、生成树、生成森林、路径、路径长度、简单路径、回路。属于基本概念,在此不再赘述,不理解的请自行百度。

2,图的存储结构;

特点:非线性结构(m:n)

顺序存储结构:无!多个顶点,无序可言,但可用数组描述元素间关系。——邻接矩阵

链式存储结构:可用多重链表:邻接表、十字链表、邻接多重链表

1).邻接矩阵(数组)表示法

建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间关系)。

设图 A = (V, E) 有 n 个顶点,则图的邻接矩阵是一个二维数组 A.Edge[n][n],定义为:

 

分析1:无向图的邻接矩阵是对称的;分析2:顶点i 的度=第 i 行 (列) 中1 的个数;

特别:完全图的邻接矩阵中,对角元素为0,其余全1。

在有向图的邻接矩阵中, 第i行含义:以结点vi为尾的弧(即出度边); 第i列含义:以结点vi为头的弧(即入度边)。

分析1:有向图的邻接矩阵一般是不对称的。

分析2:顶点的出度=第i行元素之和,OD( Vi )=\sum A.Edge[ i ][j ] 

顶点的入度=第i列元素之和。ID( Vi )=\sum A.Edge[ i ][j ]

顶点的度=第i行元素之和+第i列元素之和,即:TD(Vi)=OD( Vi )  + ID( Vi )

 

邻接矩阵法的优点:容易实现图的操作,如:求某顶点的度、判断顶点之间是否有边(弧)、找顶点的邻接点等等。

缺点:n个顶点需要n*n个单元存储边(弧);空间效率为O(n2)。 对稀疏图而言尤其浪费空间。

对于n个顶点的图或网,空间效率=O(n2)

 

2).邻接表(链式)表示法

对每个顶点vi 建立一个单链表,把与vi有关联的边的信息(即边或出度边)链接起来,表中每个结点都设为3个域;

每个单链表还应当附设一个头结点(设为2个域),存vi信息;每个单链表的头结点另外用顺序存储结构存储。

邻接表存储法的特点:其实是对邻接矩阵法的一种改进

分析1: 对于n个顶点e条边的无向图,邻接表中除了n个头结点外,只有2e个表结点,空间效率为O(n+2e)。

若是稀疏图(e<<n2),则比邻接矩阵表示法O(n2)省空间。

分析2: 在有向图中,邻接表中除了n个头结点外,只有e个表结点,空间效率为O(n+e)。若是稀疏图,则比邻接矩阵表示法合适。

邻接表的优点:空间效率高;容易寻找顶点的邻接点;

缺点:判断两顶点间是否有边或弧,需搜索两结点对应的单链表,没有邻接矩阵方便。

 

邻接表与邻接矩阵有什么异同之处?(简答)

1. 联系:邻接表中每个链表对应于邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数。

2. 区别:① 对于任一确定的无向图,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)。② 邻接矩阵的空间复杂度为O(n2),而邻接表的空间复杂度为O(n+e)。

3. 用途:邻接矩阵多用于稠密图的存储(e接近n(n-1)/2);而邻接表多用于稀疏图的存储(e<<n2)

 

十字链表  适用于有向图

思路:将邻接矩阵用链表存储,是邻接表、逆邻接表的结合。

1、每条弧对应一个结点(称为弧结点,设5个域)  2、每个顶点也对应一个结点(称为顶点结点,设3个域)

 

邻接多重表  适用于无向图

当对边操作时,无向图应采用此种结构存储。

1、每条边只对应一个结点(称为边结点),设立6个域;  2、每个顶点也对应一个结点(顶点结点),设立2个域;

 

3,图的两种遍历方法及算法;

遍历定义:从已给的连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且使每个顶点仅被访问一次,就叫做图的遍历,它是图的基本运算。    遍历实质:找每个顶点的邻接点的过程。

图的特点:图中可能存在回路,且图的任一顶点都可能与其它顶点相通,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点。

怎样避免重复访问? 可设置一个辅助数组 visited [n ],用来标记每个被访问过的顶点。它的初始状态为0,在图的遍历过程中,一旦某一个顶点i 被访问,就立即改 visited [i]为1,防止它被多次访问。

 

深度优先搜索( DFS )  仿树的先序遍历过程

从图中某个顶点V0 出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到。如果此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点做起点,重复上述过程,直至所有顶点被访问。    开辅助数组 visited [n ]

稠密图适于在邻接矩阵上进行深度遍历  O(n2);稀疏图适于在邻接表上进行深度遍历  O(n+e)。

void DFS(Graph G, int v) {

   // 从顶点v出发,深度优先搜索遍历连通图 G

    visited[v] = TRUE;   VisitFunc(v);

    for(w=FirstAdjVex(G, v);

             w!=0; w=NextAdjVex(G,v,w))

        if (!visited[w])  DFS(G, w);     

              // 对v的尚未访问的邻接顶点w

              // 递归调用DFS

} // DFS



非连通图的深度优先搜索遍历

void DFSTraverse(Graph G,

                             Status (*Visit)(int v)) {

   // 对图 G 作深度优先遍历。

  VisitFunc = Visit;   

  for (v=0; v<G.vexnum; ++v)

     visited[v] = FALSE; // 访问标志数组初始化

  for (v=0; v<G.vexnum; ++v)

     if (!visited[v])  DFS(G, v);

     // 对尚未访问的顶点调用DFS

}

 

广度优先搜索( BFS )  仿树的层次遍历过程

广度优先搜索是一种分层的搜索过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有回退的情况。因此,广度优先搜索不是一个递归的过程,其算法也不是递归的。   除辅助数组visited [n ]外,还需再开一辅助队列!

void BFSTraverse(Graph G,Status (*Visit)(int v)){

   for (v=0; v<G.vexnum; ++v)

       visited[v] = FALSE;  //初始化访问标志

   InitQueue(Q);       // 置空的辅助队列Q

   for ( v=0;  v<G.vexnum;  ++v )

      if ( !visited[v]) {          // v 尚未访问

           

    }

  } // BFSTraverse

visited[v] = TRUE;  Visit(v);    // 访问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])  {

         visited[w]=TRUE;  Visit(w);

         EnQueue(Q, w); // 访问的顶点w入队列

      } // if

} // while

 

DFS与BFS之比较:

空间复杂度相同,都是O(n)(借用了堆栈或队列);

时间复杂度只与存储结构(邻接矩阵或邻接表)有关,而与搜索路径无关。

 

 

4,图的连通性;

思考1:对连通图进行遍历,得到的是什么? ——得到的将是一个极小连通子图,即图的生成树!

由深度优先搜索得到的生成树,称为深度优先搜索生成树。由广度优先搜索得到的生成树,称为广度优先搜索生成树。

 

思考2:对非连通图进行遍历,得到的是什么?  —— 得到的将是各连通分量的生成树,即图的生成森林!

5,最小生成树。 

 寻找一个各边权值之和最小的生成树    构造最小生成树的准则:1,必须只使用该网络中的边来构造最小生成树;2,必须使用且仅使用n-1条边来联结网络中的n个顶点;3,不能使用产生回路的边。

 

Prim(普里姆)算法     将顶点归并,与边数无关,适于稠密网。用邻接矩阵表示

  取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。在添加的顶点 w 和已经在生成树上的顶点v 之间必定存在一条边,并且该边的权值在所有连通顶点 v 和 w 之间的边中取值最小。之后继续往生成树上添加顶点,直至生成树上含有 n 个顶点n-1条边为止。

设:N =(V,E)是个连通网,另设U为最小生成树的顶点集,TE为最小生成树的边集。

  • (1)初始状态:U ={u0 },( u0∈V),  TE={  },
  • (2)从E中选择顶点分别属于U、V-U两个集合、且权值最小的边( u0, v0),将顶点v0归并到集合U中,边(u0, v0)归并到TE中;
  • (3)直到U=V为止。此时TE中必有n-1条边,T=(V,{TE})就是最小生成树。

Kruskal(克鲁斯卡尔)算法   将边归并,适于求稀疏网的最小生成树。    用邻接表来存储

(1) 首先构造一个只有n个顶点但没有边的非连通图T={V,Æ}, 图中每个顶点自成一个连通分量。

(2) 当在边集 E 中选到一条具有最小权值的边时,若该边的两个顶点落在T中不同的连通分量上,则将此边加入到生成树的边集合T 中;否则将此边舍去,重新选择一条权值最小的边。

(3) 如此重复下去,直到所有顶点在同一个连通分量上为止。此时的T即为所求(最小生成树)。

 

6,拓扑排序和关键路径;

拓扑排序:由某个集合上的一个偏序得到的该集合上的一个全序,这个操作称之为拓扑排序。

直观看,偏序指集合中仅有部分成员之间可比较,而全序指集合中全体成员之间均可比较。

实质:对一个有向无环图中的顶点排成一个具有前后次序的线性序列。

方法:1,输入AOV网络。令 n 为顶点个数。

 2,在AOV网络中选一个没有直接前驱的顶点, 并输出之;

 3,从图中删去该顶点, 同时删去所有它发出的有向边;

 4,重复以上 2、3 步, 直到:全部顶点均已输出,拓扑有序序列形成,拓扑排序完成;或:图中还有未输出的顶点,但已跳出处理循环。这说明图中还剩下一些顶点,它们都有直接前驱,再也找不到没有前驱的顶点了。这时AOV网络中必定存在有向环。

 

关键路径:完成整个工程的最短时间就是关键路径的长度。

顶点的最早时间:起始顶点0;从前往后算,依次 + 路径权值,注意:取最大

顶点的最晚时间:终点的最早时间;从后往前算,依次 — 路径权值,注意:取最小

边/活动的最早时间:边的起点的最早开始时间。

边/活动的最晚时间:边的终点的最晚开始时间—边的路径权值。

7,最短路径;

|——迪杰斯特拉算法:单一给定原点的最短路径

|——弗洛伊德算法:任一点的最短路径

 

二、考试要求:

了解图的逻辑结构定义,

掌握图的术语、图的两种常用存储结构,即邻接矩阵和邻接表。

在算法实现方面要求,熟练掌握图的两种遍历方法,并能够根据图的基本原理解决一些应用问题,如:判定图的连通性、判定是否有环、计算特定路径等。

 

 

 

发布了38 篇原创文章 · 获赞 20 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_40165004/article/details/100637190