2009-04-16 8 views
8

Nous avons une application qui stocke une matrice clairsemée. Cette matrice a des entrées qui existent principalement autour de la diagonale principale de la matrice. Je me demandais s'il y avait des algorithmes efficaces (ou des bibliothèques existantes) qui peuvent gérer efficacement les matrices creuses de ce genre? De préférence, il s'agirait d'une implémentation générique où chaque entrée de matrice peut être un type défini par l'utilisateur.Meilleure façon de stocker une matrice clairsemée dans .NET

Modifier en réponse à une question/réponse:

Quand je dis la plupart du temps autour de la diagonale principale, je veux dire que les caractéristiques de la plupart des matrices seront que la plupart des entrées sont regroupées hors de la diagonale principale, mais il pourrait y avoir soit des zéros proches de la diagonale et il pourrait y avoir des valeurs non nulles loin de la diagonale. Je veux quelque chose d'efficace pour la plupart des cas ici.

Pour quoi vais-je l'utiliser? Je dois être en mesure d'avoir un accès efficace à toutes les valeurs d'une ligne ou à toutes les valeurs d'une colonne. Les valeurs stockées seraient des valeurs booléennes. Un exemple serait:

  1. Pour toutes les valeurs vraies dans une ligne, une colonne de foreach un vrai apparaît dans le jeu toutes les entrées de la colonne à quelque chose
  2. Pour toutes les fausses valeurs d'une ligne, définissez l'entrée à quelque chose

Tout cela a été fait avec des listes liées auparavant, mais était très difficile à mettre en œuvre. J'espérais qu'avec une matrice clairsemée, je pourrais améliorer l'algorithme, mais trouver le bon type d'algorithme de matrice clairsemée s'est avéré difficile.

p.s. Merci pour les réponses à ce jour

+0

J'ai mis à jour ma réponse. L'efficacité de la performance est-elle plus importante que l'efficacité de l'espace? Vous dites "moyen efficace de gérer les matrices creuses" et ensuite dans vos cas d'utilisation, vous parlez de multiples façons d'accéder aux données. –

+0

Je dirais que la performance est plus importante que l'efficacité de l'espace. Nous allons traiter de très grandes quantités de données, donc ça ne me dérange pas d'utiliser beaucoup d'espace pour la matrice tant que ça va plus vite –

Répondre

7

Vous pouvez utiliser un indice basé sur la [ligne, colonne] de la cellule. Comme les données sont en diagonale, l'approche typique consistant à stocker l'index de ligne et les indeces de colonne associés avec des données n'est pas optimale. Voici un code que vous pouvez utiliser pour le faire:

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(); 
    } 

Notez que lorsque T est un struct, vous pourriez avoir à appeler le IsCellEmpty depuis l'obtention du contenu d'une cellule ne sera pas nulle et aura la valeur par défaut pour ce type. Vous pouvez également développer le code pour obtenir un "SparseRatio" rapide basé sur la propriété Size et _cells.Count.

EDIT:

Eh bien, si vous êtes intéressant est la vitesse, vous pouvez faire le compromis de l'espace vs vitesse. Au lieu de n'avoir qu'un seul dictionnaire, en avoir trois! Il triple votre espace, mais il rend l'énumération de toute façon que vous voulez vraiment facile. Voici un nouveau code qui montre 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 vous voulez itérer sur toutes les entrées, utilisez _cells. Si vous voulez toutes les lignes pour une colonne donnée, utilisez _columns. Si vous voulez toutes les colonnes d'une ligne donnée, utilisez _rows. Si vous souhaitez effectuer une itération par ordre trié, vous pouvez commencer à ajouter LINQ au mélange et/ou utiliser une liste triée avec une classe interne qui encapsule une entrée (qui doit stocker la ligne ou la colonne et implémenter IComparable<T> pour trier au travail).

+0

Merci, j'aime bien où vous allez avec ça. L'utilisation de dictionnaires ne me donne pas un accès efficace à des lignes entières ou à des colonnes, n'est-ce pas? (peut-être en utilisant Linq ça fait ...?). Voir ma modification ci-dessus. –

+0

Voir la mise à jour pour une autre option.Si l'espace n'est pas un problème, faites le compromis pour obtenir un accès plus rapide en ayant plusieurs dictionnaires. –

+0

Excellentes suggestions, merci beaucoup –

4

Je suppose que cela suffirait Dictionary<int, Dictionary<int, object >>.

1

Je pense que cela pourrait être fait en utilisant un tableau simple contenant une classe, en sauvegardant le décalage horizontal appliqué entre les lignes de la matrice et en définissant la bande d'une ligne, par ex. le nombre d'entrées valides. Donc, pour une grande matrice où seulement la diagonale et deux éléments voisins sont définis, vous devez créer un tableau de 3 * nombre de rangées et stocker 3 comme largeur de bande. Le décalage dépend de la taille de la matrice.

Je ne connais rien de gratuit qui le fasse déjà.

+0

Bonne idée. Je pourrais l'implémenter comme tel: En supposant seulement une entrée positive, nous pourrions manipuler des nombres négatifs comme le nombre de 0 entrées entre les entrées. Donc, ce qui suit ... [1,2, -30,0,1,2, -29] ​​ S'étend dans [1,2,0,0 ...] [0,1,2,0 ...] Pour compenser, array [m * row + column] est (row, column) d'une matrice mxn –

1

Voici une liste de général data structure schemas. Chacun a ses avantages et ses inconvénients et convient à des types de problèmes légèrement différents où des matrices éparses apparaissent. Vous voudrez probablement les implémenter au-dessus des structures de données existantes, telles que la liste <> et le dictionnaire <>.

2

Il y a deux questions ici:

  • "La plupart du temps autour de la diagonale principale" est trop vague. Si les éléments se trouvent dans des bandes, utilisez le stockage par bandes des bandes elles-mêmes, en tant que vecteurs décalés par rapport à la diagonale principale.Si les éléments sont dispersés de manière aléatoire au voisinage de la diagonale principale, alors utilisez une forme en bandes qui peut inclure des zéros dans les bandes, ou utilisez une forme pure qui ne stocke que les éléments et leurs positions dans le tableau.

  • Que ferez-vous avec la matrice? Si votre but est simplement un stockage efficace, alors un formulaire en bandes sera efficace, avec un accès rapide à n'importe quel élément. Si vous allez faire de l'algèbre linéaire avec la matrice, mais jamais plus que la matrice multiplie le vecteur, alors la forme en bandes fonctionnera toujours magnifiquement. Si vous travaillez avec des matrices matricielles ou des factorisations matricielles, où le remplissage devient un problème, alors une forme épurée pure peut être plus appropriée. Par exemple, le produit de deux matrices à bandes aura des bandes supplémentaires, de sorte que le produit de deux matrices tridiagonales sera pentadiagonal. Pour une factorisation, les réorganisations seront parfois utiles pour minimiser le remplissage. (AMD est un choix, approximative permutation de degré minimum, mais il existe d'autres systèmes.)