第六章学习小结
一、本章学习小结
本章学习了图这一复杂的非线性数据结构,图包括有向图和无向图,有向图中又包含始点和终点,弧尾和弧头的概念。两者的本质区别应该是有无序。重点学习了DFS算法和BFS算法实现图的遍历,理解了迪杰斯特拉算法的逻辑思想。
1、图的基本术语(顶点数目为n,边数目为e)
子图、稀疏图和稠密图、权和网、邻接点、度、入度和出度、路径和路径长度、回路或环、连通、连通图和连通分量、强连通图和强连通分量、有向树和生成森林。
有向完全图:具有n(n-1)条边的有向图。 无向完全图:具有n(n-1)/2条边的无向图。
有向图:度=入度+出度。 无向图:无入度出度之分。
路径长度:一条路径上经过的边或弧的数目。
连通分量:无向图中的极大连通子图 。(对于连通图,其连通分量是其本身)
有向图中的极大连通子图称作有向图的强连通分量。
连通图的生成树:一个极小的连通子图包含图中全部顶点,但只有足以构成一棵树的n-1条边。
有向树:有一个顶点的入度为0,其余顶点的入度均为1的有向图。
2、图的存储结构
邻接矩阵表示法:适用于稠密图,时间和空间复杂度均为O(n^2)。
1 #define MaxInt //表示极大值 2 #define MVNum 100 //最大顶点数 3 typedef char VerTexType; //顶点的数据类型 4 typedef int ArcType; //边的权值类型 5 typedef struct 6 { 7 VerTexType vexs[MVNum]; //顶点表 8 ArcType arcs[MVNum][MVNum]; //邻接矩阵 9 int vexnum, arcnum; //图的当前点数和边数 10 }AMGraph;
邻接表表示法:适用于稀疏图,时间和空间复杂度均为O(n+e)。
1 #define MVNum 100 //最大顶点数 2 typedef struct ArcNode //邻接点的结构 3 { 4 int adjvex; //该边所指向的顶点的位置 5 struct ArcNode *nextarc; //指向下一条边的指针 6 OtherInfo info; //和边相关的信息,例如权值 7 }ArcNode; 8 typedef struct VNode //顶点的结构 9 { 10 VerTexType data; //顶点信息 11 ArcNode *firstarc; //指向第一条依附该顶点的弧 12 }VNode, AdjList[MVNum]; 13 typedef struct 14 { 15 AdjList vertices; 16 int vexnum, arcnum; //顶点数和边数 17 }ALGraph;
还有十字链表(有向图)和邻接多重表(无向图)。
3、图的遍历
深度优先遍历(DFS算法):类似树的先序遍历,借助栈结构实现运用递归
1 bool visited[MVNum]; //访问标志数组, 其初值为"false" 2 //连通图 3 void DFS(Graph G, int v) 4 { 5 cout<<v; visited[v]=true; 6 //访问第v个顶点,并置访问标志数组相应分扯值为true 7 for(w=FirstAdjVex(G,v); w>=O; w=NextAdjVex(G,v,w)) 8 //依次检查v的所有邻接点w,FirstAdjVex(G,v)表示v的第一个邻接点 9 //NextAdjVex(G,v,w)表示v相对于w的下一个邻接点,w≧0表示存在邻接点 10 if(!visited[w]) DFS(G,w); 11 //对v的尚未访问的邻接顶点w递归调用 DFS 12 } 13 //非连通图 14 void DFSTraverse(Graph G) 15 { 16 for(v=0; v<G.vexnum; ++v) 17 visited[v]=false; //访问标志数组初始化 18 for(v=0; v<G.vexnum; ++v) //循环调用算法6.3 19 if(!visited[v]) DFS(G, v); //对尚未访问的顶点调用DFS 20 }
广度优先遍历(BFS算法):类似树的层次遍历,借助队列结构
1 void BFS{Graph G, int v) //连通图 2 { 3 cout<<v; 4 visited[v]=true; 5 InitQueue(Q); //辅助队列Q初始化,置空 6 EnQueue(Q, v); //v进队 7 while(!QueueEmpty(Q)) //队列非空 8 { 9 DeQueue(Q, u); //队头元素出队并置为u 10 for(w=FirstAdjVex(G,u); w>=O; w=NextAdjVex(G,u,w)) 11 //依次检查u的所有邻接点w, FirstAdjVex(G,u)表示u的第一个邻接点 12 //NextAdjVex(G,u,w)表示u相对于w的下一个临界点,w≧0表示存在邻接点 13 if(!visited[w]) //w为u的尚未访问的邻接顶点 14 { 15 cout<<w; 16 visited[w]=true; 17 EnQueue(Q, w); //w进队 18 } //if 19 } //while 20 }
DFS算法和BFS算法的空间复杂度相同,均为O(n)(借用了堆栈或队列);时间复杂度雨存储结构(邻接矩阵或邻接表)有关,而与搜索路径无关。(邻接矩阵----时间复杂度为O(n^2),邻接表----时间复杂度为O(n+e))
4、图的应用
1)最小生成树:
普里姆算法:时间复杂度为O(n^2),与网中的边数无关,适用于稠密图。
克鲁斯卡尔算法:时间复杂度为O(eloge),与网中的边数有关,适用于稀疏图。
2)最短路径
迪杰斯特拉算法:源点到其余各点,时间复杂度为O(n^2)。
弗洛伊德算法:每对顶点之间,时间复杂度为O(n^3)。
二、实践心得
1、打代码过程中,接触到一个新函数
//重置visit[]数组
memset(visit, 0, sizeof(visit));
2、拯救007中,刚开始看题目没有头绪,后来查询了一番,将图画了出来,便清晰了然,发现这也是应用了图的遍历,可以使用DFS或BFS解决问题。
3、小测的题目是对图进行遍历,而固定思维总以为是从左至右,从上至下,结果导致出错。日后还是应该具体分析代码。