繁体   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