有向無環圖自動佈局

何爲有向無環圖?

一、首先它是一個圖,而後它是一個有向圖,其次這個有向圖的任意一個頂點出發都沒有回到這個頂點的路徑,是爲有向無環圖
二、DAG(Directed Acyclic Graph)不必定能轉化爲樹,可是樹必定是一個DAGhtml

DAG相關問題

  • 拓撲序列

圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u在線性序列中出如今v以前。拓撲排序主要用來解決有向圖中的依賴問題
求一個DAG的一條拓撲序列:
1.找到當前全部的無直接前驅的節點(入度爲0),若沒有這樣的頂點,跳到3。如有,從中選一個v,標記爲已訪問,加入到當前序列的尾部,繼續2。
2.將從v出發的有向邊所有刪除(這樣會獲得一個新的有向圖G’)。
3.若是序列中的頂點數不等於有向圖G的頂點個數,則說明圖G中存在環;若是相等,則該序列便是所求的拓撲序列。
clipboard.pngnode

{ 1, 2, 4, 3, 5 }git

  • 判斷是否成環

一、執行拓撲排序,若是序列中的頂點數不等於有向圖G的頂點個數,則說明圖G中存在環。
二、深度優先遍歷該圖,若是在遍歷的過程當中,發現某個節點有一條邊指向已經訪問過的節點,而且這個已訪問過的節點不是當前節點的父節點(這裏的父節點表示dfs遍歷順序中的父節點),則表示存在環。
換種說法:1條深度遍歷路線中若是有結點被第二次訪問到,那麼有環。github

bool dfs(int i,int pre){
    visit[i]=true;
    for(int j=1;j<=v;j++)
        if(g[i][j])
        {
            if(!visit[j])
                return dfs(j,i);
            else if(j!=pre)  //若是訪問過,且不是其父節點,那麼就構成環
                return true;
        }
}
  • DAG圖(有向無環圖)的最小路徑覆蓋

圖存儲

  • 鄰接矩陣

圖的鄰接矩陣(Adjacency Matrix)存儲方式是用兩個數組來表示圖。一個一維的數組存儲圖中頂點信息,一個二維數組(稱爲鄰接矩陣)存儲圖中的邊或弧的信息。
設圖G有n個頂點,則鄰接矩陣是一個n*n的方陣
無向圖:
clipboard.png算法

有向圖:
clipboard.png數組

  • 鄰接表

鄰接表的核心思想就是針對每一個頂點設置一個鄰居表。
clipboard.png網絡

以上面的圖爲例,這是一個有向圖,分別有頂點a, b, c, d, e, f, g, h共8個頂點。使用鄰接表就是針對這8個頂點分別構建鄰居表,從而構成一個8個鄰居表組成的結構,這個結構就是咱們這個圖的表示結構或者叫存儲結構。數據結構

const node = [a, b, c, d, e, f, g, h];
const edges = [{b, c, d, e, f},  // a 的鄰居表
{c, e},  // b 的鄰居表
{d},  // c 的鄰居表
{e},  // d 的鄰居表
{f},  // e 的鄰居表
{c, g, h},  // f 的鄰居表
{f, h},  // g 的鄰居表
{f, g}]  // h 的鄰居表

圖佈局

graphlib
graphlib提供表示圖的數據結構。它不作佈局或渲染
darge
dagre執行節點,佈局節點位置,其中全部節點經過一個graphlib圖表示的佈局(x和y定位)。它不會渲染。
dagre-d3
dagre-d3使用dagre進行佈局,並使用d3進行渲染。請注意,dagre-d3默認包含dagre和graphlib,如dagreD3.dagre和dagreD3.graphlib。
元素:框架

graph,即圖總體,咱們能夠對圖配置一些全局參數。
node,即頂點,dagre 在計算時並不關心 node 實際的形狀、樣式,只要求提供維度信息。
edge,即邊,edge 須要聲明其兩端的 node 以及自己方向。例如A -> B表示一條由 A 指向 B 的 edge。
rank,即層級,rank 是 DAG 佈局中的核心邏輯單位,edge 兩端的 node 必定屬於不一樣的 rank,而同一 rank 中的 node 則會擁有一樣的深度座標(例如在縱向佈局的 graph 中 y 座標相同)。
label,即標籤,label 不是 DAG 中的必要元素,但 dagre 爲了適用更多的場景增長了對 edge label 的佈局計算。

深刻理解rank:佈局

A->B;
B->C;
    +---+       +---+        +---+  
    | A |------>| B |------->| C |  
    +---+       +---+        +---+

A B C分別處於3個rank

A->B;
A->C;
                +---+
            --> | B |
    +---+--/    +---+
    | A |
    +---+--\    +---+
            --> | C |
                +---+

A 處於rank1,B C都處於A的下一層級rank2

A->B;
B->C;
A->C;
                +---+
             -->| B |---\
    +---+---/   +---+    --->+---+  
    | A |                    | C |  
    +---+------------------->+---+

在這個示例中,咱們發現 edge 兩端的 node 能夠相差超過一個 rank。因爲 edge 兩端的 node 不可屬於一樣的 rank,因此咱們不能讓 B 和 C 屬於同一個 rank,進而最優的繪製結果爲 A 和 C 之間相隔兩個 rank。
佈局算法
DAG 能夠用於模型化許多不一樣種類的信息,所以將一個 DAG 數據結構可視化的需求也變得很是廣泛。而且因爲大部分圖的數據都很是複雜甚至動態變化,因此自動、可配置的 DAG 可視化佈局算法顯然比人爲排版更爲高效且可靠。
約束條件:

  • 結點之間不能有重疊
  • 連線之間儘可能減小交差
  • 結點之間是有基本的層次關係對齊的

主要分4個步驟:
一、消除圖中的環。
二、尋找最優的等級(分層)分配。
三、在同一個等級內,設置頂點的順序,使交叉數最小。
四、計算頂點的座標。
dagre佈局步驟:

removeSelfEdges // 刪除自環邊
acyclic.run // 反向設置成環的邊
rank // 計算最優的等級分配
order // 同層排序
insertSelfEdges // 插入自環邊
position // 計算頂點的座標
acyclic.undo // 恢復反向邊設置

尋找成環的邊:

遍歷全部的節點,遞歸遍歷每一個節點的出邊,把一條路徑上的全部節點按路徑順序入棧,當遍歷到某個出邊的目標點已經在這個路徑上遍歷過了,那麼這條邊就是成環的邊,存下來,而後對全部成環邊反向。

function dfsFAS(g) {
  var fas = [];
  var stack = {};
  var visited = {};

  function dfs(v) {
    if (_.has(visited, v)) {
      return;
    }
    visited[v] = true;
    stack[v] = true;
    _.forEach(g.outEdges(v), function(e) {
      if (_.has(stack, e.w)) {
        fas.push(e);
      } else {
        dfs(e.w);
      }
    });
    delete stack[v];
  }

  _.forEach(g.nodes(), dfs);
  return fas;
}

算法:network-simplex(網絡單純型) longest-path(最長路徑)

ranker=network-simplex

A->B->C->E;
A->D->F;
A->G->H->I->J;

                +---+        +---+        +---+
               >| B |------->| C |------->| E |
             -/ +---+        +---+        +---+
           -/
    +---+-/     +---+        +---+
    | A |------>| D |------->| F |
    +---+-\     +---+        +---+
           -\
             -\ +---+        +---+        +---+        +---+
               >| G |------->| H |------->| I |------->| J |
                +---+        +---+        +---+        +---+
ranker=longest-path
A->B->C->E;
A->D->F;
A->G->H->I->J;
                             +---+        +---+        +---+
                         --->| B |------->| C |------->| E |
                   -----/    +---+        +---+        +---+
             -----/
    +---+---/                             +---+        +---+
    | A |-------------------------------->| D |------->| F |
    +---+-\                               +---+        +---+
           -\
             -\ +---+        +---+        +---+        +---+
               >| G |------->| H |------->| I |------->| J |
                +---+        +---+        +---+        +---+

longestPath算法就是快速初始化一個層級關係,求出來的是一條路徑的盡頭都對齊,定爲0,而後逆向路徑計算,都爲負值。
深度優先遍歷

