最近随着对tarjan算法理解的加深,我发现用另外一种途径实现tarjan的方法,且可以省去DFN数组,大大节省了空间。经过大量测试,已经无误。以下将分阶段阐述进行优化的过程。
第一阶段
下面来说一下我做出此优化的思路。设任意两个节点为u,v。纵观整个tarjan算法,我们发现,DFN数组被调用的地方只有两个:在搜索中将DFN[u]与low[v]比较大小和在回溯中与low[u]比较是否相等。我在这里将DFN的职责分别分给low与flag。
(一)在比较大小时将low[v],low[u]直接比较,经过思考我们可以发现,在搜索中,即在不重复访问节点时,比较DFN[u]和low[v]与直接比较low[u],low[v]是等效的。而同时将DFN反映是否访问过某个节点的功能交给flag,访问过的节点记flag为2。
(二)关于在回溯中与low[u]比较是否相等,从本质上探究这一操作,我发现这实际上是一种确认,即确定这个节点的low是否被修改过。由此,我们也可以将这个职责分给flag,记被修改过low的,存在于栈中的节点的flag为-1。到这一步,DFN就没有存在的必要了。
综上所述,将flag数组从bool型改为int型,综合算下来能省下一个bool型数组的空间大小。但仍然省的不多,因此有了第二阶段的优化。以下是目前的代码:(m为边数,n为顶点数,Anemone为tarjan函数,near是邻接表,个人习惯,求原谅orz)
(别着急,第二阶段将在代码后面继续论述,对这个代码不感兴趣的大佬们也可以跳过直接看第二阶段。)
#include<iostream> #include<cstdio> using namespace std; struct near { int num,nex; }ne[10000010]; int h[10000010],flag[10000010],low[10000010],z[10000010],wh=0,cont=0; int Anemone(int x) { int no,mem; wh++; cont++; z[cont]=ne[x].num; flag[ne[x].num]++; low[ne[x].num]=wh; no=h[ne[x].num]; for(;;) { if(no==0) { break; } if(flag[ne[no].num]==0) { mem=Anemone(no); if(mem<low[ne[x].num]) { low[ne[x].num]=mem; flag[ne[x].num]=-1; } } else if(flag[ne[no].num]==1||flag[ne[no].num]==-1) { if(low[ne[no].num]<low[ne[x].num]) { low[ne[x].num]=low[ne[no].num]; flag[ne[x].num]=-1; } } no=ne[no].nex; } if(flag[ne[x].num]==1) { for(;;) { printf("%d ",z[cont]); if(flag[z[cont]]==1) { flag[z[cont]]=2; cont--; break; } else { flag[z[cont]]=2; cont--; } } printf(" "); } return low[ne[x].num]; } int main() { freopen("yangli.out","r",stdin); freopen("dan.out","w",stdout); int n,m,mem,no; scanf("%d%d",&n,&m); int i,x,y; for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); mem=h[x]; h[x]=i; ne[i].num=y; ne[i].nex=mem; } for(i=1;i<=n;i++) { if(flag[i]==0) { wh++; cont++; z[cont]=i; flag[i]++; low[i]=wh; no=h[i]; for(;;) { if(no==0) { break; } if(flag[ne[no].num]==0) { mem=Anemone(no); if(mem<low[i]) { low[i]=mem; flag[i]=-1; } } else if(flag[ne[no].num]==1||flag[ne[no].num]==-1) { if(low[ne[no].num]<low[i]) { low[i]=low[ne[no].num]; flag[i]=-1; } } no=ne[no].nex; } if(flag[i]==1) { for(;;) { printf("%d ",z[cont]); if(flag[z[cont]]==1) { flag[z[cont]]=2; cont--; break; } else { flag[z[cont]]=2; cont--; } } printf(" "); } } } return 0; }
第二阶段(见证奇迹的时刻!(大雾))
仔细观察第一阶段,可以看出,flag的值实际上只有四种值,即-1,0,1,2。这么几个值就开个int数组真的是相当的浪费,但却又没有办法使用bool,处于非常尴尬的境地。这时候,我突然想到了一种魔性做法--使用char型变量存储!
char型只占一个字节,用来干这种事可谓再好不过了。需要注意的是,char值不能为-1(好像不能吧,记不清了,记错了大家也别那么较真),因此将-1,0,1,2对应的改为0,1,2,3。
最后,终于成功省掉了相当于一个ing型数组大小的内存。下面是最终代码:(m为边数,n为顶点数,Anemone为tarjan函数,near是邻接表,个人习惯,求原谅orz)
#include<iostream> #include<cstdio> using namespace std; struct near { int num,nex; }ne[10000010]; int h[10000010],low[10000010],z[10000010],wh=0,cont=0; char flag[10000010]; int Anemone(int x) { int no,mem; wh++; cont++; z[cont]=ne[x].num; flag[ne[x].num]=2; low[ne[x].num]=wh; no=h[ne[x].num]; for(;;) { if(no==0) { break; } if(flag[ne[no].num]==1) { mem=Anemone(no); if(mem<low[ne[x].num]) { low[ne[x].num]=mem; flag[ne[x].num]=0; } } else if(flag[ne[no].num]==2||flag[ne[no].num]==0) { if(low[ne[no].num]<low[ne[x].num]) { low[ne[x].num]=low[ne[no].num]; flag[ne[x].num]=0; } } no=ne[no].nex; } if(flag[ne[x].num]==2) { for(;;) { printf("%d ",z[cont]); if(flag[z[cont]]==2) { flag[z[cont]]=3; cont--; break; } else { flag[z[cont]]=3; cont--; } } printf(" "); } return low[ne[x].num]; } int main() { freopen("yangli.out","r",stdin); freopen("dan.out","w",stdout); int n,m,mem,no; scanf("%d%d",&n,&m); int i,x,y; for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); mem=h[x]; h[x]=i; ne[i].num=y; ne[i].nex=mem; } for(i=1;i<=n;i++) { flag[i]=1; } for(i=1;i<=n;i++) { if(flag[i]==1) { wh++; cont++; z[cont]=i; flag[i]=2; low[i]=wh; no=h[i]; for(;;) { if(no==0) { break; } if(flag[ne[no].num]==1) { mem=Anemone(no); if(mem<low[i]) { low[i]=mem; flag[i]=0; } } else if(flag[ne[no].num]==2||flag[ne[no].num]==0) { if(low[ne[no].num]<low[i]) { low[i]=low[ne[no].num]; flag[i]=0; } } no=ne[no].nex; } if(flag[i]==2) { for(;;) { printf("%d ",z[cont]); if(flag[z[cont]]==2) { flag[z[cont]]=3; cont--; break; } else { flag[z[cont]]=3; cont--; } } printf(" "); } } } return 0; }