2012-04-05 15 views
6

Tengo un gráfico bipartito y estoy buscando la forma iterativa más eficiente para dividirlo en componentes conectados. Mi versión recursiva ha comenzado a desbordar la pila en grandes conjuntos de datos. Estoy dispuesto a portar desde cualquier idioma/pseudocódigo, pero para estar completo estaré codificando en C#.Algoritmo iterativo de componentes conectados

Mi código existente está especializado para mis tipos de datos. Una partición es proteínas, la otra es espectros. Map y Set son C++ stdlib workalikes.

void recursivelyAssignProteinToCluster (long proteinId, 
             long clusterId, 
             Set<long> spectrumSet, 
             Map<long, Set<long>> spectrumSetByProteinId, 
             Map<long, Set<long>> proteinSetBySpectrumId, 
             Map<long, long> clusterByProteinId) 
{ 
    // try to assign the protein to the current cluster 
    var insertResult = clusterByProteinId.Insert(proteinId, clusterId); 
    if (!insertResult.WasInserted) 
     return; 

    // recursively add all "cousin" proteins to the current cluster 
    foreach (long spectrumId in spectrumSet) 
     foreach (var cousinProteinId in proteinSetBySpectrumId[spectrumId]) 
     { 
      if (proteinId != cousinProteinId) 
      { 
       Set<long> cousinSpectrumSet = spectrumSetByProteinId[cousinProteinId]; 
       recursivelyAssignProteinToCluster(cousinProteinId, 
                clusterId, 
                cousinSpectrumSet, 
                spectrumSetByProteinId, 
                proteinSetBySpectrumId, 
                clusterByProteinId); 
      } 
     } 
} 

Map<long, long> calculateProteinClusters (NHibernate.ISession session) 
{ 
    var spectrumSetByProteinId = new Map<long, Set<long>>(); 
    var proteinSetBySpectrumId = new Map<long, Set<long>>(); 

    var query = session.CreateQuery("SELECT pi.Protein.id, psm.Spectrum.id " + GetFilteredQueryString(FromProtein, ProteinToPeptideSpectrumMatch)); 

    foreach (var queryRow in query.List<object[]>()) 
    { 
     long proteinId = (long) queryRow[0]; 
     long spectrumId = (long) queryRow[1]; 

     spectrumSetByProteinId[proteinId].Add(spectrumId); 
     proteinSetBySpectrumId[spectrumId].Add(proteinId); 
    } 

    var clusterByProteinId = new Map<long, long>(); 
    int clusterId = 0; 

    foreach (var pair in spectrumSetByProteinId) 
    { 
     long proteinId = pair.Key; 

     // for each protein without a cluster assignment, make a new cluster 
     if (!clusterByProteinId.Contains(proteinId)) 
     { 
      ++clusterId; 

      recursivelyAssignProteinToCluster(proteinId, 
               clusterId, 
               pair.Value, 
               spectrumSetByProteinId, 
               proteinSetBySpectrumId, 
               clusterByProteinId); 
     } 
    } 

    return clusterByProteinId; 
} 

Como ShinTakezou sugirió que refactorizado poner la pila en el montón y funciona muy bien. Utilicé el enfoque DepthFirstSearch del ejemplo de digEmAll.

var clusterByProteinId = new Map<long, long>(); 
int clusterId = 0; 
var clusterStack = new Stack<KeyValuePair<long, Set<long>>>(); 

foreach (var pair in spectrumSetByProteinId) 
{ 
    long proteinId = pair.Key; 

    if (clusterByProteinId.Contains(proteinId)) 
     continue; 

    // for each protein without a cluster assignment, make a new cluster 
    ++clusterId; 
    clusterStack.Push(new KeyValuePair<long, Set<long>>(proteinId, spectrumSetByProteinId[proteinId])); 
    while (clusterStack.Count > 0) 
    { 
     var kvp = clusterStack.Pop(); 

     // try to assign the protein to the current cluster 
     var insertResult = clusterByProteinId.Insert(kvp.Key, clusterId); 
     if (!insertResult.WasInserted) 
      continue; 

     // add all "cousin" proteins to the current cluster 
     foreach (long spectrumId in kvp.Value) 
      foreach (var cousinProteinId in proteinSetBySpectrumId[spectrumId]) 
       if (!clusterByProteinId.Contains(cousinProteinId)) 
        clusterStack.Push(new KeyValuePair<long, Set<long>>(cousinProteinId, spectrumSetByProteinId[cousinProteinId])); 
    } 
} 
+0

Publique su método recursivo y alguien aquí lo traducirá a un método iterativo para usted. – Brannon

