2009-04-16 26 views
8

Tenemos una aplicación que almacena una matriz dispersa. Esta matriz tiene entradas que en su mayoría existen alrededor de la diagonal principal de la matriz. Me preguntaba si había algoritmos eficientes (o bibliotecas existentes) que pudieran manejar de forma eficiente las matrices dispersas de este tipo. Preferiblemente, esto sería una implementación genérica donde cada entrada de matriz puede ser un tipo definido por el usuario.La mejor manera de almacenar una matriz dispersa en .NET

Editar en respuesta a una pregunta/respuesta:

Cuando digo sobre todo alrededor de la diagonal principal que quiere decir que las características de la mayoría de las matrices será que la mayoría de las entradas se agrupan fuera de la diagonal principal pero podría ser ceros cerca de la diagonal y podría haber valores distintos de cero lejos de la diagonal. Quiero algo eficiente para la mayoría de los casos aquí.

¿Para qué voy a usar esto? Necesito poder tener acceso eficiente a todos los valores en una fila o todos los valores en una columna. Los valores almacenados serían valores booleanos. Un ejemplo sería:

  1. Para todos los verdaderos valores en una fila, columna foreach un verdadero aparece en el conjunto de todas las entradas de la columna a algo
  2. Para todos los falsos valores en una fila, establezca la entrada a algo

Todo esto se hizo con listas vinculadas anteriormente pero fue muy confuso de implementar. Tenía la esperanza de que con una matriz dispersa podría mejorar el algoritmo, pero encontrar el tipo correcto de algoritmo de matriz dispersa ha resultado difícil.

p.s. Gracias por las respuestas hasta el momento

+0

He actualizado mi respuesta. Entonces, ¿la eficiencia del rendimiento es más importante que la eficiencia del espacio? Usted dice "forma eficiente de manejar matrices dispersas" y luego en sus casos de uso habla sobre múltiples formas de acceder a los datos. –

+0

Yo diría que el rendimiento es más importante que la eficiencia del espacio. Manejaremos grandes cantidades de datos de todos modos, así que no me importa usar mucho espacio para la matriz, siempre que vaya más rápido –

Respuesta

7

Se podría utilizar un índice basado en el [fila, columna] de la célula. Dado que los datos están en diagonal, el enfoque típico de almacenar el índice de fila y los datos de columna asociados con los datos no es óptimo. Aquí hay un código que puede utilizar para hacerlo:

public class SparseMatrix<T> 
    { 
     public int Width { get; private set; } 
     public int Height { get; private set; } 
     public long Size { get; private set; } 

     private Dictionary<long, T> _cells = new Dictionary<long, T>(); 

     public SparseMatrix(int w, int h) 
     { 
      this.Width = w; 
      this.Height = h; 
      this.Size = w * h; 
     } 

     public bool IsCellEmpty(int row, int col) 
     { 
      long index = row * Width + col; 
      return _cells.ContainsKey(index); 
     } 

     public T this[int row, int col] 
     { 
      get 
      { 
       long index = row * Width + col; 
       T result; 
       _cells.TryGetValue(index, out result); 
       return result; 
      } 
      set 
      { 
       long index = row * Width + col; 
       _cells[index] = value; 
      } 
     } 
    } 

    static void Main() 
    { 
     var sm = new SparseMatrix<int>(512, 512); 
     sm[42, 42] = 42; 
     int val1 = sm[13, 13]; 
     int val2 = sm[42, 42]; 

     Console.WriteLine("VAL1 = " + val1); // prints out 0 
     Console.WriteLine("VAL2 = " + val2); // prints out 42 

     Console.ReadLine(); 
    } 

Tenga en cuenta que cuando T es una estructura, es posible que tenga que llamar a la IsCellEmpty desde conseguir el contenido de una celda no será nulo y tendrá el valor por defecto para ese tipo. También puede expandir el código para darle un "SparseRatio" rápido basado en la propiedad Size y _cells.Count.

EDIT:

Bueno, si usted es interesante es la velocidad, que puede hacer la compensación del espacio vs velocidad. ¡En lugar de tener solo un diccionario, tiene tres! Triplica tu espacio, pero hace que la enumeración de la forma que quieras sea realmente fácil. Aquí está un nuevo código que muestra que:

