Javascript 寻找无向图的所有连通分量
我有一个对象(无向边)列表,如下所示:Javascript 寻找无向图的所有连通分量,javascript,algorithm,data-structures,graph-theory,graph-algorithm,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"] ]; 实际上
pairs = [
pair:["a2", "a5"],
pair:["a3", "a6"],
pair:["a4", "a5"],
pair:["a7", "a9"]
];
我需要在单独的组中查找所有组件(连接的节点)。因此,从给定的对中,我需要得到:
groups = [
group1: ["a2", "a5", "a4"],
group2: ["a3", "a6"],
group3: ["a7", "a9"]
];
实际上,我在这里读了一些答案,并在谷歌上搜索了这个,这就是我如何知道这叫做“在图形中查找连接的组件”,但是,找不到任何示例代码。我在Node.js上使用JavaScript,但任何其他语言的示例都会非常有用。谢谢 你要解决的任务最好用的算法来解决 基本上,它旨在以最佳方式准确地解决您描述的问题
- 为每个顶点指定其所属的顶点集。让我们将其表示为
。在整个算法中,每个这样的集合都将被视为种植树。因此,我们将把根(v)
与树的根相关联。因此,我们假设根(v)
返回一个顶点索引root(v)
- 如果在整个算法中保留一个辅助数组,即种植树中每个顶点的父顶点,则该算法将变得最简单。如果不存在这样的顶点,我们将存储-1来表示它。因此,对于每个
,您可以从v
开始算法,因为所有顶点最初都在它们自己的树中,并且没有父节点parent[v]=-1
- 我们还需要一个额外的数组来存储一些称为顶点秩的东西。它基本上等于以顶点为根的子树的深度。你不必太担心它。该阵列用于性能优化。让我们把它命名为
。我们将所有列初始化为1rank
- 开始逐个处理边,每条边都可能触发树的合并。这是您可以优化的有趣部分
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.
parent[v]=-1表示顶点中的v
顶点中v的秩[v]=1
根(v):
已处理=[]
当父母[v]!=-1.
已处理秩[v2]:
父[v2]=v1
父[v2]=v1
排名[v1]++
合并树(v1,v2)
根1=根(v1)
root2=根(v2)
如果root1==root2:
//已经在同一棵树上了,这里没有别的事可做
返回真值
其他的
//连接树
加入(v1,v2)
返回错误
主要内容:
numberTrees=大小(垂直)
对于边:边
如果合并树(edge.begin,edge.end):
数树--
打印号码树//这是您感兴趣的号码。
注意如果你对表演不太感兴趣,可以省略排名。如果没有它,您的算法可能会运行较慢,但可能会更容易理解和维护。在这种情况下,您可以在任何方向上连接顶点。我应该警告您,这样会有一些情况触发您的算法运行变慢。这可以通过广度优先搜索来解决 其思想是通过跳到相邻顶点,从源顶点遍历所有可到达的顶点。首先访问源顶点旁边的顶点,然后是距离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);
//宽度优先搜索函数
//v是源顶点
//所有_对都是输入数组,其中包含长度为2的数组
//visited是用于跟踪节点是否被访问的字典
var bfs=功能(v,所有_对,已访问){
var q=[];
var当前_组=[];
变量i,nextVertex,成对;
var length\u all\u pairs=all\u pairs.length;
q、 推(v);
而(q.length>0){
v=q.shift();
如果(!已访问[v]){
访问[v]=正确;
当前组推送(v);
//遍历输入数组以查找以下顶点:
//直接与当前顶点相邻,并将其放置
//排队
对于(i=0;i
更新:我更新了答案,以演示如何将边列表转换为邻接列表。代码已经注释,应该很好地说明这个概念。对宽度优先搜索函数进行了修改,以使用邻接列表,并对其进行了轻微修改(将顶点标记为已访问顶点)
//将边列表转换为邻接列表表示形式
//在这个程序中,我们使用字典作为邻接列表,
//其中每个关键点都是顶点,每个值都是所有关键点的列表
//与该顶点相邻的顶点
var convert_edgelist_to_adjlist=函数(edgelist){
阿贾利斯变种
// 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);
// 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;
}
// 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;
}