簡體   English   中英

使用Kruskal算法檢測圖形中的循環

[英]Detect cycle in a graph using Kruskal's algorithm

我正在實現Kruskal算法,這是一種眾所周知的方法來查找加權圖的最小生成樹。 但是,我正在調整它以在圖表中查找周期。 這是Kruskal算法的偽代碼:

KRUSKAL(G):
1 A = ∅
2 foreach v ∈ G.V:
3    MAKE-SET(v)
4 foreach (u, v) ordered by weight(u, v), increasing:
5    if FIND-SET(u) ≠ FIND-SET(v):
6       A = A ∪ {(u, v)}
7       UNION(u, v)
8 return A

我很難掌握FIND-SET()MAKE-SET()函數,或者用不相交的數據結構實現它們的實現。

我當前的代碼如下所示:

class edge {
    public:      //for quick access (temp) 
      char leftV;
      char rightV;
      int weight;
};

std::vector<edge> kruskalMST(std::vector<edge> edgeList){
    std::vector<char> set;
    std::vector<edge> result;
    sortList(edgeList);    //sorts according to weight ( passed by reference)
    do{
        if(set.empty()){
            set.push_pack(edgeList[i].leftV);    //also only push them when
            set.push_pack(edgeList[i].rightV);    //they aren't there , will fix
            result.push_back(edgeList[i]);
            ++i;
        }
        else {
            if((setContains(set , edgeList[i].leftV)) && (setContains(set , edgeList[i].rightV)))
                ++i; //skip node 
            else {
                set.push_pack(edgeList[i].leftV);    //also only push them when
                set.push_pack(edgeList[i].rightV);    //they aren't there , will fix
                result.push_back(edgeList[i]);
                ++i;
            }
     } while(i<edgeList.size());
    return result;
}

當兩個已經存在於set vector頂點再次出現時,我的代碼檢測到圖形中的循環。 在大多數情況下,這似乎有效,直到我遇到這樣的情況:

  [a]              [c]
   |                |
   |                |
   |                |
  [b]              [d]

當這些邊緣按排序順序出現時,會發生這種情況,因為abcd已被推入set vector 連接[a][c]不會在圖形內部產生循環,但由於當前實現而被檢測為循環。

在我的案例中,有沒有可行的替代方法來檢測周期? 或者如果有人能解釋MAKE-SETFIND-SETUNION在Kruskal的算法中工作,那將會有很大幫助。

MAKE-SET(v)表示您正在初始化僅由頂點v組成的集合。 最初,每個頂點都在一個集合中。

FIND-SET(u)是一個函數,它告訴你頂點屬於哪個集合。 它必須返回唯一標識該集合的指針或ID號。

UNION(u, v)表示將包含u的集合與包含v的集合合並。 換句話說,如果uv在不同的集合中, UNION操作將形成一個新集合,其中包含集合FIND-SET(u)FIND-SET(v)所有成員。

當我們使用不相交集數據結構實現這些操作時,關鍵思想是每個集合都由一個領導者表示。 每個頂點都有一個指向其集合中某個頂點的指針。 集合的領導者是指向自身的頂點。 所有其他頂點指向父節點,指針形成以領導者為根的樹結構。

要實現FIND-SET(u) ,您可以跟蹤從u開始的指針,直到到達set leader,這是集合中唯一指向自身的頂點。

要實現UNION(u, v) ,您可以將一個設定點的領導者設為另一個設定點的領導者。

這些操作可以通過秩和路徑壓縮的聯合思想進行優化。

按等級聯合意味着您可以跟蹤從集合中任何頂點到領導者的最大指針數。 這與指針形成的樹的高度相同。 您可以通過為每個UNION操作執行一定數量的步驟來更新排名,這是集合排名唯一可以更改的時間。 假設我們合並集合A和B使得A具有比B更大的等級。我們使B的領導者指向A的領導者。結果集合具有與A相同的等級。如果A具有比B更小的等級。 ,我們使A點的領導者成為B的領導者,並且得到的集合具有與B相同的等級。如果A和B具有相同的等級,則我們選擇哪個領導者並不重要。 無論我們將A點的領導者指向B的領導者還是反之亦然,結果集的排名將比A的排名大一。

路徑壓縮意味着當我們執行FIND操作時,它需要跟隨一組指向集合的領導者的指針,我們使我們遇到的所有頂點直接指向領導者。 這僅增加了當前FIND操作的工作量,並且減少了將來調用FIND的工作量。

如果您通過排名和路徑壓縮實現union,那么您將擁有一個非常快速的union-find實現。 n次操作的時間復雜度為O(nα(n)) ,其中α是逆Ackermann函數。 這個函數增長得如此之慢,如果n是宇宙中原子的數量,則α(n)為5.因此,它實際上是一個常數,優化的不相交集數據結構實際上是聯合的線性時間實現 -找。

我不會重復聯合/查找算法的集合理論描述(Kruskal只是它的一個特例),但是使用更簡單的方法(你可以通過秩和路徑壓縮來應用聯合)。

為簡單起見,我認為每個頂點都有一個唯一的整數ID,范圍從0到1 - 1(例如,頂點ID可以用作數組的索引。)

朴素的算法非常簡單,代碼本身就說明了:

int find(int v, int cc[]) {
  while (cc[v] >= 0)
    v = cc[v];
  return v;
}

bool edge_union(int v0, int v1, int cc[]) {
  int r0 = find(v0, cc);
  int r1 = find(v1, cc);
  if (r0 != r1) {
    cc[r1] = r0;
    return true;
  }
  return false;
}

cc數組在每個地方初始化為-1(當然它的大小反映了圖形順序。)

然后可以通過在find函數的while循環中堆疊遇到的頂點來完成路徑壓縮,然后為所有頂點設置相同的表示。

int find2(int v, int cc[]) {
  std::deque<int> stack;
  while (cc[v] >= 0) {
    stack.push_back(v);
    v = cc[v];
  }
  for (auto i : stack) {
    cc[i] = v;
  }
  return v;
}

對於按等級的並集,我們只使用數組的負值,值越小,等級越大。 這是代碼:

bool edge_union2(int v0, int v1, int cc[]) {
  int r0 = find(v0, cc);
  int r1 = find(v1, cc);
  if (r0 == r1)
    return false;
  if (cc[r0] < cc[r1])
    cc[r1] = r0;
  else {
    if (cc[r1] < cc[r0])
      cc[r0] = r1;
    else {
      cc[r1] = r0;
      cc[r0] -= 1;
    }
  }
  return true;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM