Sparse Matrices
Sparse Matrices
Introduction
In economic applications it's not uncommon to have matrices with a large number of
zero-valued elements and, because C/C++ stores zeros in the same way it stores any
other numeric value, these elements can use memory space unnecessarily and can
sometimes require extra computing time. Examples of applications that use sparse
matrices: graph stored as adjacency matrices, optimization problems using linear
algebra and sparse linear equation systems.
Sparse matrices provide a way to store data that has a large percentage of zero
elements more efficiently. While full matrices internally store every element in
memory regardless of value, sparse matrices store only the nonzero elements and their
position. Using sparse matrices can significantly reduce the amount of memory
required for data storage.
Because sparse matrices store both element values and position for non-zero elements,
they require more storage space if number of such elements isn’t much smaller than
zero elements. Another drawback of using a sparse matrix is that it doesn’t provide
direct access to elements. Basic operations are also more difficult to implement
compared to normal matrices.
These tradeoffs must be well considered when deciding whether to use a normal or a
sparse matrix. Usually sparse matrices are used when dealing with large volumes of
data that contain between 0.15% and 3% non-zero elements.
Storage
When storing a sparse matrix we need to consider two kinds of information:
In the following sections we’ll use the first form (we’ll use three dynamically
allocated arrays for storing line indices, column indices and values). For easier
manipulation all the information related to a matrix is grouped into a C structure:
struct MatriceRara
{
// vectorii pentru stocarea liniei/coloanei si valorii
int *Linie, *Coloana, *Valoare;
Conversions
The conversions between normal matrices and sparse matrices and vice versa are
straightforward:
indiceMatR++;
}
return matR;
}
// returnare rezultat
return mat;
}
Basic operations
One way to implement matrix operations on sparse matrices is to simulate direct
access to elements using Get/Set functions and use the traditional algorithms. This is
the simplest method but has the drawback that is very slow because we must consider
all elements (even non-zero ones) and use the sequential access functions that need
O(nrZ) time to complete.
A better solution is to develop specialized algorithms that use the particularities of the
sparse matrices to provide a fast execution time. In order to speed up the operations
we’ll assume that elements inside the sparse matrix are sorted according to (line,
column); all operations presented here preserve this property on the matrices.
Transpose
The algorithm to compute the transpose of a sparse matrix is very simple. All we need
to do is swap the columns and line indices and sort the resulted arrays in order to
preserve the mentioned property:
return R;
}
Addition
The algorithm for sparse matrix addition has three steps:
• check that the matrix sizes are equal
• determine the number of elements in the resulted matrix and allocate memory
for them
• compute the value for all elements and store them in the resulted matrix
Because the matrices are sorted by (line, column) we can do only one pass to
determine the number of elements and to compute the values. In the process we take
care to eliminate zero elements in the result.
// Aduna doua matrici rare
MatriceRara Adunare(const MatriceRara& M1, const MatriceRara& M2)
{
MatriceRara R = {NULL, NULL, NULL, 0, 0, 0};
i++; j++;
}
}
}
// initializare rezultat
R.Linie = new int[nr];
R.Coloana = new int[nr];
R.Valoare = new int[nr];
R.nrElemente = nr;
R.nrLinii = M1.nrLinii;
R.nrColoane = M1.nrColoane;
// calculare rezultat
int k = 0; // indice in matricea rezultat
i = 0; j = 0;
while (k < R.nrElemente)
{
if (M1.Linie[i] < M2.Linie[j])
{
R.Linie[k] = M2.Linie[i];
R.Coloana[k] = M2.Coloana[i];
R.Valoare[k] = M2.Valoare[i];
i++; k++;
}
else if (M1.Linie[i] > M2.Linie[j])
{
R.Linie[k] = M1.Linie[j];
R.Coloana[k] = M1.Coloana[j];
R.Valoare[k] = M1.Valoare[j];
j++; k++;
}
else
{
if (M1.Coloana[i] < M2.Coloana[j])
{
R.Linie[k] = M2.Linie[i];
R.Coloana[k] = M2.Coloana[i];
R.Valoare[k] = M2.Valoare[i];
i++; k++;
}
else if (M1.Coloana[i] > M2.Coloana[j])
{
R.Linie[k] = M1.Linie[j];
R.Coloana[k] = M1.Coloana[j];
R.Valoare[k] = M1.Valoare[j];
j++; k++;
}
else
{
if (M1.Valoare[i] + M2.Valoare[j] == 0)
{
i++; j++; // element comun nul
}
else
{
R.Linie[k] = M1.Linie[i];;
R.Coloana[k] = M1.Coloana[i];
R.Valoare[k] = M1.Valoare[i] +
M2.Valoare[j];
i++; j++; k++; // element comun nenul
}
}
}
}
return R;
}
Multiplication
Matrix multiplication is done in two phases. In the first phase we simulate the
operation in order to determine the number of elements in the result set and in the
second one we compute the result.
In order to compute the value of one element in the result set we do the following:
• for every element in the current column from the first matrix
if (M1.nrColoane != M2.nrLinii)
{
cout << "Eroare: Dimensiunile matricelor nu corespund." << endl;
return R;
}
// initializare rezultat
R.Linie = new int[nr];
R.Coloana = new int[nr];
R.Valoare = new int[nr];
R.nrElemente = nr;
R.nrLinii = M1.nrLinii;
R.nrColoane = M2.nrColoane;
// calcul rezultat
int indice = 0;
for (int i = 0; i < M1.nrLinii; i++)
for (int j = 0; j < M2.nrLinii; j++)
{
suma = 0;
2. Write functions to read a sparse matrix from the console and to write the result
in both forms (sparse and normal).
3. Write a function to multiply a sparse matrix with a constant. Use this function
in conjunction with the addition to simulate sparse matrix subtraction.
5. Give a solution to eliminate the necessity of the first pass in the matrix
multiplication algorithm.
Solution:
We model the problem as an undirected graph with cities represented as vertexes and
flight segments as edges and we store the graph as an adjacency matrix A (using a
sparse matrix). In this case Ak will contain all flight routes of length k between cities.