tarjan算法非遞歸實現求強連通分量

 tarjan算法用來求有向圖和無向圖的強連通分量,強連通分量的概念請自行百度。

此代碼來源於華爲軟挑的題目,在有向帶權圖中找長度爲3-7的環路;

tarjan算法僞代碼:

tarjan(u){
  DFN[u]=Low[u]=++Index // 爲節點u設定次序編號和Low初值
  Stack.push(u)   // 將節點u壓入棧中
  for each (u, v) in E // 枚舉每一條邊
    if (v is not visted) // 如果節點v未被訪問過
        tarjan(v) // 繼續向下找
        Low[u] = min(Low[u], Low[v])
    else if (v in S) // 如果節點u還在棧內
        Low[u] = min(Low[u], DFN[v])
  if (DFN[u] == Low[u]) // 如果節點u是強連通分量的根
  repeat v = S.pop  // 將v退棧,爲該強連通分量中一個頂點
  print v
  until (u== v)
    }

 Java實現:

//鏈式前向星方式存圖
class Edge {
    public int next;  //相同起點的下一條邊的位置
    public int end;  //邊的終點 
    public int w;  //權重
    public Edge(int next, int v, int w) {
        this.next = next;
        this.end = v;
        this.w = w;
    }
    public String toString() {
        return " " + next + " " + end + " " + w;
    }
}

public class Graph {
    final int MaxEdges = 2800000;
    public Edge[] edges = new Edge[MaxEdges];
    public int cnt;
    public List<List<Integer>> path;
    public LinkedHashSet<String> strPath;
    private String inputFileName;
    private String outputFileName;
    private Logger logger;
    final private int MinLen = 3;  //環的最小長度
    final private int MaxLen = 7;  //環的最大長度
    Map<Integer, Integer> head = new HashMap<>();

    Set<Integer> visited = new HashSet<>();
    Set<Integer> endNodesSet;
    public int dfsCount = 0;
    /*-------------------------------- tarjan 變量--------------------------------------------*/
    int visitTime = 0;
    Deque<Integer> stack = new ArrayDeque<>();
    Set<Integer> stackSet = new HashSet<>();
    public List<List<Integer>> tarRes = new LinkedList<>();
    Map<Integer, Integer> dfn = new HashMap<>(head.size());
    Map<Integer, Integer> low = new HashMap<>(head.size());
    Set<Integer> tarVisited = new HashSet<>();
    int resCnt = 0;


    public Graph(String inputFileName, String outputFileName){
        init(inputFileName, outputFileName);
        try {
            loadFile();
        } catch (IOException e) {
            logger.info("Fail: loadFile...");
        }

//---------------------------tarjan------------------------
//        tarjan();
//        ----------  dfs --------------
//        findLoop();
//        output();
//        System.out.println("dfs調用次數:" + dfsCount);

    }

    private void Tarjan() {
        long s = System.currentTimeMillis();
        for (int node : head.keySet()) {
            if (!endNodesSet.contains(node) || tarVisited.contains(node)) continue;
            tarjan(node);
        }
        long e = System.currentTimeMillis();
        System.out.println("tarjan time: " + (double) (e - s) / 1000);
        System.out.println("強連通分量個數:" + tarRes.size());
        sort(tarRes);
        output("src/data/Tarjan.txt", tarRes);
    }