function longestPath(g) {
  var visited = {};
  // 深度優先
  function dfs(v) {
    var label = g.node(v);
    if (_.has(visited, v)) {
      return label.rank;
    }
    visited[v] = true;
    // g.outEdges(v) v點的出度,v的rank就是他出度的點的層級減去出度的minlen,若是v是指向多個點的,那麼就取最小的那個層級
    var rank = _.min(_.map(g.outEdges(v), function(e) {
      return dfs(e.w) - g.edge(e).minlen;
    }));

    if (rank === Number.POSITIVE_INFINITY || // return value of _.map([]) for Lodash 3
        rank === undefined || // return value of _.map([]) for Lodash 4
        rank === null) { // return value of _.map([null])
      rank = 0;
    }
    return (label.rank = rank);
  }
  _.forEach(g.sources(), dfs);
}

clipboard.png

緊湊樹型:
一、任意節點的層級必須知足邊的長度大於等於邊的minlen最小長度。
二、某條邊的鬆弛度被定義爲其長度和最小長度之間的差值,邊的鬆弛度爲0,則爲緊湊的。

從圖中任意找一個節點,做爲起點,從這個點開始遞歸找到一棵最大的緊湊樹,並返回這顆樹的節點個數。
遞歸遍歷鬆弛度爲0的節點加到新的樹上,新樹的節點個數少於舊樹的節點個數,說明還有節點由於鬆弛度大於0而沒被加到新樹上。在全部的邊裏找只有起點或者終點只有一個在新樹上的邊,而後判斷邊的兩個端點裏不在新樹上的節點是起點仍是終點,若是是起點,則把新樹上全部的點對應的舊樹上的點的rank加這個點的鬆弛度,若是是終點則是減去鬆弛度。

function tightTree(t, g) {
  function dfs(v) {
    // 遍歷v節點的全部邊,而後檢查邊的對點是否存在樹上,不存在且該邊是緊湊的即緊湊度是0,則該點能夠加到這棵樹上
    _.forEach(g.nodeEdges(v), function(e) {
      var edgeV = e.v,
          w = (v === edgeV) ? e.w : edgeV;
      if (!t.hasNode(w) && !slack(g, e)) {
        t.setNode(w, {});
        t.setEdge(v, w, {});
        dfs(w);
      }
    });
  }

  _.forEach(t.nodes(), dfs);
  return t.nodeCount();
}
+---+        +---+        +---+
                         --->| B |------->| C |------->| E |
                   -----/    +---+        +---+        +---+
             -----/            -2           -1           0
    +---+---/                             +---+        +---+
    | A |-------------------------------->| D |------->| F |
    +---+-\                               +---+        +---+
     -4    -\                               -1           0
             -\ +---+        +---+        +---+        +---+
               >| G |------->| H |------->| I |------->| J |
                +---+        +---+        +---+        +---+
                  -3           -2           -1           0
+---+        +---+        +---+
               >| B |------->| C |------->| E |
             -/ +---+        +---+        +---+
           -/     -2           -1           0
    +---+-/                               +---+        +---+
    | A |-------------------------------->| D |------->| F |
    +---+-\                               +---+        +---+
     -3    -\                               -1           0
             -\ +---+        +---+        +---+        +---+
               >| G |------->| H |------->| I |------->| J |
                +---+        +---+        +---+        +---+
                  -2           -1           0           1
+---+        +---+        +---+
               >| B |------->| C |------->| E |
             -/ +---+        +---+        +---+
           -/     -1           0            1
    +---+-/     +---+        +---+
    | A |------>| D |------->| F |
    +---+-\     +---+        +---+
     -2    -\     -1           0
             -\ +---+        +---+        +---+        +---+
               >| G |------->| H |------->| I |------->| J |
                +---+        +---+        +---+        +---+
                  -1           0            1            2

