Tarjan算法

求最近公共祖先(LCA)的Tarjan算法

   首先,Tarjan算法是一种离线算法,也就是说,它要首先读入所有的询问(求一次LCA叫做一次询问),然后并不一定按
照原来的顺序处理这些询问。而打乱这个顺序正是这个算法的巧妙之处。看完下文,你便会发现,如果偏要按原来的顺序
处理询问,Tarjan算法将无法进行。
   Tarjan算法是利用并查集来实现的。它按DFS的顺序遍历整棵树。对于每个结点x,它进行以下几步操作:
   * 计算当前结点的层号lv[x],并在并查集中建立仅包含x结点的集合,即root[x]:=x。
   * 依次处理与该结点关联的询问。
   * 递归处理x的所有孩子。
   * root[x]:=root[father[x]](对于根结点来说,它的父结点可以任选一个,反正这是最后一步操作了)。

  现在我们来观察正在处理与x结点关联的询问时并查集的情况。由于一个结点处理完毕后,它就被归到其父结点所在的集合,所以在已经处理过的结点中(包括 x本身),x结点本身构成了与x的LCA是x的集合,x结点的父结点及以x的所有已处理的兄弟结点为根的子树构成了与x的LCA是father[x]的集合,x结点的父结点的父结点及以x的父结点的所有已处理的兄弟结点为根的子树构成了与x的LCA是father[father[x]]的集合……(上面这几句话如果看着别扭,就分析一下句子成分,也可参照右面的图)假设有一个询问(x,y)(y是已处理的结点),在并查集中查到y所属集合的根是z,那么z 就是x和y的LCA,x到y的路径长度就是lv[x]+lv[y]-lv[z]*2。累加所有经过的路径长度就得到答案。   现在还有一个问题:上面提到的询问(x,y)中,y是已处理过的结点。那么,如果y尚未处理怎么办?其实很简单,只要在询问列表中加入两个询问(x, y)、(y,x),那么就可以保证这两个询问有且仅有一个被处理了(暂时无法处理的那个就pass掉)。而形如(x,x)的询问则根本不必存储。   如果在并查集的实现中使用路径压缩等优化措施,一次查询的复杂度将可以认为是常数级的,整个算法也就是线性的了。


求有向图的强连通分支(SCC)的Tarjan算法

求有向图的强连通分支的Tarjan算法是以其发明者Robert Tarjan命名的。Robert Tarjan还发明了求双连通分量(割点、桥)的Tarjan算法,以及求最近公共祖先的Tarjan算法。

Tarjan算法是通过对原图进行一次DFS实现的。下面给出该算法的PASCAL语言模板:

procedure dfs(s:int);
var ne:int;
begin
  view[s]:=1;                  //view[i]表示点i的访问状态.未访问,正访问,已访问的点,值分别为0,1,2
  inc(top); stack[top]:=s;     //当前点入栈
  inc(time); rea[s]:=time; low[s]:=time;  //记录访问该点的真实时间rea和最早时间low
 
  ne:=head[s];
  while ne<>0 do begin
    if view[e[ne]]=0 then dfs(e[ne]);     //如果扩展出的点未被访问,继续扩展
    if view[e[ne]]<2 then low[s]:=min(low[s],low[e[ne]]);
    //如果扩展出的不是已访问的点,更新访问源点s的最早时间.容易理解,如果一个点能到达之前访问过的点,那么路径中存在一个环使它能更早被访问
    ne:=next[ne];
  end;
 
  if rea[s]=low[s] then begin             //如果s的最早访问时间等于其实际访问时间,则可把其视作回路的"始点"
    inc(tot);                             //连通块编号
    while stack[top+1]<>s do begin        //将由s直接或间接扩展出的点标记为同一连通块,标记访问后出栈
      lab[stack[top]]:=tot;               //lab[i]表示点i所属的连通块
      view[stack[top]]:=2;
      dec(top);
    end;
  end;
end;

图是用邻接表存储的,e[i]表示第i条边指向的点。

算法运行过程中,每个顶点和每条边都被访问了一次,所以该算法的时间复杂度为O(V+E)。

下面是求强连通分量的Tarjan算法的C++实现

#define  M 5010              //题目中可能的最大点数       
int STACK[M],top=0;          //Tarjan 算法中的栈 
bool InStack[M];             //检查是否在栈中 
int DFN[M];                  //深度优先搜索访问次序 
int Low[M];                  //能追溯到的最早的次序 
int ComponentNumber=0;        //有向图强连通分量个数 
int Index=0;                 //索引号 
vector <int> Edge[M];        //邻接表表示 
vector <int> Component[M];   //获得强连通分量结果
int InComponent[M];   //记录每个点在第几号强连通分量里
int ComponentDegree[M];     //记录每个强连通分量的度
void Tarjan(int i) 
{ 
    int j; 
    DFN[i]=Low[i]=Index++; 
    InStack[i]=true; 
    STACK[++top]=i; 
    for (int e=0;e<Edge[i].size();e++) 
    { 
        j=Edge[i][e]; 
        if (DFN[j]==-1) 
        { 
            Tarjan(j); 
            Low[i]=min(Low[i],Low[j]); 
        } 
        else if (InStack[j]) 
            Low[i]=min(Low[i],DFN[j]); 
    } 
    if (DFN[i]==Low[i]) 
    { 
        ComponentNumber++; 
        do 
        { 
            j=STACK[top--]; 
            InStack[j]=false; 
            Component[ComponentNumber].push_back(j);
   InComponent[j]=ComponentNumber;
        } 
        while (j!=i); 
    } 
}
 
