赞
踩
1.图的强连通分量
(1). 定义
图的强连通分量是图论中的一个重要概念,主要在有向图中进行讨论。具体来说,如果在一个有向图G
中,任意两个顶点vi
和vj
(其中vi
大于vj
)之间都存在一条从vi
到vj
的有向路径,同时也存在一条从vj
到vi
的有向路径,那么这两个顶点就被称为强连通。如果有向图G
的每两个顶点都强连通,那么G
就被称为一个强连通图。
进一步地,有向图G
的极大强连通子图被称为强连通分量。这里,“极大”意味着如果添加任何额外的顶点,子图将不再保持强连通的属性。因此,强连通分量实际上是原图中满足强连通条件的最大的子图。
强连通分量在实际应用中有着重要的价值。例如,在复杂网络分析中,强连通分量可以帮助我们识别出网络中紧密连接、相互依赖的节点群体,这对于理解网络的结构和功能至关重要。此外,强连通分量还可以用于简化图的复杂度,将原图分解为多个更小的强连通分量,从而方便后续的分析和处理。
在算法实现上,有多种方法可以用来寻找有向图中的强连通分量,如Kosaraju
算法、Tarjan
算法和Gabow
算法等。这些算法通常基于深度优先搜索(DFS
)的思想,通过遍历图中的节点和边来识别强连通分量。
(2). Kosaraju
算法求解图的强连通分量
Kosaraju
算法是一种求解有向图强连通分量(Strongly Connected Components, SCC
)的算法。这个算法的基本思想是分两步进行深度优先搜索(DFS
)。首先,对原图进行DFS
,并记录每个节点的完成时间(即退出DFS
的时间)。然后,根据完成时间的逆序(从大到小),对节点进行第二次DFS
,每次DFS
找到的就是一个强连通分量。
(3). 实例
上述是一个图结构.利用Kosaraju
算法可以求解其中每个强连通分量.
// 强连通分量 class NodeInfo{ public: char m_nName; bool m_bVisit = false; int32_t m_nTag = -1; int32_t m_nFirstTick; int32_t m_nLastTick; }; template<class T> class Node{ public: T m_nEle; }; template<class EdgeInfo> class Edge{ public: bool m_bValid = false; }; Node<NodeInfo> stNodes[9]; Edge<int> stEdges[9][9]; Edge<int> stEdges2[9][9]; int nVisitTick = 0; void Visit(int nCurIndex, int32_t nTag = -1); // 对节点按最后访问时间逆序排列 struct VisitInfo{ int32_t nVisitTick; int32_t nIndex; }; void Sort(VisitInfo* lpBegin, VisitInfo *lpEnd); int main(){ stNodes[0].m_nEle.m_nName = 'A'; stNodes[1].m_nEle.m_nName = 'B'; stNodes[2].m_nEle.m_nName = 'C'; stNodes[3].m_nEle.m_nName = 'D'; stNodes[4].m_nEle.m_nName = 'E'; stNodes[5].m_nEle.m_nName = 'F'; stNodes[6].m_nEle.m_nName = 'G'; stNodes[7].m_nEle.m_nName = 'H'; stNodes[8].m_nEle.m_nName = 'I'; stEdges[0][1].m_bValid = true; stEdges[1][0].m_bValid = true; stEdges[1][2].m_bValid = true; stEdges[2][1].m_bValid = true; stEdges[4][5].m_bValid = true; stEdges[5][4].m_bValid = true; stEdges[4][3].m_bValid = true; stEdges[3][4].m_bValid = true; stEdges[4][6].m_bValid = true; stEdges[6][4].m_bValid = true; stEdges[6][8].m_bValid = true; stEdges[8][6].m_bValid = true; stEdges2[1][0].m_bValid = true; stEdges2[0][1].m_bValid = true; stEdges2[2][1].m_bValid = true; stEdges2[1][2].m_bValid = true; stEdges2[5][4].m_bValid = true; stEdges2[4][5].m_bValid = true; stEdges2[3][4].m_bValid = true; stEdges2[4][3].m_bValid = true; stEdges2[6][4].m_bValid = true; stEdges2[4][6].m_bValid = true; stEdges2[8][6].m_bValid = true; stEdges2[6][8].m_bValid = true; for(int i = 0; i < 9; i++){ if(stNodes[i].m_nEle.m_bVisit == false){ Visit(i); } } VisitInfo stArr[9]; for(int32_t i = 0; i < 9; i++){ stArr[i].nIndex = i; stArr[i].nVisitTick = stNodes[i].m_nEle.m_nLastTick; } // 按访问时间逆序排列数组元素 Sort(stArr, stArr + 9); // 构造一个转置图,已经提前构建好.复用了原图的节点结构.深度优先前复位节点状态. for(int32_t i = 0; i < 9; i++){ for(int32_t j = 0; j < 9; j++){ stEdges[i][j] = stEdges2[i][j]; } } for(int32_t i = 0; i < 9; i++){ stNodes[i].m_nEle.m_bVisit = false; stNodes[i].m_nEle.m_nFirstTick = -1; stNodes[i].m_nEle.m_nLastTick = -1; stNodes[i].m_nEle.m_nTag = -1; } // 对转置图按排序后节点顺序再次执行深度搜索 int32_t nTag = 0; for(int32_t i = 0; i < 9; i++){ int32_t nNodeIndex = stArr[i].nIndex; if(stNodes[nNodeIndex].m_nEle.m_bVisit == false){ Visit(nNodeIndex, nTag); nTag++; } } // 此时一次性求出了图的所有强连通分量.tag相同的节点位于同一个强连通分量. printf("strong connect num:%d\n", nTag); for(int32_t i = 0; i < nTag; i++){ printf("%d:\n", i); for(int32_t k = 0; k < 9; k++){ if(stNodes[k].m_nEle.m_nTag == i){ printf("%c ", stNodes[k].m_nEle.m_nName); } } printf("\n"); } return 0; } void Sort(VisitInfo* lpBegin, VisitInfo *lpEnd){ int32_t nNum = lpEnd - lpBegin; if(nNum <= 1){ return; } // 归并排序 int32_t nMid = nNum / 2; Sort(lpBegin, lpBegin+nMid); Sort(lpBegin+nMid,lpEnd); // 归并 VisitInfo* lpArrTmp = (VisitInfo*)malloc(sizeof(VisitInfo) * nNum); int32_t nArrIndex = 0; int32_t nLeftIndex = 0; int32_t nRightIndex = 0; while(nLeftIndex < nMid && nRightIndex < (nNum - nMid)){ if(lpBegin[nLeftIndex].nVisitTick >= lpBegin[nRightIndex+nMid].nVisitTick){ lpArrTmp[nArrIndex] = lpBegin[nLeftIndex]; nArrIndex++; nLeftIndex++; } else{ lpArrTmp[nArrIndex] = lpBegin[nRightIndex+nMid]; nArrIndex++; nRightIndex++; } } if(nLeftIndex < nMid){ for(int32_t i = nLeftIndex; i < nMid; i++){ lpArrTmp[nArrIndex] = lpBegin[i]; nArrIndex++; } } else{ for(int32_t i = nRightIndex; i < (nNum - nMid); i++){ lpArrTmp[nArrIndex] = lpBegin[i+nMid]; nArrIndex++; } } // 设置回去 for(int32_t i = 0; i < nNum; i++){ lpBegin[i] = lpArrTmp[i]; } } // 基础的深度优先重点在于节点遍历.而不是,走完所有可能路径. void Visit(int nCurIndex, int32_t nTag){ nVisitTick++; stNodes[nCurIndex].m_nEle.m_bVisit = true; stNodes[nCurIndex].m_nEle.m_nFirstTick = nVisitTick; stNodes[nCurIndex].m_nEle.m_nTag = nTag; for(int i = 0; i < 9; i++){ if(stEdges[nCurIndex][i].m_bValid && stNodes[i].m_nEle.m_bVisit == false){ Visit(i, nTag); } } stNodes[nCurIndex].m_nEle.m_nLastTick = nVisitTick; nVisitTick++;// 用来保证LastTick不出现重复 }
特意说明下,这里的深度优先访问是最基础的深度优先版本.此版本主要侧重于对每个可达节点执行一次访问.
2.图的最小生成树
(1). 定义
图的最小生成树(Minimum Spanning Tree,MST
)是一个在图论中常见的概念。给定一个加权连通图(即图的每条边都有一个权重,连通图是图中任意两点均可相互到达的意思),最小生成树是一个子图,它包含了原图中的所有顶点,且所有的边都属于原图的边,同时边的权值和在所有这样的子图中是最小的。换句话说,最小生成树是一个连通所有顶点的树,且所有边的权值和最小。(也可说成是代价最小的连通所有顶点的子图)
最小生成树问题是一个优化问题,它在网络设计、电路设计等领域有广泛的应用。例如,在构建通信网络时,我们可能希望以最小的成本连接所有的城市,这就可以通过求解最小生成树问题来实现。
求解最小生成树问题的常见算法有两种:普里姆算法(Prim's algorithm
)和克鲁斯卡尔算法(Kruskal's algorithm
)。
普里姆算法:
图需要是无向图
(1). 将源节点加入当前最小生成树.
(2). 对所有边按权重排序.
(3). 循环迭代:
a. 从所有边中选出跨越当前最小生成树,图剩余部分的权重最小的边.跨越意味着此边一个节点在当前最小生成树中,另一节点不在.
b. 将此边的另一节点也加入当前最小生成树.
(4). 树中所有节点均加入最小生成树后,迭代结束.获得最小生成树.
克鲁斯卡尔算法:
(1).对图中的所有边按照权值大小进行排序.
(2). 按照权值从小到大的顺序依次选择边。
在选择每一条边时,需要判断这条边的两个顶点是否已经在同一个连通分量中,如果是,则跳过这条边;如果不是,则将这条边加入最小生成树中,并更新连通分量的信息。
(2). 实例
可利用上述算法求解上图中的一颗最小生成树.
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。