Лучший способ хранить разреженную матрицу в .NET - PullRequest
10 голосов
/ 16 апреля 2009

У нас есть приложение, которое хранит разреженную матрицу. Эта матрица имеет записи, которые в основном существуют вокруг главной диагонали матрицы. Мне было интересно, есть ли эффективные алгоритмы (или существующие библиотеки), которые могут эффективно обрабатывать разреженные матрицы такого рода? Предпочтительно, это будет общая реализация, где каждая матричная запись может быть пользовательского типа.

Изменить в ответ на вопрос / ответ:

Когда я говорю в основном вокруг главной диагонали, я имею в виду, что характеристики большинства матриц будут состоять в том, что большинство записей сгруппированы вне главной диагонали, но могут быть нули, близкие к диагонали, и могут быть ненулевые значения далеко от диагонали. Я хочу что-то эффективное для «большинства» случаев здесь.

Для чего я буду использовать это? Мне нужно иметь эффективный доступ ко всем значениям в строке или ко всем значениям в столбце. Сохраненные значения будут логическими значениями. Примером может быть:

  1. Для всех истинных значений в строке, перед каждым столбцом появляется истина в значении, устанавливающем все записи в столбце
  2. Для всех ложных значений в строке установите запись в какое-либо значение

Все это было сделано ранее со связанными списками, но было очень запутанным для реализации. Я надеялся, что с разреженной матрицей я смогу улучшить алгоритм, но найти «правильный» тип алгоритма разреженной матрицы оказалось трудно.

p.s. Спасибо за ответы до сих пор

Ответы [ 6 ]

8 голосов
/ 16 апреля 2009

Вы можете использовать индекс, основанный на [строке, столбце] ячейки. Поскольку данные расположены по диагонали, типичный подход сохранения индекса строки и соответствующего столбца с данными не является оптимальным. Вот код, который вы можете использовать для этого:

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

Обратите внимание, что когда T является структурой, вам может потребоваться вызвать IsCellEmpty, поскольку получение содержимого ячейки не будет нулевым и будет иметь значение по умолчанию для этого типа. Вы также можете расширить код, чтобы получить быстрый SparseRatio на основе свойства Size и _cells.Count.

EDIT:

Ну, если вам интересна скорость, вы можете сделать компромисс между скоростью и пространством. Вместо того, чтобы иметь только один словарь, есть три! Он утраивает ваше пространство, но делает перечисление любым удобным для вас способом. Вот новый код, который показывает, что:

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

Если вы хотите перебрать все записи, используйте _cells. Если вам нужны все строки для данного столбца, используйте _columns. Если вы хотите, чтобы все столбцы в данной строке использовались _rows.

Если вы хотите выполнить итерацию в отсортированном порядке, вы можете начать добавлять LINQ в микс и / или использовать отсортированный список с внутренним классом, который инкапсулирует запись (которая должна хранить строку или столбец и реализовывать * 1018). * для сортировки на работу).

4 голосов
/ 16 апреля 2009

Полагаю, Dictionary<int, Dictionary<int, object >> будет достаточно.

3 голосов
/ 16 апреля 2009

Здесь есть два вопроса:

  • "В основном по главной диагонали" слишком расплывчато. Если элементы лежат в полосах, то используйте полосовое хранение самих полос, поскольку векторы смещены от главной диагонали. Если элементы разбросаны случайным образом в окрестности главной диагонали, то либо используйте полосчатую форму, которая может содержать несколько нулей в полосах, либо используйте чисто разреженную форму, которая хранит только элементы и их позиции в массиве.

  • Что вы будете делать с матрицей? Если ваша цель - просто эффективное хранилище, тогда будет эффективна полосчатая форма с быстрым доступом к любому элементу. Если вы будете выполнять линейную алгебру с матрицей, но не больше, чем умножение вектора на матрицу , то полосчатая форма все равно будет работать великолепно. Если вы работаете с матричным умножением или разложением матриц, где заполнение становится проблемой, тогда более разреженная форма может быть более подходящей. Например, произведение двух полосчатых матриц будет иметь дополнительные полосы, поэтому произведение двух трехдиагональных матриц будет пятиугольным. Для факторизации переупорядочения иногда будут полезны для минимизации заполнения. (AMD - один из вариантов, перестановка Приблизительная минимальная степень, но есть и другие схемы.)

2 голосов
/ 16 апреля 2009

Я не использовал его, но Nmath Matrix обрабатывает это (не бесплатно).

Также Числовые библиотеки экстремальной оптимизации для .NET (не бесплатно).

Вот бесплатный: Math.NET Project (в частности, MathNet.Numerics.LinearAlgebra.Sparse namespace )

1 голос
/ 16 апреля 2009

Вот список общих схем структур данных . У каждого есть свои преимущества и недостатки, и они подходят для слегка различного рода задач, где возникают разреженные матрицы. Возможно, вы захотите реализовать их поверх существующих структур данных, таких как List <> и Dictionary <>.

1 голос
/ 16 апреля 2009

Я думаю, что это можно сделать, используя класс, содержащий простой массив, сохраняя горизонтальное смещение, примененное между строками матрицы, и определяя полосу строки, например, количество действительных записей. Таким образом, для большой матрицы, в которой определены только диагональ и два соседних элемента, вы должны создать массив из 3 * строк и сохранить 3 в качестве ширины полосы. Смещение зависит от размера матрицы.

Я не знаю ничего бесплатного, которое уже делает это.

...