一、图的两种常用存储结构
邻接矩阵和邻接表存储的都是边的信息,顶点类还需要单独定义和实现。
- 邻接矩阵
特点:无向图中为对称矩阵,适合存储稠密图。 - 邻接表
特点:适合存储稀疏图。
二、图的两种遍历方式
-
深度优先遍历(DFS)
类似于树的先序遍历,采用递归的方法。(以邻接矩阵为例)void CMap::depthFirstTraverse(int nodeIndex){ //输出顶点值 cout<<m_pNodeArray[nodeIndex].m_cData<<" "; //设置当前节点为已经遍历过 m_pNodeArray[nodeIndex].m_bIsVisited = true; //找到与当前节点连接的节点(还需要未被遍历过)进行遍历 for(int i =0;i<m_iCapacity;i++){ if(m_pMatrix[nodeIndex*m_iCapacity+i] && !m_pNodeArray[i].m_bIsVisited){ depthFirstTraverse(i); } } }
-
广度优先遍历
使用队列存储即将访问的节点,与树的层序遍历类似。(以邻接矩阵为例)void CMap::breadthFirstTraverse(int nodeIndex){ queue<int> queue1; queue1.push(nodeIndex);//将起始节点压入堆栈 while(!queue1.empty()){ int temp = queue1.front(); queue1.pop();//取出并删除堆栈第一个节点 if(!m_pNodeArray[temp].m_bIsVisited){ cout<<m_pNodeArray[temp].m_cData<<" "; //将该节点访问状态设置为true m_pNodeArray[temp].m_bIsVisited = true; //找到与该节点相连且未被访问过的节点,压入队列下次访问 for(int i =0;i<m_iCapacity;i++){ if(m_pMatrix[temp*m_iCapacity+i] && !m_pNodeArray[i].m_bIsVisited){ queue1.push(i); } } } } }
三、最短路径的两种算法
-
Dijkstra算法
算法的原理这个视频讲的非常清楚B站Dijkstra算法,大概意思是 每次从未确定的顶点中选择一个 源点到未确定顶点集合中cost最小的点,更新与它相连的其他最小cost 并 将 它的确定状态设置为true,直到遍历到终点。int CMap::dijkstra(int start, int end) { int value[m_iCapacity]; //记录源点到各个点的最小cost for(int i = 0;i<m_iCapacity;i++){ value[i] = 100; //设置初值为一个很大的值 } value[start] = 0; for(int i=0;i<m_iCapacity-1;i++){ int min = 100; //记录每次 int min_index; for(int j = 0;j<m_iCapacity;j++){ //寻找未确定的顶点集合中cost的最小值 if(value[j]<min && !m_pNodeArray[j].m_bIsVisited){ min = value[j]; min_index = j; } } m_pNodeArray[min_index].m_bIsVisited = true; //更新与最小点连接的其他点的最小cost for(int j = 0;j<m_iCapacity;j++){ if(!m_pNodeArray[j].m_bIsVisited && m_pMatrix[min_index*m_iCapacity+j]>0) if(value[min_index]+m_pMatrix[min_index*m_iCapacity+j]<value[j]){ value[j] = value[min_index]+m_pMatrix[min_index*m_iCapacity+j]; } } } return value[end]; }
-
弗洛伊德(Floyed)算法
这个视频B站视频Floyed算法也讲的比较明白,大概思想是遍历每个顶点,更新以当前顶点为中间点的最小cost。比如有ABCD四个点,遍历A时比较 B到C的最小cost 和 B到A的最小cost+A到C的最小cost 两者哪个更小。最终返回起始点到终点的最小cost即可。int CMap::floyed(int start, int end) { int D[m_iCapacity*m_iCapacity]; //存储两点之间cost最小值 int P[m_iCapacity*m_iCapacity]; //存储两点之间经过的最后一个顶点 //初始化 初值为很大的数 for(int i =0;i<m_iCapacity*m_iCapacity;i++){ if(m_pMatrix[i]) D[i] = m_pMatrix[i]; else D[i] = 100; P[i] = i%m_iCapacity; } //以i为中间点,从j到k的cost for(int i =0;i<m_iCapacity;i++){ for(int j=0;j<m_iCapacity;j++){ for(int k=0;k<m_iCapacity;k++){ if(D[j*m_iCapacity+i]+D[i*m_iCapacity+k]<D[j*m_iCapacity+k]){ //当从j到k经过i的cost值最小时,更新从j到k的最小值 D[j*m_iCapacity+k] = D[j*m_iCapacity+i]+D[i*m_iCapacity+k]; //更新从j到k经过的顶点坐标 P[j*m_iCapacity+k] = P[j*m_iCapacity+i] ; } } } } //输出路径 int j = end; cout<<end<<" "; while(start!=j){ cout<<P[j*m_iCapacity+start]<<" "; j = P[j*m_iCapacity+start]; } cout<<endl; //返回最小cost return D[start*m_iCapacity+end]; }
四、最小生成树的两种算法
最小生成树:无回路、V个顶点有V-1条边
最小生成树:包含全部顶点、V-1条边都在图里、任加一条边就会构成回路。
最小生成树: 边的权重之和最小。
B站视频最小生成树
-
Prim普利姆算法
和Dijkstra很像,但也有不同之处
Prim每次比较的是i到j的最小权值,而Dijkstra比较的是从起点到i的最小权值+i到j的最小权值。for(int j = 0;j<m_iCapacity;j++){ if(!m_pNodeArray[j].m_bIsVisited && m_pMatrix[min_index*m_iCapacity+j]>0) //这里的比较不需要加value[min_index] if(m_pMatrix[min_index*m_iCapacity+j]<value[j]){ value[j] = m_pMatrix[min_index*m_iCapacity+j]; } }
-
Kruskal克鲁斯卡尔算法
主要思想:将权值按从小到大排序,从小到大逐个生成图。如果新插入的权值对应的边使得图变成环,就不加入这个权值,直到插入的边的个数为V-1即可。
如何判断图成为环:维护一个顶点个数长度的数组,初始化为节点自身的编号,数组内容记录更新构建最小生成树过程中的根节点,如果一条边起点和终点的根节点一样则会形成环。
如图所示,边排序好之后为{[E,F] [C,D] [D,E] [C,F] [B,F]…}- 最小权值为2,E[E]!=F[F],选择EF,将起点为F的全部改成E ~~~~~~~ E对应的起点为[E],F对应的起点为[E]
- 最小权值为3,C[C]!=D[D],选择CD,将起点为D的全部改成C ~~~~~~ C对应的起点为[C],D对应的起点为[C]
- 最小权值为4,D[C]!=E[E],选择DE,将起点为E的全部改成C ~~~~~~ F、E起点都改为[C]
- 最小权值为5,C[C]==E[C],C和E对应的起点都是C,不选择CE
- 最小权值为6,C[C]==F[C],C和F对应的起点都是C,不选择CF
- 最小权值为7,B[B]!=F[C],选择BF,将起点为C的全部改为B ~~~~~~~ C对应的起点(E、F、C、D)都改为[B]
- 最小权值为8,E[B]!=G[G],选择EG,将起点为G的全部改为B ~~~~~~ G对应的起点都改为[B]
- 最小权值为9,F[B]==G[B],不选择FG
- 最小权值为12,A[A]!=B[B],选择AB,将起点B的全部改为A ~~~~~~~~~ B(B、C、D、E、F、G)对应的起点都改为[A]
~~~~~~ A~G共7个顶点,选择了6条边即可完成选择。
五、拓扑排序
看的视频:浙江大学-数据结构P99
- 一些名词
- AOV网:人们常用有向图来描述和分析一项工程的计划和实施过程,一个工程常被分为多个小的子工程,这些子工程被称为活动(Activity),在有向图中若以顶点表示活动,有向边表示活动之间的先后关系。有向无环图才是合理的。
- 拓扑序列:顶点活动网中将活动按发生的先后次序进行的一种排列。 拓扑排序,是对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。
- 基本思想:维护一个队列,一开始为空。首先将入度为0的点加入队列中,每操作并弹出一个顶点,将与该顶点相连的顶点入度-1,并判断入度是否为0,如果入度为0则加入队列中,直至队列为空。
六、关键路径
- AOE网:在带权有向图中以顶点表示事件,有向边表示活动,边上的权值表示该活动持续的时间。
- 关键路径:路径上各个活动所持续时间之和称为路径长度,则从源点到汇点具有在最大长度的路径即为关键路径。
最大长度路径即加工工序中最耗时的部分,关键路径也就是求加工工序正常完成的最短时间。
~~~~~~ 看的视频:浙江大学-数据结构P100
~~~~~~ 可以看视频进行理解。