+2

cuando la recursión come demasiada pila, como primer intento de mantener el mismo algoritmo, puede intentar cambiar la recursividad en un bucle consumiendo datos (a través de "pop") desde una * pila * (la llamada a la misma función se convierte en un impulso pila de los argumentos requeridos para la función). Por supuesto, con "pila" me refiero a una lista LIFO implementada por el usuario, y esto requiere un poco de trabajo, pero de esta manera estás limitado por el montón y no por la pila. (Tal vez esto funciona fácilmente solo para recursividad de cola? Tengo que pensar en ello ...) – ShinTakezou

+0

Por "componentes", ¿te refieres a subgrafos? – Beta

Respuesta

5

He aquí un ejemplo de una clase de ayuda que mantiene un grafo no dirigido y permite obtener los componentes conectados de la misma (iterativa):

public class Graph<T> 
{ 
    public Dictionary<T, HashSet<T>> nodesNeighbors; 
    public IEnumerable<T> Nodes 
    { 
     get { return nodesNeighbors.Keys; } 
    } 
    public Graph() 
    { 
     this.nodesNeighbors = new Dictionary<T, HashSet<T>>(); 
    } 
    public void AddNode(T node) 
    { 
     this.nodesNeighbors.Add(node, new HashSet<T>()); 
    } 
    public void AddNodes(IEnumerable<T> nodes) 
    { 
     foreach (var n in nodes) 
      this.AddNode(n); 
    } 
    public void AddArc(T from, T to) 
    { 
     this.nodesNeighbors[from].Add(to); 
     this.nodesNeighbors[to].Add(from); 
    } 
    public bool ContainsNode(T node) 
    { 
     return this.nodesNeighbors.ContainsKey(node); 
    } 
    public IEnumerable<T> GetNeighbors(T node) 
    { 
     return nodesNeighbors[node]; 
    } 
    public IEnumerable<T> DepthFirstSearch(T nodeStart) 
    { 
     var stack = new Stack<T>(); 
     var visitedNodes = new HashSet<T>(); 
     stack.Push(nodeStart); 
     while (stack.Count > 0) 
     { 
      var curr = stack.Pop(); 
      if (!visitedNodes.Contains(curr)) 
      { 
       visitedNodes.Add(curr); 
       yield return curr; 
       foreach (var next in this.GetNeighbors(curr)) 
       { 
        if (!visitedNodes.Contains(next)) 
         stack.Push(next); 
       } 
      } 
     } 
    } 
    public Graph<T> GetSubGraph(IEnumerable<T> nodes) 
    { 
     Graph<T> g = new Graph<T>(); 
     g.AddNodes(nodes); 
     foreach (var n in g.Nodes.ToList()) 
     { 
      foreach (var neigh in this.GetNeighbors(n)) 
       g.AddArc(n, neigh); 
     } 
     return g; 
    } 

    public IEnumerable<Graph<T>> GetConnectedComponents() 
    { 
     var visitedNodes = new HashSet<T>(); 
     var components = new List<Graph<T>>(); 

     foreach (var node in this.Nodes) 
     { 
      if (!visitedNodes.Contains(node)) 
      { 
       var subGraph = GetSubGraph(this.DepthFirstSearch(node)); 
       components.Add(subGraph); 
       visitedNodes.UnionWith(subGraph.Nodes); 
      } 
     } 
     return components; 
    } 
} 

Uso:

static void Main(string[] args) 
{ 
    var g = new Graph<long>(); 
    g.AddNodes(new long[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); 
    g.AddArc(1, 2); 
    g.AddArc(1, 3); 

    g.AddArc(9, 6); 
    g.AddArc(6, 7); 
    g.AddArc(6, 8); 

    g.AddArc(4, 5); 

    var subGraphs = g.GetConnectedComponents(); 

} 

Usted podría use la clase Graph<> en lugar de sus mapas, o si quiere seguir con sus mapas, eche un vistazo al código que es bastante fácil de entender (dentro de la clase se usa un Dictionary<T,HashSet<T>> para mantener nodos y arcos, por lo que es muy similar a su enfoque)

+0

hola, ¿hay alguna versión de esto que funcione para Líneas (es decir, con un punto de inicio y un punto final)? consejos muy apreciados – BKSpurgeon

+0

Hola, usando 'g.DepthFirstSearch (startPoint) .ToList()' obtendrás la lista de nodos conectados al nodo de inicio. Luego puede verificar si endPoint está presente en esa lista, si es el caso, significa que startPoint está conectado a endPoint. – digEmAll

Cuestiones relacionadas