mirror of
https://github.com/kangjianwei/Data-Structure.git
synced 2026-02-07 17:01:51 +08:00
263 lines
7.2 KiB
C
263 lines
7.2 KiB
C
/*==================
|
||
* 有向图的强连通分量
|
||
===================*/
|
||
|
||
#include "StronglyConnectedComponents.h"
|
||
|
||
|
||
/* 全局变量 */
|
||
static int count; // 对访问过的结点计数
|
||
|
||
// Kosaraju算法
|
||
static Boolean visited[MAX_VERTEX_NUM]; // 访问标志数组,记录访问过的顶点
|
||
static int finished[MAX_VERTEX_NUM]; // 存储深度优先遍历中访问到的元素
|
||
|
||
// Tarjan算法
|
||
static int dfs[MAX_VERTEX_NUM]; // 深度优先遍历时依次访问到的元素(的序号)
|
||
static int low[MAX_VERTEX_NUM]; // low[v] ==> 元素v可以回溯到的最早祖先
|
||
static int stack[MAX_VERTEX_NUM]; // 存储遍历过程中遇到的结点
|
||
|
||
|
||
/*
|
||
* (1) Tarjan算法求取强连通分量
|
||
*
|
||
* 核心思想是深度优先遍历+回溯,与本章第8节求关节点的算法有类似之处
|
||
*/
|
||
void Tarjan(OLGraph G) {
|
||
int i;
|
||
|
||
count = -1; // 初始化计数
|
||
|
||
// 初始化顶点为尚未访问
|
||
for(i = 0; i < G.vexnum; i++) {
|
||
dfs[i] = -1;
|
||
low[i] = -1;
|
||
}
|
||
|
||
printf("打印当前有向图中的各组强连通分量:\n");
|
||
|
||
for(i = 0; i < G.vexnum; i++) {
|
||
if(dfs[i] == -1) {
|
||
DFS_Tarjan(G, i);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 深度优先遍历顶点v可达的所有元素,并尝试找出其中的强连通分量
|
||
*/
|
||
static void DFS_Tarjan(OLGraph G, int v) {
|
||
int min;
|
||
int w;
|
||
int i;
|
||
|
||
++count;
|
||
|
||
// 记录访问次序
|
||
dfs[v] = min = count;
|
||
|
||
// 记录遇到的结点
|
||
stack[count] = v;
|
||
|
||
// 深度遍历v起始的图
|
||
for(w = FirstAdjVex(G, G.xlist[v].data); w >= 0; w = NextAdjVex(G, G.xlist[v].data, G.xlist[w].data)) {
|
||
// 如果w结点未访问
|
||
if(dfs[w] == -1) {
|
||
DFS_Tarjan(G, w); // 对尚未访问的顶点调用DFS
|
||
|
||
if(low[w] < min) {
|
||
min = low[w];
|
||
}
|
||
|
||
// 遇到已访问过的结点
|
||
} else {
|
||
// 如果w已被访问过,且w是v在生成树上的祖先
|
||
if(dfs[w] < min) {
|
||
min = dfs[w];
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果"祖先"结点还没有确定回溯祖先,则只记录"祖先"结点上的访问次序
|
||
if(low[stack[min]]==-1) {
|
||
low[v] = min;
|
||
} else {
|
||
// 获取"祖先"结点上的可回溯祖先
|
||
low[v] = low[stack[min]];
|
||
}
|
||
|
||
// 获取当前已搜索到的强连通子集
|
||
if(low[v] == dfs[v]) {
|
||
for(i = count; i >= 0; i--) {
|
||
if(low[stack[i]] == dfs[v]) {
|
||
printf("%c ", GetVex(G, stack[i]));
|
||
count--;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
printf("\n");
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
* (2) Kosaraju算法求取强连通分量,这是教材中提到的算法
|
||
*
|
||
* 核心思想是:
|
||
* 1.从某顶点出发,深度遍历有向图,得到一个访问序列;
|
||
* 2.逆置有向图;
|
||
* 3.遍历上面得出的访问序列,对其中的每个顶点进行深度优先遍历。
|
||
*/
|
||
void Kosaraju(OLGraph G) {
|
||
int i, v;
|
||
|
||
count = 0; // 初始化计数
|
||
|
||
// 1.从某顶点出发,深度遍历有向图,得到一个访问序列
|
||
DFSTraverse(G, StoreElem);
|
||
|
||
// 2.逆置有向图
|
||
InverseGraph(&G);
|
||
|
||
// 访问标志数组初始化
|
||
for(i = 0; i < G.vexnum; i++) {
|
||
visited[i] = FALSE;
|
||
}
|
||
|
||
printf("打印当前有向图中的各组强连通分量:\n");
|
||
|
||
// 3.遍历上面得出的访问序列,对其中的每个顶点进行深度优先遍历
|
||
for(i = 0; i < count; i++) {
|
||
v = LocateVex(G, finished[i]);
|
||
|
||
if(visited[v] == TRUE) {
|
||
continue;
|
||
}
|
||
|
||
DFS_Inverse(G, v);
|
||
printf("\n");
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 逆置有向图(十字链表存储结构),即将所有弧的方向逆置
|
||
*
|
||
* 核心思想是:
|
||
* 1.创建一个不包含任何弧的空图;
|
||
* 2.依次遍历每个顶点的每条入弧;
|
||
* 3.摘下遇到的入弧,将其插入到空图中变成出弧。
|
||
*/
|
||
static void InverseGraph(OLGraph* G) {
|
||
OLGraph newG = *G;
|
||
int i;
|
||
ArcBox* r;
|
||
ArcBox* pre, * p;
|
||
int tmp;
|
||
|
||
for(i = 0; i < newG.vexnum; i++) {
|
||
newG.xlist[i].firstin = NULL;
|
||
newG.xlist[i].firstout = NULL;
|
||
}
|
||
|
||
for(i = 0; i < (*G).vexnum; i++) {
|
||
while((*G).xlist[i].firstin != NULL) {
|
||
// 摘下遇到的指向顶点i的入弧
|
||
r = (*G).xlist[i].firstin;
|
||
(*G).xlist[i].firstin = r->hlink;
|
||
|
||
// 逆置弧的方向
|
||
tmp = r->headvex;
|
||
r->headvex = r->tailvex;
|
||
r->tailvex = tmp;
|
||
|
||
r->hlink = NULL;
|
||
r->tlink = NULL;
|
||
|
||
// 插入到新图的纵向上
|
||
pre = newG.xlist[r->headvex].firstin;
|
||
if(pre == NULL) {
|
||
newG.xlist[r->headvex].firstin = r;
|
||
} else {
|
||
if(r->tailvex < pre->tailvex) {
|
||
r->hlink = newG.xlist[r->headvex].firstin;
|
||
newG.xlist[r->headvex].firstin = r;
|
||
} else if(r->tailvex > pre->tailvex) {
|
||
p = pre->hlink;
|
||
|
||
while(p != NULL && r->tailvex > p->tailvex) {
|
||
pre = p;
|
||
p = p->hlink;
|
||
}
|
||
|
||
if(p == NULL || r->tailvex < p->tailvex) {
|
||
r->hlink = p;
|
||
pre->hlink = r;
|
||
}
|
||
} else {
|
||
// 等于时不处理
|
||
}
|
||
}
|
||
|
||
// 插入到新图的横向上
|
||
pre = newG.xlist[r->tailvex].firstout;
|
||
if(pre == NULL) {
|
||
newG.xlist[r->tailvex].firstout = r;
|
||
} else {
|
||
if(r->headvex < pre->headvex) {
|
||
r->tlink = newG.xlist[r->tailvex].firstout;
|
||
newG.xlist[r->tailvex].firstout = r;
|
||
} else if(r->headvex > pre->headvex) {
|
||
p = pre->tlink;
|
||
|
||
while(p != NULL && r->headvex > p->headvex) {
|
||
pre = p;
|
||
p = p->tlink;
|
||
}
|
||
|
||
if(p == NULL || r->headvex < p->headvex) {
|
||
r->tlink = p;
|
||
pre->tlink = r;
|
||
}
|
||
} else {
|
||
// 等于时不处理
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
*G = newG;
|
||
}
|
||
|
||
/*
|
||
* 存储深度优先遍历中访问到的元素
|
||
*/
|
||
static Status StoreElem(VertexType c) {
|
||
finished[count++] = c;
|
||
return OK;
|
||
}
|
||
|
||
/*
|
||
* 深度遍历已经逆置后的图
|
||
*/
|
||
static void DFS_Inverse(OLGraph G, int v) {
|
||
int w;
|
||
|
||
// 如果已访问过,直接返回
|
||
if(visited[v] == TRUE) {
|
||
return;
|
||
}
|
||
|
||
// 从第v个顶点出发递归地深度优先遍历图G
|
||
visited[v] = TRUE;
|
||
|
||
// 打印访问到的顶点
|
||
printf("%c ", GetVex(G, v));
|
||
|
||
for(w = FirstAdjVex(G, G.xlist[v].data);
|
||
w >= 0;
|
||
w = NextAdjVex(G, G.xlist[v].data, G.xlist[w].data)) {
|
||
DFS_Inverse(G, w); // 对尚未访问的顶点调用DFS
|
||
}
|
||
}
|