---恢复内容开始---
第六章—图
相比于之前学过的线性表和树,图更为复杂,功能也更加强大。图这一章节主要分为:定义和基本术语、存储结构、遍历、应用。
一、定义
概念就不说了,有几点需要注意的地方:
1.顶点v的度指的是和v相关联的边的数目。对于有向图,图的度是入度和出度的总和。
2.连通图和连通分量的概念要注意区分。连通图里任意两个顶点都是连通的,也就是说所有的顶点都是有通路的,而连通分量,就是无向图中的极大连通子图,可以看做是有几个独立开来的网状结构,就有多少个连通分量。
二、图的存储结构
分为邻接矩阵和邻接表,相当于线性结构和链式结构,它们各自有自己的优缺点。
优点 |
缺点 |
适用情况 |
联系 |
时间复杂度 |
|
邻接矩阵 |
容易实现图的操作,如:求某顶点的度、判断顶点之间是否有边、找顶点的邻接点等等 |
n个顶点需要n*n个单元存储边; 空间效率为。对稀疏图而言尤其浪费空间 |
稠密图 |
邻接表中每个链表对应于邻接矩阵中的一行,链表中节点个数等于一行中非零元素的个数 |
O(n2) |
邻接表 |
便于增加和删除顶点,空间效率高 |
不便于实现对图的操作,不便于计算又想吐各个顶点的度 |
稀疏图 |
O(n+e) |
邻接矩阵比较好理解,我主要谈谈我对邻接表的理解,邻接表的存储表示包含三个结构体:
1. 表头结点表:包含数据域和链域,主要是用来记录结点信息的。
1 typedef struct { 2 VerTexType data; // 顶点信息 3 ArcNode *firstarc; 4 // 指向第一条依附该顶点的弧 5 } VNode, AdjList[MVNUM];
2.边表:包含邻接点域、数据域和链域,主要是用来记录边信息的(包括权值)
1 typedef struct ArcNode { 2 int adjvex; //该边所指向的顶点的位置 3 struct ArcNode *nextarc; //指向下一条边的指针 4 OtherInfo info; //和边相关的信息,例如权值 5 } ArcNode;
3.图结构:包含了以上两种结构体和图当前的顶点数和边数
typedef struct { AdjList vertices; int vexnum, arcnum; //顶点数和边数 } ALGraph;
三、图的遍历
DFS(深度优先搜索):
1.从顶点v出发,访问v,并置visited[v]的值为true
2. 然后依次从v的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到
BFS(广度优先搜索):
1.从图中某个顶点v出发,访问v,并置visited[v]的值为true,然后将v 进队。
2.只要队列不空,则重复下述处理:
(1)队头顶点u出队。
(2)依次检查u的所有邻接点w,如果visited[w]的值为false,则访问w,并置visited[w]的值为true,然后将w 进队。
四、图的应用
主要讲述了两个算法内容,普里姆算法和克鲁斯卡尔算法。
五、代码题解—列出连通图
步骤如下:
1.建立图
2.深度优先搜索
3.重置标记数组
4.广度优先搜索
第一步 建立图
由于题目说“总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。”,而且数据量不大,因此我选择了用邻接矩阵来建图。又因为在输入的时候直接输入了两个相连的顶点,因此我没有建立顶点表,直接设立了邻接矩阵。
1 typedef struct{ 2 3 int arcs[MVNum][MVNum];//邻接矩阵 4 int N,E; //图的当前点数和边数 5 6 }AMGraph;
1 //采用邻接矩阵表示法创建无向图G 2 void CreatUDN(AMGraph &G){ 3 4 int v1,v2,i,j,k; 5 cin>>G.N>>G.E;//输入总顶点数、总边数 6 7 for(i=0;i//初始化邻接矩阵,将边的权值初始化为0 8 for(j=0;j j){ 9 G.arcs[i][j]=0; 10 } 11 } 12 for(k=0;k //构造邻接矩阵 13 cin>>v1>>v2; //输入一条边依附的两个顶点 14 G.arcs[v1][v2]=1;//置边 和 15 G.arcs[v2][v1]=1; 16 } 17 };的边的权值为1
第二步 深度优先搜索
这里分为了两部分:由于可能存在多个连通分量,因此要多次调用DFS_AM函数,在这里调用多少次就说明图中有多少个连通分量。然后就是依次检查邻接矩阵v所在的行。
1 //采用邻接矩阵表示非连通图的深度优先搜索遍历 2 void DFS_AM(AMGraph G,int v){ 3 4 int w; 5 cout<" "; 6 visited[v]=true;//访问第v个顶点,并置访问标志数组相应分量的值为true 7 for(w=0;w ){ 8 if((G.arcs[v][w]!=0)&&(!visited[w]))//依次检查邻接矩阵v所在的行 9 DFS_AM(G,w); 10 }//G.arcs[v][w]!=0表示w是v的邻接点,如果w未访问,则递归调用DFS_AM 11 }; 12 13 void DFSTraverse(AMGraph G){ 14 15 int v; 16 for(v=0;v v){ 17 if(!visited[v]){ 18 cout<<"{"<<" "; 19 DFS_AM(G,v); 20 cout<<"}"<<endl; 21 }//对尚未访问的顶点调用DFS_AM 22 } 23 24 };
第三步 重置标记数组(这里容易忽略)
标记数组:
1 bool visited[MVNum]={false};//访问标志数组初始化
重置:
1 //重置标志数组,使其恢复到初始化状态 2 void restore(bool visited[],AMGraph G){ 3 int i; 4 for(i=0;ii){ 5 visited[i]=false; 6 } 7 };
第四步 广度优先搜索
这里我让所有的元素先入队,然后记录队列的第一个元素并进行出队,再判断出队元素的邻接点是否已经被访问过。
1 //采用邻接矩阵表示非连通图的广度优先搜索遍历 2 void BFS_AM(AMGraph G,int v){ 3 4 visited[v]=true;//访问第v个顶点,并置访问标志数组相应分量值为true 5 queue <int> q;//队列初始化 6 int w,u; 7 q.push(v);//v进队 8 while(!q.empty()){ 9 w = q.front();//队头元素出队并置为w 10 q.pop(); 11 cout<" "; 12 for(u=0;u ){ 13 if((G.arcs[w][u]!=0)&&(!visited[u])){//若i为w尚未访问的邻接顶点,则标志数组置为true,u进队 14 visited[u]=true; 15 q.push(u); 16 } 17 } 18 } 19 }; 20 21 void BFSTraverse(AMGraph G){ 22 23 int v; 24 for(v=0;v v){ 25 if(!visited[v]){ 26 cout<<"{"<<" "; 27 BFS_AM(G,v); 28 cout<<"}"<<endl; 29 }//对尚未访问的顶点调用BFS_AM 30 } 31 };
小结:
图这章知识点比较难理解,特别是邻接表、DFS、BFS,所以做题的进度比较慢,希望下次能够当天消化好老师上课所讲的内容,尽可能早完成所有题解。