void solve(int N)     //N是此图中点的个数,注意是0-indexed! 
{ 
    memset(STACK,-1,sizeof(STACK)); 
    memset(InStack,0,sizeof(InStack)); 
    memset(DFN,-1,sizeof(DFN)); 
    memset(Low,-1,sizeof(Low)); 
 
    for(int i=0;i<N;i++) 
        if(DFN[i]==-1) 
            Tarjan(i);    
}

关于Tarjan算法的更为详细的讲解,可以在这里找到。

Tarjan的C++代码(STL):

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <list>
#include <stack>
 
using namespace std;
 
const int kMaxN = 3001;
 
class Graph {
 public:
  Graph(int vertex_count = 0) {
    vertex_count_ = vertex_count;
    memset(degree_, 0, sizeof(degree_));
  }
  void insert_edge(int v, int w) {
    graph_[v].push_back(w);
    degree_[w]++;
  }
  void TarjanInit() {
    tarjan_count = 0;
    memset(tarjan_dfn, 0, sizeof(tarjan_dfn));
    memset(tarjan_low, 0, sizeof(tarjan_low));
    memset(tarjan_instack, false, sizeof(tarjan_instack));
    for (int i = 1; i <= vertex_count_; i++) {
      tarjan_set[i] = i;
    }
  }
  void Tarjan(int v) {                  // Need TarjanInit()
    tarjan_count++;
    tarjan_dfn[v] = tarjan_count;
    tarjan_low[v] = tarjan_count;
    tarjan_stack.push(v);
    tarjan_instack[v] = true;
 
    for (list<int>::const_iterator i = graph_[v].begin(); i != graph_[v].end(); ++i) {
      if (!tarjan_dfn[*i]) {
        Tarjan(*i);
        tarjan_low[v] = min(tarjan_low[v], tarjan_low[*i]);
      } else if (tarjan_instack[*i]) {
        tarjan_low[v] = min(tarjan_low[v], tarjan_dfn[*i]);
      }
    }
 
    if (tarjan_dfn[v] == tarjan_low[v]) {
      while (tarjan_stack.top() != v) {
        tarjan_instack[tarjan_stack.top()] = false;
        tarjan_set[tarjan_stack.top()] = v;
        tarjan_stack.pop();
      }
      tarjan_instack[tarjan_stack.top()] = false;
      tarjan_set[tarjan_stack.top()] = v;
      tarjan_stack.pop();
    }
  }
  static bool compare(const int &a, const int &b) {
    return a < b;
  }
  void unique() {
    for (int v = 0; v < vertex_count_; v++) {
      graph_[v].sort(compare);
      graph_[v].unique();
    }
  }
  void BuildDAG(Graph &new_graph) {
    for (int v = 1; v <= vertex_count_; v++) {
      for (list<int>::const_iterator i = graph_[v].begin(); i != graph_[v].end(); ++i) {
        if (tarjan_set[v] == tarjan_set[*i]) {
          continue;
        } else {
          new_graph.insert_edge(tarjan_set[v], tarjan_set[*i]);
        }
      }
    }
    new_graph.unique();
  }
  int view_degree(int v) {
    return degree_[v];
  }
 
  void show(int v) {
    cout << "Vertex " << v << " : ";
    for (list<int>::const_iterator i = graph_[v].begin(); i != graph_[v].end(); ++i) {
      cout << *i << " ";
    }
    cout << endl;
  }
  void show_all() {
    for (int i = 1; i <= vertex_count_; i++) {
      show(i);
    }
  }
 
  int tarjan_count;
  int tarjan_set[kMaxN];
  int tarjan_dfn[kMaxN];
  int tarjan_low[kMaxN];
  bool tarjan_instack[kMaxN];
  stack<int> tarjan_stack;
 private:
  int vertex_count_;
  int degree_[kMaxN];
  list<int> graph_[kMaxN];
};
 
int n, p, r;
int buy[kMaxN];
 
int main() {
  ios::sync_with_stdio(false);
  cin >> n;
  Graph graph(n);
  Graph graph_dag(n);
  cin >> r;
  for (int i = 1; i <= r; i++) {
    int x, y;
    cin >> x >> y;
    graph.insert_edge(x, y);
  }
 
  graph.TarjanInit();
  for (int i = 1; i <= n; i++) {
    if (!graph.tarjan_dfn[i]) {
      graph.Tarjan(i);
    }
  }
  graph.BuildDAG(graph_dag);
 
  graph_dag.show_all();
 
  return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值