Click here to Skip to main content
15,888,454 members
Articles / Programming Languages / C++
Article

Compute Permanent of a Matrix with Ryser's Algorithm

Rate me:
Please Sign up or sign in to vote.
4.85/5 (4 votes)
13 Nov 2007CPOL3 min read 34.9K   21   1
A quick introduction to permanents and an implementation of a variant of Ryser's formula

Introduction

This article introduces the permanent of a matrix (as a function mapping matrices to real numbers) and in particular a straightforward implementation of Ryser's formula to compute the exact permanent in O(n*2^n).

Although permanents are defined similarly to the determinant, the latter being computable in T(n³) (see my CSML project on CodeProject), no polynomial algorithm for permanent computation is known so far.

Furthermore, the computation of a permanent of a 0-1-matrix A is #P-complete, and a polynomial algorithm for permanents would particularly imply P = NP. However, approximation is well possible: "When the entries of A are nonnegative, the permanent can be computed approximately in probabilistic polynomial time, up to an error of eM, where M is the value of the permanent and e > 0 is arbitrary." (See Wiki article.)

Definition

Let

Screenshot - a_def.gif

be a real n by n matrix, then the permanent is defined by

Screenshot - per_leibniz.gif

(Leibniz's formula), where

Screenshot - pi_def.gif

For instance, consider the n by n 1-matrix U (all entries are ones), then we receive per(U) = n! -- why? There are n! permutations of {1,...,n}, and each permutated product in Leibniz's formula is equal to one.

Complexity of Leibniz's formula: (n-1)*(n!) flops, which is super-exponential.

Sadly, there is no geometric interpretation of what a permanent is (determinants can be considered an n-dimensional volume); its use is mainly in combinatorics and graph theory: The permanent is a count for the number of perfect matchings in a bipartite graph (ibd).

Moreover, there is a recursive algorithm similar to the Laplacian development of the determinant. You can develop by the i-th row

Screenshot - per_laplace_row.gif

or by the j-th column

Screenshot - per_laplace_col.gif

where the i-j-minor of A is defined by

Screenshot - a_minor.gif

This algorithm is still super-exponential, but it is well suited to calculate permanents of very small matrices by hand.

Ryser's Formula

The fastest known exact algorithm for the computation of the permanent is due to Ryser:

Screenshot - per_ryser.gif

where

Screenshot - gamma_def.gif

is the set of all n by k submatrices of A, and

Screenshot - rowsum_def.gif

is the i-th row sum of X. Ideally, Ryser's algorithm needs (n-1)(2^n - 1) flops; the implementation presented here runs in O((n^2)(2^n)).

The Code

At first, we need to transform a decimal to a binary array with an additional element (the last one) saving the number of ones in the array:

C++
inline int* dec2binarr(long n, int dim)
{
    // note: res[dim] will save the sum res[0]+...+res[dim-1]
    int* res = (int*)calloc(dim + 1, sizeof(int));   
    int pos = dim - 1;

    // note: this will crash if dim < log_2(n)...
    while (n > 0)
    {
        res[pos] = n % 2;
        res[dim] += res[pos];
        n = n / 2; // integer division        
        pos--;
    }

    return res;
}

Here comes the actual algorithm:

C++
long per(int* A, int n) // expects n by n matrix encoded as vector
{
    long sum = 0;
    long rowsumprod, rowsum;
    int* chi = new int[n + 1];    
    double C = (double)pow((double)2, n); 

    // loop all 2^n submatrices of A
    for (int k = 1; k < C; k++)
    {
        rowsumprod = 1;
        chi = dec2binarr(k, n); // characteristic vector        

        // loop columns of submatrix #k
        for (int m = 0; m < n; m++)
        {
            rowsum = 0;

            // loop rows and compute rowsum
            for (int p = 0; p < n; p++)
                rowsum += chi[p] * A[m * n + p];
        
            // update product of rowsums
            rowsumprod *= rowsum;    
        
            // (optional -- use for sparse matrices)
            // if (rowsumprod == 0) break;    
        }        
        
        sum += (long)pow((double)-1, n - chi[n]) * rowsumprod;        
    }    

    return sum;
} 

Notes

  1. The submatrices are chosen by use of a characteristic vector chi (only the columns are considered, where chi[p] == 1). To retrieve the t from Ryser's formula, we need to save the number n-t, as is done in chi[n]. Then we get t = n - chi[n].
  2. The matrix parameter A is expected to be a one-dimensional integer array -- should the matrix be encoded row-wise or column-wise? -- It doesn't matter. The permanent is invariant under row-switching and column-switching, and it is Screenshot - per_inv.gif .
  3. Further enhancements: If any rowsum equals zero, the entire rowsumprod becomes zero, and thus the m-loop can be broken. Since if-statements are relatively expensive compared to integer operations, this might save time only for sparse matrices (where most entries are zeros).
  4. If anyone finds a polynomial algorithm for permanents, he will get rich and famous (at least in the computer science world).

History

  • 13th November, 2007: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralThanks for the example! Pin
thewopr16-Apr-12 13:37
thewopr16-Apr-12 13:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.