查找无向图的所有连通分量

时间:2014-02-20 07:11:14

标签: javascript algorithm data-structures graph-theory graph-algorithm

我有一个对象列表(无向边),如下所示:

pairs = [

 pair:["a2", "a5"],
 pair:["a3", "a6"],
 pair:["a4", "a5"],
 pair:["a7", "a9"]

];

我需要在不同的组中找到所有组件(连接的节点)。所以从我需要得到的配对:

groups = [
  group1: ["a2", "a5", "a4"],
  group2: ["a3", "a6"],
  group3: ["a7", "a9"]
];

我实际上在这里阅读了一些答案,并用Google搜索了这个,这就是我如何学习这个被称为“在图中查找连接的组件”,但是,找不到任何示例代码。我在Node.js上使用JavaScript,但任何其他语言的样本都会非常有用。谢谢。

3 个答案:

答案 0 :(得分:11)

这可以使用广度优先搜索来解决。

这个想法是通过跳转到相邻的顶点来遍历源顶点的所有可到达顶点。首先访问源顶点旁边的顶点,然后是2跳的顶点,等等。

由于使用了图表表示,这里的代码效率不高,这是一个边缘列表。要获得更好的性能,您可能需要使用邻接列表

以下是JavaScript中的一些工作代码。我用node.js来运行它:

// Breadth First Search function
// v is the source vertex
// all_pairs is the input array, which contains length 2 arrays
// visited is a dictionary for keeping track of whether a node is visited
var bfs = function(v, all_pairs, visited) {
  var q = [];
  var current_group = [];
  var i, nextVertex, pair;
  var length_all_pairs = all_pairs.length;
  q.push(v);
  while (q.length > 0) {
    v = q.shift();
    if (!visited[v]) {
      visited[v] = true;
      current_group.push(v);
      // go through the input array to find vertices that are
      // directly adjacent to the current vertex, and put them
      // onto the queue
      for (i = 0; i < length_all_pairs; i += 1) {
        pair = all_pairs[i];
        if (pair[0] === v && !visited[pair[1]]) {
          q.push(pair[1]);
        } else if (pair[1] === v && !visited[pair[0]]) {
          q.push(pair[0]);
        }
      }
    }
  }
  // return everything in the current "group"
  return current_group;
};

var pairs = [
  ["a2", "a5"],
  ["a3", "a6"],
  ["a4", "a5"],
  ["a7", "a9"]
];

var groups = [];
var i, k, length, u, v, src, current_pair;
var visited = {};

// main loop - find any unvisited vertex from the input array and
// treat it as the source, then perform a breadth first search from
// it. All vertices visited from this search belong to the same group
for (i = 0, length = pairs.length; i < length; i += 1) {
  current_pair = pairs[i];
  u = current_pair[0];
  v = current_pair[1];
  src = null;
  if (!visited[u]) {
    src = u;
  } else if (!visited[v]) {
    src = v;
  }
  if (src) {
    // there is an unvisited vertex in this pair.
    // perform a breadth first search, and push the resulting
    // group onto the list of all groups
    groups.push(bfs(src, pairs, visited));
  }
}

// show groups
console.log(groups);

更新:我已更新我的答案,演示如何将边缘列表转换为邻接列表。代码被评论,应该很好地说明这个概念。修改宽度优先搜索功能以使用邻接列表,以及另一个略微修改(关于将顶点标记为已访问)。

// Converts an edgelist to an adjacency list representation
// In this program, we use a dictionary as an adjacency list,
// where each key is a vertex, and each value is a list of all
// vertices adjacent to that vertex
var convert_edgelist_to_adjlist = function(edgelist) {
  var adjlist = {};
  var i, len, pair, u, v;
  for (i = 0, len = edgelist.length; i < len; i += 1) {
    pair = edgelist[i];
    u = pair[0];
    v = pair[1];
    if (adjlist[u]) {
      // append vertex v to edgelist of vertex u
      adjlist[u].push(v);
    } else {
      // vertex u is not in adjlist, create new adjacency list for it
      adjlist[u] = [v];
    }
    if (adjlist[v]) {
      adjlist[v].push(u);
    } else {
      adjlist[v] = [u];
    }
  }
  return adjlist;
};

// Breadth First Search using adjacency list
var bfs = function(v, adjlist, visited) {
  var q = [];
  var current_group = [];
  var i, len, adjV, nextVertex;
  q.push(v);
  visited[v] = true;
  while (q.length > 0) {
    v = q.shift();
    current_group.push(v);
    // Go through adjacency list of vertex v, and push any unvisited
    // vertex onto the queue.
    // This is more efficient than our earlier approach of going
    // through an edge list.
    adjV = adjlist[v];
    for (i = 0, len = adjV.length; i < len; i += 1) {
      nextVertex = adjV[i];
      if (!visited[nextVertex]) {
        q.push(nextVertex);
        visited[nextVertex] = true;
      }
    }
  }
  return current_group;
};