    public void init(String inputFileName, String outputFileName) {
        logger = Logger.getLogger("Graph");
        cnt = 0;
        path = new LinkedList<>();
        strPath = new LinkedHashSet<>();
        this.inputFileName = inputFileName;
        this.outputFileName = outputFileName;
        endNodesSet = new HashSet<>();
    }
    //讀取文件,按照鏈式前向星的方法爲圖添加邊
    public void loadFile() throws IOException {
        long startTime = System.currentTimeMillis();
        System.out.println("inputFile: " + inputFileName);
        File f = new File(inputFileName);
        InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(f), "utf-8");
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String lineText = null;
        while ((lineText = bufferedReader.readLine()) != null) {
            String[] data = lineText.split(",");
            addEdge(Integer.parseInt(data[0]), Integer.parseInt(data[1]), Integer.parseInt(data[2]));

        }
        long endTime = System.currentTimeMillis();
        System.out.println("read file and create graph: " + (double) (endTime - startTime) / 1000);
        System.out.println("edges num: " + cnt);
    }
    /*
    * @Description add edge set
    * @param null
    * @Return
    * @Author sunwb
    * @Date 2020/3/29
    */
    public void addEdge(int u, int v, int w) {
        if (!head.containsKey(u))
            head.put(u, -1);
        Edge e = new Edge(head.get(u), v, 0);
        edges[cnt] = e;
        head.put(u, cnt++);
        endNodesSet.add(v);
    }

    public void findLoop(){
        long start = System.currentTimeMillis();
        for (int node : head.keySet()) {
            if (!endNodesSet.contains(node) || visited.contains(node)) continue;
            LinkedHashSet<Integer> nodeList = new LinkedHashSet<>();
            nodeList.add(node);
            dfs(node, node, nodeList);
        }
        long end = System.currentTimeMillis();
        System.out.println("findLoop time: " + (double) (end - start) / 1000);
        sort(path);
    }

    //dfs尋找長度爲3~7的環路,第一版代碼,沒有任何優化,純暴力dfs
    public void dfs(int root, int node, LinkedHashSet<Integer> nodeList) {
        dfsCount++;
        if (!head.containsKey(node)) return;
        int index = head.get(node);
        if (index < 0) return;
        visited.add(node);
        while (index != -1) {
            Edge e = edges[index];
            if (nodeList.contains(e.end)) {
                List<Integer> list = new ArrayList<>(7);
                boolean flag = false;
                for (int x : nodeList) {  //此處可加入路徑長度計數器,避免list的構建
                    if (!flag && x == e.end) {
                        flag = true;
                    }
                    if (flag) {
                        list.add(x);
                        visited.add(x);
                    }
                }
                if (list.size()<=MaxLen && list.size()>=MinLen) {
                    path.add(change(list));
                }
            } else {
                nodeList.add(e.end);
                dfs(root, e.end, nodeList);
            }
            index = e.next;
            if (index < 0) {
                nodeList.remove(node);
                return;
            }
        }
    }
    /*
    * @Description 改變list的順序,以最小節點開始
    * @param list :一條循環路徑
    * @Return java.util.List<java.lang.Integer>
    * @Author sunwb
    * @Date 2020/4/2 23:08
    **/
    private List<Integer> change(List<Integer> list) {
        int min = 0;
        for (int i = 1; i < list.size();i++) {
            if (list.get(i) < list.get(min))
                min = i;
        }
        List<Integer> l = new ArrayList<>(list.size());
        for (int i = 0; i < list.size() - min; i++) {
            l.add(list.get(min+i));
        }
        for (int i = 0; i < min; i++) {
            l.add(list.get(i));
        }
        return l;
    }

    private void sort(List<List<Integer>> path) {
        Collections.sort(path, new Comparator<List<Integer>>() {
            @Override
            public int compare(List<Integer> o1, List<Integer> o2) {
                if (o1.size() != o2.size()) return o1.size()-o2.size();
                else {
                    for (int i = 0; i < o1.size(); i++) {
                        if (o1.get(i) != o2.get(i) ) return o1.get(i) - o2.get(i);
                    }
                }
                return 0;
            }
        });
    }

    /*
    * @Description 將結果輸出至文件
    * @param filename
    * @param path
    * @Return void
    * @Author sunwb
    * @Date 2020/4/2 23:09
    **/
    public void output() {
        long start = System.currentTimeMillis();
        for (List<Integer> l : path) {
            String s = l.toString();
            strPath.add(s.substring(1, s.length()- 1));
        }
        long e1 = System.currentTimeMillis();
        System.out.println("del the same list: " + (double)(e1 - start)/1000);
        try {
            File file = new File(outputFileName);
            if (!file.exists())
                file.createNewFile();
            FileWriter fw = new FileWriter(file.getAbsoluteFile());
            BufferedWriter bufferedWriter = new BufferedWriter(fw);
            bufferedWriter.write(String.valueOf(strPath.size()));
            bufferedWriter.newLine();
            for (String s : strPath) {
                bufferedWriter.write(s);
                bufferedWriter.newLine();
            }
            bufferedWriter.close();
        } catch (IOException e) {
            System.out.println("Fail: create file!");
        }
        long e2 = System.currentTimeMillis();
        System.out.println("output file: " + (double)(e2 - start)/1000);
    }

    /*--------------------------------------------------------------------
    tarjan(u){
  DFN[u]=Low[u]=++Index // 爲節點u設定次序編號和Low初值
  Stack.push(u)   // 將節點u壓入棧中
  for each (u, v) in E // 枚舉每一條邊
    if (v is not visted) // 如果節點v未被訪問過
        tarjan(v) // 繼續向下找
        Low[u] = min(Low[u], Low[v])
    else if (v in S) // 如果節點u還在棧內
        Low[u] = min(Low[u], DFN[v])
  if (DFN[u] == Low[u]) // 如果節點u是強連通分量的根
  repeat v = S.pop  // 將v退棧,爲該強連通分量中一個頂點
  print v
  until (u== v)
    }---------------------------------------------------------*/
    /*
    * @Description tarjan算法,僞代碼見上
    * @param u 當前遍歷到的節點
    * @Return void
    * @Author sunwb
    * @Date 2020/4/3 20:41
    **/
    public void tarjan(int u) {
            dfn.put(u, visitTime);
            low.put(u, visitTime);
            visitTime++;
            stack.push(u);
            stackSet.add(u);
            tarVisited.add(u);
            if (!head.containsKey(u)) return;
            int index = head.get(u);
            while (index != -1) {
                if (!tarVisited.contains(edges[index].end)) {
                    tarjan(edges[index].end);
                    low.put(u, Math.min(low.get(u), low.get(edges[index].end)));
                } else if (stackSet.contains(edges[index].end)) {
                    low.put(u, Math.min(low.get(u), low.get(edges[index].end)));
                }
                index = edges[index].next;
            }
            if (dfn.get(u).equals(low.get(u))) {
                List<Integer> list = new LinkedList<>();
                int n = stack.peek();
                if (n == u) {
//                    list.add(0, n);
                    stack.pop();
                    stackSet.remove(n);
//                    tarRes.add(list);
                    return;
                }
                while (n != u) {
                    n = stack.pop();
                    stackSet.remove(n);
                    list.add(0, n);
                }
                if (list.size()>2) tarRes.add(list); //大於等於3的環才添加
            }
    }

    /*
    * @Description 自己根據僞代碼實現的非遞歸tarjan算法,沒有優化,效率很低
    * @param
    * @Return void
    * @Author sunwb
    * @Date 2020/4/10 20:47
    **/
    public void tarjan() {
        long s = System.currentTimeMillis();
        Set<Integer> visEdges = new HashSet<>();
        for (int headNode : head.keySet()) {
            if (!endNodesSet.contains(headNode) || tarVisited.contains(headNode)) continue;
            Map<Integer, Integer> preNode = new HashMap<>();
            stack.add(headNode);
            preNode.put(headNode, headNode);
            while (!stack.isEmpty()) {
                if (!tarVisited.contains(headNode)) {
                    dfn.put(headNode, visitTime);
                    low.put(headNode, visitTime);
                    visitTime++;
                    tarVisited.add(headNode);
                    stackSet.add(headNode);
                    stack.push(headNode);
                }
                if (!head.containsKey(headNode)) {
                    headNode = preNode.get(headNode);
                    continue;
                }
                int index = head.get(headNode);
                while (index != -1) {
                    if (visEdges.contains(index)) {
                        index = edges[index].next;
                        if (index < 0) {
                            updateStack(headNode);
                            headNode = preNode.get(headNode);
                            break;
                        }
                        continue;
                    }
                    visEdges.add(index);
                    int end = edges[index].end;
                    if (!tarVisited.contains(end)) {
                        preNode.put(end, headNode);
                        headNode = end;
                        break;
                    } else if (stackSet.contains(end)) {
                        low.put(headNode, Math.min(low.get(headNode), low.get(end)));
                        updateLow(end, headNode, low.get(headNode));
                    }
                    index = edges[index].next;
                    if (index < 0) {
                        updateStack(headNode);
                        headNode = preNode.get(headNode);
                        break;
                    }
                }
            }
        }
        long e = System.currentTimeMillis();
        System.out.println("tarjan time: " + (double) (e - s) / 1000);
        System.out.println("強連通分量個數:" + tarRes.size());
        sort(tarRes);
        output("src/data/tar_jan.txt", tarRes);
//        printTarInfo();
    }
    //存儲強連通分量,並更新棧
    private void updateStack(int headNode) {
        if (dfn.get(headNode).equals(low.get(headNode))) {
            List<Integer> list = new LinkedList<>();
            int n = stack.peek();
            if (n == headNode) {
                stack.pop();
                stackSet.remove(n);
            }
            while (n != headNode) {
                n = stack.pop();
                stackSet.remove(n);
                list.add(0, n);
            }
            if (list.size()>2) tarRes.add(list); //大於等於3的環才添加
        }
    }
    //更新環路節點的low值
    private void updateLow(int end, int headNode, int val) {
        List<Integer> list = new LinkedList<>();
        if (stack.isEmpty()) return;
        while (!stack.isEmpty() && stack.peek() != end) {
            int n = stack.pop();
            low.put(n, val);
            list.add(0, n);
        }
        for (int n : list) {
            stack.push(n);
        }
    }

    private void printTarInfo() {
        System.out.println("強連通分量個數:" + tarRes.size());
        for (List<Integer> list : tarRes) {
            System.out.println(list.toString());
        }
//        System.out.println("--------low------- : \n" + low.toString());
    }

    public void output(String outFile, List<List<Integer>> res) {
        try {
            File file = new File(outFile);
            if (!file.exists()) file.createNewFile();
            FileWriter fw = new FileWriter(file.getAbsoluteFile());
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(String.valueOf(res.size()));
            bw.newLine();
            for (List<Integer> l : res) {
                bw.write(l.toString());
                bw.newLine();
            }
            bw.close();
        } catch (IOException e) {
            System.out.println("Fail: write to file!");
        }
    }

    /*
    * @Description 按照python庫networkx中的非遞歸tarjan邏輯實現,賊快
    * @param
    * @Return void
    * @Author sunwb
    * @Date 2020/4/12 17:05
    **/
    public void newTarjan() {
        long s = System.currentTimeMillis();
        int visTime = 0; //preorder counter;
        Map<Integer, Integer> preorder = new HashMap<>();
        Map<Integer, Integer> lowlink = new HashMap<>();
        Map<Integer, Boolean> sccFound = new HashMap<>();
        Deque<Integer> sccQueue = new ArrayDeque<>();
        for (int node : head.keySet()) {
            Deque<Integer> queue = new ArrayDeque<>();
            if (!sccFound.containsKey(node)) {
                queue.push(node);
                while (!queue.isEmpty()) {
                    int v = queue.peek();
                    if (!preorder.containsKey(v)) {
                        preorder.put(v, ++visTime);
                    }
                    boolean done = true;
                    if (!head.containsKey(v)) {
                        lowlink.put(v, preorder.get(v));
                        queue.pop();
                        continue;
                    }
                    int index = head.get(v);
                    while (index != -1) {
                        int w = edges[index].end;
                        if (!preorder.containsKey(w)) {
                            queue.push(w);
                            done = false;
                            break;
                        }
                        index = edges[index].next;
                    }
                    if(done) {
                        lowlink.put(v, preorder.get(v));
                        index = head.get(v);
                        while (index != -1) {
                            int w = edges[index].end;
                            if (!sccFound.containsKey(w)) {
                                if (preorder.get(w).intValue() > preorder.get(v).intValue()) {
                                    lowlink.put(v, Math.min(lowlink.get(v), lowlink.get(w)));
                                } else {
                                    lowlink.put(v, Math.min(lowlink.get(v), preorder.get(w)));
                                }
                            }
                            index = edges[index].next;
                        }
                        queue.pop();
                        if (lowlink.get(v).equals(preorder.get(v))) {
                            sccFound.put(v, true);
                            List<Integer> scc = new ArrayList<>();
                            scc.add(v);
                            while (!sccQueue.isEmpty() && preorder.get(sccQueue.peek()).intValue() > preorder.get(v).intValue()) {
                                int k = sccQueue.pop();
                                sccFound.put(k, true);
                                scc.add(k);
                            }
                            if (scc.size()>2) tarRes.add(scc);
                        } else {
                            sccQueue.push(v);
                        }
                    }
                }
            }

        }
        long e = System.currentTimeMillis();
        System.out.println("tarjan time: " + (double) (e - s) / 1000);
        System.out.println("強連通分量個數:" + tarRes.size());
        sort(tarRes);
//        System.out.println(tarRes.toString());
//        output("src/data/newTarjan.txt", tarRes);

    }
    public static void main(String[] args) {
        String inputFile = "src/data/data12.txt";
        String outputFile = "src/data/answer.txt";
//        String inputFile = "/data/test_data.txt";
//        String outputFile = "/projects/student/result.txt";
        Graph graph = new Graph(inputFile, outputFile);
        graph.newTarjan();

    }
}

C++一維存圖加非遞歸tarjan

#define MAXE  2500000
#define MAXN  1000000

struct Edge {
    uint32 to;  //邊的終點
    uint32 w;  //權值
};
struct Node {
    uint32 l;  //鄰接表中的左邊界
    uint32 r;  //鄰接表中的右邊界
};

Edge neighborsTable[MAXE];  //一維鄰接表
Node G[MAXN];  //通過下標訪問對應點在鄰接表中的左右邊界
//neighborsTable[G[i]]表示以i爲起點的邊

/*************************** tarjan ***************************************/
vector<vector<unsigned int>> tarRes;
void tarjan() {
    clock_t start = clock();
    int visitTime = 0;
    int tarjanCnt = 0;
    unordered_map<unsigned int, int> preorder;
    unordered_map<unsigned int, int> lowlink;
    unordered_map<unsigned int, bool> sccFound;
    stack<unsigned int> sccQueue;
    for (int node = 0; node < nodeCnt; ++node) {
        stack<unsigned int> queue;
        if (!sccFound.count(node)) {
            queue.push(node);
            while (!queue.empty()) {
                uint v = queue.top();
                if (!preorder.count(v))
                    preorder[v] = ++visitTime;
                bool done = true;
                for (uint index = G[v].l; index < G[v].r; ++index) {
                    uint w = neighborsTable[index].to;
                    if (!preorder.count(w)) {
                        queue.push(w);
                        done = false;
                        break;
                    }
                }
                if (done) {
                    lowlink[v] = preorder[v];
                    for (uint index = G[v].l; index < G[v].r; ++index) {
                        uint w = neighborsTable[index].to;
                        if (!sccFound.count(w)) {
                            if (preorder[w] > preorder[v]) {
                                lowlink[v] = min(lowlink[v], lowlink[w]);
                            } else {
                                lowlink[v] = min(lowlink[v], preorder[w]);
                            }
                        }
                    }
                    queue.pop();
                    if (lowlink[v] == preorder[v]) {
                        sccFound[v] = true;
                        vector<uint> scc;
                        scc.emplace_back(v);
                        while (!sccQueue.empty() && preorder[sccQueue.top()] > preorder[v]) {
                            uint k = sccQueue.top();
                            sccQueue.pop();
                            sccFound[k] = true;
                            scc.emplace_back(k);
                        }
                        tarjanCnt++;
                    } else
                        sccQueue.push(v);

                }
            }
        }
    }
    clock_t end = clock();
    cout << "tarjan time: " << (double)(end - start) / CLOCKS_PER_SEC << endl;
    cout << "scc size: " << tarjanCnt << endl;

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章