算法导论 22.5-5 O(V+E)求有向图的分支图

一、题目

给出一个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输出的是分支图中和边,边的顶点用联通子图的编号表示
算法导论 22.5-5 O(V+E)求有向图的分支图_第1张图片

你可能感兴趣的:(算法导论 22.5-5 O(V+E)求有向图的分支图)