一、题目
给出一个O(V+E)时间的算法,以计算一个有向图G=(V,E)的分支图。注意在算法产生的分支图中,两个顶点之间至多只能有一条边。
二、思考
没有找到关于有向图的分支图的定义,在网上找了一种解法,根据这个解法的结果推测,有向图的分支图应该是书P338,22-9(c)中顶点合并后的结果。
过程如下:
STEP1:求强联通分量,结果用scc[u]表示,即顶点u属于第scc[u]个强联通分量,O(V+E)
STEP2:按照scc[u]从小到大对顶点排序,O(V)
STEP3:把每个强联通分量的第一个顶点加入到V|SCC中,O(V)
STEP4:计算集合S={(x, y)|edge(u, v)属于E,x=scc[u],y=scc[v],x!=y},O(E)
STEP5:对S做基数排序,即两次计数排序,O(V+E)
STEP6:把每个不同的(x, y)加入到E|SCC中,O(E)
总时间O(V+E)
三、代码
1.基数排序Radix_Sort.h
struct Set
{
int x;
int y;
};
int digit = 2, length_A;
//基数排序调用的稳定排序
void Stable_Sort(Set *A, Set *B, int k, int d)
{
int i, j;
//将C数组初始化为0,用于计数
int *C = new int[k+1];
for(i = 0; i <= k; i++)
C[i] = 0;
int *D = new int[length_A+1];
for(j = 1; j <= length_A; j++)
{
//D[j]表示第[j]个元素的第i位数字
if(d == 1)
D[j] = A[j].x;
else
D[j] = A[j].y;
//C[j]表示数字D[j]在数组A中出现的次数
C[D[j]]++;
}
//C[i]表示所以<=i的数字出现过的次数
for(i = 1; i <= k; i++)
C[i] = C[i] + C[i-1];
//初始化B为0,B用于输出排序结果
// for(i = 1; i <= length_A; i++)
// B[i] = 0;
for(j = length_A; j >= 1; j--)
{
//如果<=D[j]的数字的个数是x,那么排序后A[j]应该出现在第x个位置,即B[x]=A[j]
B[C[D[j]]] = A[j];
C[D[j]]--;
}
delete []C;
delete []D;
}
//基数排序
void Radix_Sort(Set *A, Set *B, int len, int n)
{
int i, j;
length_A = len;
//依次对每一位进行排序,从低位到高位
for(i = 1; i <= digit; i++)
{
Stable_Sort(A, B, n+1, i);
//输入的是A,输出的是B,再次排序时要把输出数据放入输出数据中
for(j = 1; j <= length_A; j++)
A[j] = B[j];
}
}
2.邻接表表示的有向图求分支图Link_Graph.h
#include <iostream>
#include <queue>
using namespace std;
#include "Radix_Sort.h"
#define N 100
#define WHITE 0
#define GRAY 1
#define BLACK 2
#define NONE 0
#define TREE 1
#define BACK 2
#define FORWARD 3
#define CROSS 4
struct Edge
{
int start;
int end;
int value;
int type;
Edge *next;
Edge *DFS_Next;
Edge(int s, int e, int v)
:start(s),end(e),value(v),type(NONE),next(NULL),DFS_Next(NULL){}
};
struct Vertex
{
int id;
int ceil;
int d, f;//第一次被发现的时间和结束检查的时间
int color;//顶点的颜色
int p;//指向遍历结果的父结点
Edge *head;//指向以该顶点为起点的下一条边
Edge *DFS_Head;
Vertex():color(WHITE),p(0),head(NULL),DFS_Head(NULL){};
bool operator<(Vertex &B)
{
return ceil < B.ceil;
}
};
class Link_Graph
{
public:
bool flag;
int time;
int n, m;
int *Sort;
Vertex *V;
int *RetV;
Link_Graph(int num, int m):n(num),m(m),flag(false)
{
V = new Vertex[n+1];
Sort = new int[n+1];
Sort[0] = n;
int i;
for(i = 0; i <= n; i++)
V[i].id = i;
}
~Link_Graph(){delete []V;}
void AddSingleEdge(int start, int end, int value = 1)
{
Edge *NewEdge = new Edge(start, end, value);
if(V[start].head == NULL || V[start].head->end > end)
{
NewEdge->next = V[start].head;
V[start].head = NewEdge;
}
else
{
Edge *e = V[start].head, *pre = e;
while(e != NULL && e->end < end)
{
pre = e;
e = e->next;
}
if(e && e->end == end)
{
delete NewEdge;
return;
}
NewEdge->next = e;
pre->next = NewEdge;
}
}
void AddDoubleEdge(int a, int b, int value = 1)
{
AddSingleEdge(a, b, value);
AddSingleEdge(b, a, value);
}
void DeleteSingleEdge(int start, int end)
{
Edge *e = V[start].head, *pre = e;
while(e && e->end < end)
{
pre = e;
e = e->next;
}
if(e == NULL || e->end > end) return;
if(e == V[start].head)
V[start].head = e->next;
else
pre->next = e->next;
delete e;
}
void DeleteDoubleEdge(int a, int b)
{
DeleteSingleEdge(a, b);
DeleteSingleEdge(b, a);
}
//22.5
//有向图的转置
Link_Graph* Reverse();
void Strongly_Connected_Component();
void SetFlag(bool f){flag = f;}
void DFS();
void DFS_Visit(int u, int ceil);
void Solve();
};
Link_Graph* Link_Graph::Reverse()
{
Link_Graph *ret = new Link_Graph(n, m);
int i;
//遍历图G中的每一条边,以终点为起点,以起点为终点,加入到新图RET中
for(i = 1; i <= n; i++)
{
Edge *e = V[i].head;
while(e)
{
ret->AddSingleEdge(e->end, e->start);
e = e->next;
}
}
ret->Sort = Sort;
return ret;
}
void Link_Graph::DFS()
{
int u, i, ceil = 0;
//对每个顶点初始化
for(u = 1; u <= n; u++)
{
V[u].color = WHITE;
V[u].p = NULL;
}
//时间戳初始化
time = 0;
//依次检索V中的顶点,发现白色顶点时,调用DFS_Visit访问该顶点
for(i = 1; i <= n; i++)
{
if(flag == 0)
u = i;
else
u = Sort[i];
if(V[u].color == WHITE)
{
ceil++;
DFS_Visit(u, ceil);
}
}
}
void Link_Graph::DFS_Visit(int u, int ceil)
{
int v;
Edge *e;
//将u置为灰色
V[u].color = GRAY;
//使全局变量time增值
time++;
//将time的新值记录为发现时间
V[u].d = time;
e = V[u].head;
while(e)
{
v = e->end;
//如果顶点为白色
if(V[v].color == WHITE)
{
//递归访问顶点
V[v].p = u;
DFS_Visit(v, ceil);
//树边
e->type = TREE;
}
else if(V[v].color == GRAY)
{
//反向边
e->type = BACK;
}
else if(V[v].color == BLACK)
{
//正向边
if(V[u].d < V[v].d)
e->type = FORWARD;
//交叉边
else
e->type = CROSS;
}
e = e->next;
}
//以u为起点的所有边都被探寻后,置u为黑色
V[u].color = BLACK;
V[u].ceil = ceil;
//并将完成时间记录在f[u]中
time++;
V[u].f = time;
//把结果按照f从大到小的顺序保存于Sort数组中
if(flag == 0)
{
Sort[Sort[0]] = u;
Sort[0]--;
}
}
void Link_Graph::Strongly_Connected_Component()
{
//第一次DFS,计算每个顶点的f
DFS();
//转置,计算GT
Link_Graph *G2 = Reverse();
//第一次的DFS和第二次的DFS有细微不同,用flag区分
G2->SetFlag(true);
//第二次的DFS,按照f从大到小的顺序
G2->DFS();
int i;
for(i = 0;i <= n; i++)
V[i].ceil = G2->V[i].ceil;
}
//22.5-5
void Link_Graph::Solve()
{
int i;
//STEP1:求强联通分量,结果用scc[u]表示,即顶点u属于第scc[u]个强联通分量,O(V+E)
Strongly_Connected_Component();
//STEP2:按照scc[u]从小到大对顶点排序,O(V)
//在本文中将用V[u].ceil表示scc[u],并对副本进行排序
Vertex *tempV = new Vertex[n+1];
for(i = 1; i <= n; i++)
tempV[i] = V[i];
sort(tempV, tempV+n+1);
//STEP3:把每个强联通分量的第一个顶点加入到V|SCC中,O(V)
RetV = new int[n+1];
RetV[0] = 0;
cout<<"Vertex:"<<endl;
//这里输出的编号是顶点的编号,每个联通子图用一个顶点代表
//按联通子图的编号从小到大的顺序输出
for(i = 1; i <= n; i++)
{
if(tempV[i].ceil != tempV[i-1].ceil)
{
RetV[++RetV[0]] = tempV[i].id;
cout<<V[i].id<<' ';
}
}
cout<<endl;
delete []tempV;
//STEP4:计算集合S={(x, y)|edge(u, v)属于E,x=scc[u],y=scc[v],x!=y},O(E)
Set *A = new Set[m+1];
Set *B = new Set[m+1];
int cnt = 0;
for(i = 1; i <= n; i++)
{
Edge *e = V[i].head;
while(e)
{
int u = e->start, v = e->end;
int x = V[u].ceil, y = V[v].ceil;
if(x != y)
{
cnt++;
A[cnt].x = x;
A[cnt].y = y;
}
e = e->next;
}
}
//STEP5:对S做基数排序,即两次计数排序,O(V+E)
Radix_Sort(A, B, cnt, RetV[0]);
//STEP6:把每个不同的(x, y)加入到E|SCC中,O(E)
//本文中没有保存,而是直接输出
cout<<"Edge:"<<endl;
//这里输出的是连通分量的编号,不是代表联通分量的那个顶点的编号
for(i = 1; i <= cnt; i++)
if(A[i].x != A[i-1].x || A[i].y != A[i-1].y)
cout<<A[i].x<<' '<<A[i].y<<endl;
delete []A;
delete []B;
}
3.主程序main.cpp
#include <iostream>
using namespace std;
#include "Link_Graph.h"
/*
8 14
1 2
2 3
3 4
4 3
5 1
2 5
2 6
3 7
4 8
5 6
6 7
7 6
7 8
8 8
*/
int main()
{
int n, m, i;
int start, end;
//输入点的个数和边的个数
cin>>n>>m;
//构造一个空的图
Link_Graph *G = new Link_Graph(n, m);
//输入边
for(i = 1; i <= m; i++)
{
cin>>start>>end;
G->AddSingleEdge(start, end);
}
G->Solve();
delete G;
return 0;
}
四、代码测试
测试数据是图22-9(a),字母换成了数字。
测试结果中,Vertex输出的是每个联通子图的代表顶点,Edge输出的是分支图中和边,边的顶点用联通子图的编号表示