var pairs = [
  ["a2", "a5"],
  ["a3", "a6"],
  ["a4", "a5"],
  ["a7", "a9"]
];

var groups = [];
var visited = {};
var v;

// this should look like:
// {
//   "a2": ["a5"],
//   "a3": ["a6"],
//   "a4": ["a5"],
//   "a5": ["a2", "a4"],
//   "a6": ["a3"],
//   "a7": ["a9"],
//   "a9": ["a7"]
// }
var adjlist = convert_edgelist_to_adjlist(pairs);

for (v in adjlist) {
  if (adjlist.hasOwnProperty(v) && !visited[v]) {
    groups.push(bfs(v, adjlist, visited));
  }
}

console.log(groups);

答案 1 :(得分:5)

使用Disjoint Set Forest算法最佳地解决您要解决的任务。

基本上它旨在以最佳方式解决您描述的问题。

  • 为每个顶点分配它所属的顶点集。我们将其表示为root(v)。在整个算法中,每个这样的集合将被视为种植树。因此,我们将root(v)与树的根关联起来。所以我们假设root(v)返回一个顶点索引。
  • 如果在整个算法中保留一个辅助数组 - 其种植树中每个顶点的父顶点,算法将变得最简单。如果不存在这样的顶点,我们将存储-1而不是表示。因此,对于每个parent[v] = -1,您使用v启动算法,因为所有顶点最初都在他们自己的树中而您没有父母
  • 我们还需要一个额外的数组来存储一些被称为顶点等级的数组。它基本上等于以顶点为根的子树的深度。你不必太担心它。该数组用于性能优化。让我们将其命名为rank。我们将所有等级初始化为1s
  • 您开始逐个处理边缘,每条边可能会触发树的合并。这是您可以优化的有趣部分。

这是一个伪代码:

parent[v] = -1 for v in Vertices
rank[v] = 1 for v in Vertices
root (v):
   processed = []
   while parent[v] != -1
      processed << v
      v = parent[v]
   for vertex : processed
      parent = v // optimisation: here we move the assoc. trees to be directly connected the root
   return v

join (v1, v2):
  if rank[v1] < rank[v2]:
     parent[v1] = v2
  if rank[v1] > rank[v2]:
     parent[v2] = v1
  parent[v2] = v1
  rank[v1]++

merge_trees (v1, v2)
  root1 = root(v1)
  root2 = root(v2)
  if root1 == root2:
    // already in same tree nothing else to be done here
    return true
  else
    // join trees
    join (v1, v2)
    return false

main:
   numberTrees = size(Vertives)
   for edge: edges
      if merge_trees(edge.begin, edge.end):
         numberTrees--
   print numberTrees // this is the number you are interested in.

注意如果您在性能方面没有太多参与,您可以省略排名。没有它,你的算法可能会运行得更慢,但也许你更容易理解和维护。在这种情况下,您可以join任意方向的顶点。我应该警告你,然后会存在会触发你的算法运行速度较慢的场景。

答案 2 :(得分:2)

您想要Graph Traversal

特定的示例中,您没有节点数,甚至可能难以遍历图表,因此首先我们将获得“图表”

// It will return an object like Vertices{node: EdgesTo{node,node}, node:...}
function toGraph(arr) {
    var graph = {}; // this will hold the node "IDs"
    for (var i = 0; i < arr.length; i++) {
        // "create node" if the it's not added in the graph yet
        graph[arr[i][0]] = graph[arr[i][0]] || {};
        graph[arr[i][1]] = graph[arr[i][1]] || {};
        // add bidirectional "edges" to the "vertices"
        // Yes, we set the value to null, but what's important is to add the key.
        graph[arr[i][0]][arr[i][1]] = null;
        graph[arr[i][1]][arr[i][0]] = null;
    }
    return graph;
}

然后使用您选择的任何方法(DFSBFS)遍历图表非常容易

我将使用DFS做一个例子:

// to be called after getting the result from toGraph(arr)
function getSubGraphs(graph) {
    var subGraphs = []; // array of connected vertices
    var visited = {};
    for (var i in graph) { // for every node...
        var subGraph = dfs(graph, i, visited); // ... we call dfs
        if (subGraph != null) // if vertex is not added yet in another graph
        subGraphs.push(subGraph);
    }
    return subGraphs;
}

// it will return an array of all connected nodes in a subgraph
function dfs(graph, node, visited) {
    if (visited[node]) return null; // node is already visited, get out of here.
    var subGraph = [];
    visited[node] = true;
    subGraph.push(node);
    for (var i in graph[node]) {
        var result = dfs(graph, i, visited);
        if (result == null) continue;
        subGraph = subGraph.concat(result);
    }
    return subGraph;
}

你最终会像getSubGraphs(toGraph(myArray));那样称呼它并随意做任何事情。

Fiddle Here