排序:
每層中的頂點順序決定了佈局的邊交叉狀況,所以一個好的層級內頂點順序應該要儘可能少產生交叉邊。
前提條件:分配完層級以後,跨越多個層級的邊會被替換成由多條鏈接臨時節點或者「虛擬節點」的單位長度的邊。虛擬節點被安插到中間層級上,使得整張圖中全部邊都只鏈接相鄰層級的節點。
理論:
把多層的DAG圖,分紅一個個的雙層圖,兩層兩層的進行排序。當訪問某一層時,這一層每一個頂點都會根據其關聯的上一層頂點的位置分配一個權重。而後這一層的頂點會根據這個權重進行排序。
權重計算:定義一個兩層圖,下層節點根據上層節點排序,下層每一個頂點v的權重等於:
每條與v關聯的邊的weight*order/sumWeight。weight是邊的權重,默認爲1,order是邊的上層節點在上層的排序,sumWeight是關聯邊的權重總和。
而後咱們就能夠執行一系列迭代嘗試改進這個順序,直到找到一個滿意的解時中止迭代。
啓發式迭代:
biasRight:重心相等時索引小的左偏仍是右偏
downLayerGraphs:從上到下分層,n行根據n-1行排序
upLayerGraphs:從下到上分層,n行根據n+1行排序
重心相等時索引小的左偏的狀況下,先從下到上分層掃描,排序;再進行從上到下分層掃描,排序;
重心相等時索引小的右偏的狀況下,先從下到上分層掃描,排序;再進行從上到下分層掃描,排序;
每次排序後都會計算交叉點個數,若是交叉個數更好了,則替換節點矩陣,而後再進行上述的4邊掃描,直到上述4遍掃描後都沒有再取得更優解,迭代結束。

A->B;
A->C;
A->F
B->E;
C->D;
C->G;
F->D;

原始圖:
clipboard.png

第一次迭代:從下到上分層掃描,左偏
clipboard.png
crossCount = 1
第二次迭代:再進行從上到下分層掃描,左偏
clipboard.png
crossCount = 1
第三次迭代:從下到上分層掃描,右偏
clipboard.png
crossCount = 0
得到了更優解,這個迭代週期結束,從新開始一個迭代週期,在這個迭代週期都沒有再找到更優解,迭代結束
輸出矩陣:

[
  [A],
  [25, 27, 26],
  [B, F, C],
  [28, 31, 29, 30],
  [E, D, G]
]

下面是上述過程的代碼:

function order(g) {
  var maxRank = util.maxRank(g),
      downLayerGraphs = buildLayerGraphs(g, _.range(1, maxRank + 1), "inEdges"), // 從上到下分層,n行根據n-1行排序
      upLayerGraphs = buildLayerGraphs(g, _.range(maxRank - 1, -1, -1), "outEdges"); // 從下到上分層,n行根據n+1行排序
  var layering = initOrder(g);
  assignOrder(g, layering);
  var bestCC = Number.POSITIVE_INFINITY,
      best;
  for (var i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) {
    sweepLayerGraphs(i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2); // 掃描按權重排序
    layering = util.buildLayerMatrix(g); // 節點id的矩陣
    var cc = crossCount(g, layering); // 返回當前矩陣下交叉點個數
    if (cc < bestCC) {
      lastBest = 0;
      best = _.cloneDeep(layering);
      bestCC = cc;
    }
  }
  assignOrder(g, best);
}

計算頂點座標:
節點的層號和層內序號肯定後,佈局結果的基本框架就已經肯定了.通常有向圖能夠採用在垂直方向或者水平方向按序號遞增的方式分別分配縱座標和橫座標。

實際應用場景

一、依賴關係:
可視化項目依賴,組件依賴關係:好比打包編譯依賴的時候,把各類包的依賴關係按照拓撲序列排序,先引入排在前面的包,後引入排在後面的包。
二、調度流程:
自動化佈局UML圖,workflow等。
事項流程:
clipboard.png
spark任務執行:大規模數據處理計算引擎
clipboard.png
UML類圖
clipboard.png
兒茶酚胺合成代謝路徑
clipboard.png
三、決策樹:鄙視鏈案例-婚姻市場中的房市
clipboard.png
四、複雜人物關係鏈分析:紅樓夢
clipboard.png

參考資料:
http://jgaa.info/accepted/200...
http://www.jos.org.cn/jos/ch/...
http://leungwensen.github.io/...

相關文章
相關標籤/搜索