public class SparseMatrix<T> 
    { 
     public int Width { get; private set; } 
     public int Height { get; private set; } 
     public long MaxSize { get; private set; } 
     public long Count { get { return _cells.Count; } } 

     private Dictionary<long, T> _cells = new Dictionary<long, T>(); 

     private Dictionary<int, Dictionary<int, T>> _rows = 
      new Dictionary<int, Dictionary<int, T>>(); 

     private Dictionary<int, Dictionary<int, T>> _columns = 
      new Dictionary<int, Dictionary<int, T>>(); 

     public SparseMatrix(int w, int h) 
     { 
      this.Width = w; 
      this.Height = h; 
      this.MaxSize = w * h; 
     } 

     public bool IsCellEmpty(int row, int col) 
     { 
      long index = row * Width + col; 
      return _cells.ContainsKey(index); 
     } 

     public T this[int row, int col] 
     { 
      get 
      { 
       long index = row * Width + col; 
       T result; 
       _cells.TryGetValue(index, out result); 
       return result; 
      } 
      set 
      { 
       long index = row * Width + col; 
       _cells[index] = value; 

       UpdateValue(col, row, _columns, value); 
       UpdateValue(row, col, _rows, value); 
      } 
     } 

     private void UpdateValue(int index1, int index2, 
      Dictionary<int, Dictionary<int, T>> parent, T value) 
     { 
      Dictionary<int, T> dict; 
      if (!parent.TryGetValue(index1, out dict)) 
      { 
       parent[index2] = dict = new Dictionary<int, T>(); 
      } 
      dict[index2] = value; 
     } 
    } 

Si desea iterar sobre todas las entradas, utilice _cells. Si desea todas las filas para una columna determinada, use _columns. Si desea todas las columnas en una fila determinada, use _rows.

Si desea iterar en orden ordenado, puede comenzar a agregar LINQ en la mezcla y/o utilizar una lista ordenada con una clase interna que encapsula una entrada (que debería almacenar la fila o columna e implementar IComparable<T> para clasificar para trabajar).

+0

Gracias, me gusta a dónde vas con esto. Usar diccionarios no me da acceso eficiente a filas o columnas enteras, ¿o sí? (Tal vez usando Linq lo hace ...?). Ver mi edición arriba. –

+0

Vea la actualización para otra opción.Si el espacio no es un problema, haga las concesiones para obtener un acceso más rápido teniendo múltiples diccionarios. –

+0

Excelentes sugerencias, muchas gracias –

4

supongo que sería suficiente una Dictionary<int, Dictionary<int, object >>.

1

Creo que esto podría hacerse utilizando una clase que contenga una matriz simple, guardando la compensación horizontal aplicada entre las filas de la matriz y la definición de la franja de una fila, p. la cantidad de entradas válidas Entonces, para una matriz grande donde solo se definen los elementos diagonales y dos vecinos se crearía una matriz de 3 * número de filas y se almacenaría 3 como ancho de la franja. El desplazamiento depende del tamaño de la matriz.

No tengo conocimiento de nada gratuito que ya lo haga.

+0

Buena idea. Podría implementarlo como tal: Suponiendo solo una entrada positiva, podríamos manejar los números negativos como el número de 0 entradas entre las entradas. Entonces el siguiente ... [1,2, -30,0,1,2, -29] ​​ Se expande en [1,2,0,0 ...] [0,1,2,0 ...] Para compensar, la matriz [m * fila + columna] es (fila, columna) de una matriz mxn –

1

Aquí hay una lista de general data structure schemas. Cada uno tiene sus ventajas y desventajas, y son adecuados para tipos ligeramente diferentes de problemas donde surgen matrices dispersas. Probablemente desee implementarlos sobre las estructuras de datos existentes, como List <> y Dictionary <>.

2

Hay dos preguntas aquí:

  • "En su mayoría alrededor de la diagonal principal" es demasiado vaga. Si los elementos se encuentran en bandas, utilice el almacenamiento por bandas de las mismas bandas, como vectores desplazados de la diagonal principal.Si los elementos están dispersos aleatoriamente en las proximidades de la diagonal principal, entonces use una forma en bandas que puede incluir algunos ceros en las bandas, o use una forma puramente dispersa que almacena solo los elementos y sus posiciones en la matriz.

  • ¿Qué harás con la matriz? Si su objetivo es simplemente el almacenamiento eficiente, una forma de banda será eficiente, con acceso rápido a cualquier elemento. Si vas a hacer álgebra lineal con la matriz, pero nunca más que la matriz , el vector se multiplica, entonces la forma en bandas funcionará espléndidamente. Si trabaja con matriz multiplicaciones de matrices o factorizaciones de matrices, donde el relleno se convierte en un problema, entonces una forma puramente dispersa puede ser más apropiada. Por ejemplo, el producto de dos matrices con bandas tendrá bandas adicionales, por lo que el producto de dos matrices tridiagonales será pentadiagonal. Para una factorización, los reordenamientos a veces serán útiles para minimizar el relleno. (DMAE es una opción, la permutación aproximado mínimo grado, pero hay otros esquemas.)

Cuestiones relacionadas