Algorytm Strassenaalgorytm wykorzystywany do mnożenia macierzy. Został nazwany na cześć swego twórcy – Volkera Strassena. Jest on szybszy od standardowego mnożenia macierzy i jest użyteczny dla dużych macierzy, ale działa wolniej od najszybszej znanej obecnie metody mnożenia – algorytmu Coppersmitha-Winograda – dla bardzo dużych macierzy.

Algorytm został opublikowany w 1969 roku. Pomimo że był tylko trochę szybszy od metody klasycznej, jako pierwszy wykazał, że standardowe podejście nie jest optymalne. Dzięki niemu rozpoczęto poszukiwania jeszcze szybszych algorytmów, aż do algorytmu Coppersmitha-Winograda opublikowanego w 1987 roku.

Działanie algorytmu edytuj

Lewa kolumna przedstawia mnożenie macierzy o rozmiarze 2 na 2. Naiwna metoda mnożenia wymaga jednego mnożenia dla każdej jedynki z lewej kolumny. Pozostałe kolumny reprezentują kolejne z siedmiu mnożeń algorytmu. Suma kolumn daje prawidłowe pomnożenie macierzy pokazane w lewej kolumnie.

Niech   i   będą dwiema macierzami kwadratowymi nad pierścieniem   Chcemy obliczyć macierz   taką, że

 

Jeżeli macierze   i   nie są rozmiaru   to brakujące kolumny i wiersze wypełniamy zerami. Następnie dzielimy macierze     i   na macierze klatkowe o równych rozmiarach.

 

gdzie:

 

czyli

 
 
 
 

W powyższym przypadku nie zredukowaliśmy liczby potrzebnych do wykonania mnożeń. Wciąż musimy wykonać 8 mnożeń, czyli tyle samo, jak w przypadku zwykłego mnożenia macierzy.

Definiujemy nowe macierze według poniższego wzoru:

 
 
 
 
 
 
 

Powyższe macierze zostaną użyte do wyrażenia macierzy   w zależności od Mk. Dzięki takiemu zdefiniowaniu macierzy możemy wyeliminować jedno z mnożeń w algorytmie, wyrażając   jako:

 
 
 
 

Iterujemy powyższy proces podziału   razy, dopóki podmacierze nie przerodzą się w liczby (elementy pierścienia  ).

Praktyczna implementacja algorytmu Strassena przekształca standardową metodę mnożenia macierzy do wystarczająco małych podmacierzy, dla których algorytm jest bardziej efektywny. Szczególny przypadek, dla którego algorytm Strassena jest bardziej efektywny, zależy od szczegółów jego implementacji oraz sprzętu.

Złożoność obliczeniowa edytuj

Standardowe mnożenie macierzy posiada złożoność obliczeniową rzędu   gdyż potrzebnych jest 8 rekurencyjnych operacji mnożenia macierzy. W metodzie Strassena dzięki zastosowaniu innego rekurencyjnego podejścia do problemu wymagane jest 7 rekurencyjnych mnożeń oraz   operacji dodawania lub odejmowania wartości skalarnej. Wyznaczając złożoność obliczeniową, otrzymujemy wynik   czyli w przybliżeniu  

Implementacja algorytmu w języku C edytuj

/*------------------------------------------------------------------------------*/

/* Program kompilować bez konsolidacji                                                             */
/* Zakładając, że nazwa pliku to Strassen.c to przy użyciu gcc kompilacja powinna wyglądać:        */
/*     gcc -c Strassen.c                                                                           */

#include <stdio.h>
#include <stdlib.h>

void strassen(double **a, double **b, double **c, int tam);
void sum(double **a, double **b, double **result, int tam);
void subtract(double **a, double **b, double **result, int tam);
double **allocate_real_matrix(int tam, int random);
double **free_real_matrix(double **v, int tam);

void strassen(double **a, double **b, double **c, int tam) {

    // najprostszy przypadek: kiedy macierz ma rozmiar 1 na 1:
    if (tam == 1) {
        c[0][0] = a[0][0] * b[0][0];
        return;
    }

    // pozostałe przypadki:
    else {
        int newTam = tam/2;
        double **a11, **a12, **a21, **a22;
        double **b11, **b12, **b21, **b22;
        double **c11, **c12, **c21, **c22;
        double **p1, **p2, **p3, **p4, **p5, **p6, **p7;

        // alokacja pamięci:
        a11 = allocate_real_matrix(newTam, -1);
        a12 = allocate_real_matrix(newTam, -1);
        a21 = allocate_real_matrix(newTam, -1);
        a22 = allocate_real_matrix(newTam, -1);

        b11 = allocate_real_matrix(newTam, -1);
        b12 = allocate_real_matrix(newTam, -1);
        b21 = allocate_real_matrix(newTam, -1);
        b22 = allocate_real_matrix(newTam, -1);

        c11 = allocate_real_matrix(newTam, -1);
        c12 = allocate_real_matrix(newTam, -1);
        c21 = allocate_real_matrix(newTam, -1);
        c22 = allocate_real_matrix(newTam, -1);

        p1 = allocate_real_matrix(newTam, -1);
        p2 = allocate_real_matrix(newTam, -1);
        p3 = allocate_real_matrix(newTam, -1);
        p4 = allocate_real_matrix(newTam, -1);
        p5 = allocate_real_matrix(newTam, -1);
        p6 = allocate_real_matrix(newTam, -1);
        p7 = allocate_real_matrix(newTam, -1);

        double **aResult = allocate_real_matrix(newTam, -1);
        double **bResult = allocate_real_matrix(newTam, -1);

        int i, j;

        //dzielenie macierzy na cztery podmacierze:
        for (i = 0; i < newTam; i++) {
            for (j = 0; j < newTam; j++) {
                a11[i][j] = a[i][j];
                a12[i][j] = a[i][j + newTam];
                a21[i][j] = a[i + newTam][j];
                a22[i][j] = a[i + newTam][j + newTam];

                b11[i][j] = b[i][j];
                b12[i][j] = b[i][j + newTam];
                b21[i][j] = b[i + newTam][j];
                b22[i][j] = b[i + newTam][j + newTam];
            }
        }

        //obliczanie macierzy od M1 do M7:

        sum(a11, a22, aResult, newTam); // a11 + a22
        sum(b11, b22, bResult, newTam); // b11 + b22
        strassen(aResult, bResult, p1, newTam); // p1 = (a11+a22) * (b11+b22)

        sum(a21, a22, aResult, newTam); // a21 + a22
        strassen(aResult, b11, p2, newTam); // p2 = (a21+a22) * (b11)

        subtract(b12, b22, bResult, newTam); // b12 - b22
        strassen(a11, bResult, p3, newTam); // p3 = (a11) * (b12 - b22)

        subtract(b21, b11, bResult, newTam); // b21 - b11
        strassen(a22, bResult, p4, newTam); // p4 = (a22) * (b21 - b11)

        sum(a11, a12, aResult, newTam); // a11 + a12
        strassen(aResult, b22, p5, newTam); // p5 = (a11+a12) * (b22)

        subtract(a21, a11, aResult, newTam); // a21 - a11
        sum(b11, b12, bResult, newTam); // b11 + b12
        strassen(aResult, bResult, p6, newTam); // p6 = (a21-a11) * (b11+b12)

        subtract(a12, a22, aResult, newTam); // a12 - a22
        sum(b21, b22, bResult, newTam); // b21 + b22
        strassen(aResult, bResult, p7, newTam); // p7 = (a12-a22) * (b21+b22)

        //obliczanie c21, c21, c11 i c22:

        sum(p3, p5, c12, newTam); // c12 = p3 + p5
        sum(p2, p4, c21, newTam); // c21 = p2 + p4

        sum(p1, p4, aResult, newTam); // p1 + p4
        sum(aResult, p7, bResult, newTam); // p1 + p4 + p7
        subtract(bResult, p5, c11, newTam); // c11 = p1 + p4 - p5 + p7

        sum(p1, p3, aResult, newTam); // p1 + p3
        sum(aResult, p6, bResult, newTam); // p1 + p3 + p6
        subtract(bResult, p2, c22, newTam); // c22 = p1 + p3 - p2 + p6

        //grupowanie wyników w jedną macierz:
        for (i = 0; i < newTam ; i++) {
            for (j = 0 ; j < newTam ; j++) {
                c[i][j] = c11[i][j];
                c[i][j + newTam] = c12[i][j];
                c[i + newTam][j] = c21[i][j];
                c[i + newTam][j + newTam] = c22[i][j];
            }
        }

        //dealokacja pamięci:
        a11 = free_real_matrix(a11, newTam);
        a12 = free_real_matrix(a12, newTam);
        a21 = free_real_matrix(a21, newTam);
        a22 = free_real_matrix(a22, newTam);

        b11 = free_real_matrix(b11, newTam);
        b12 = free_real_matrix(b12, newTam);
        b21 = free_real_matrix(b21, newTam);
        b22 = free_real_matrix(b22, newTam);

        c11 = free_real_matrix(c11, newTam);
        c12 = free_real_matrix(c12, newTam);
        c21 = free_real_matrix(c21, newTam);
        c22 = free_real_matrix(c22, newTam);

        p1 = free_real_matrix(p1, newTam);
        p2 = free_real_matrix(p2, newTam);
        p3 = free_real_matrix(p3, newTam);
        p4 = free_real_matrix(p4, newTam);
        p5 = free_real_matrix(p5, newTam);
        p6 = free_real_matrix(p6, newTam);
        p7 = free_real_matrix(p7, newTam);
        aResult = free_real_matrix(aResult, newTam);
        bResult = free_real_matrix(bResult, newTam);
    } //koniec przypadku else

} //koniec funkcji Strassen

/*------------------------------------------------------------------------------*/
//funkcja sumująca dwie macierze
void sum(double **a, double **b, double **result, int tam) {

    int i, j;

    for (i = 0; i < tam; i++) {
        for (j = 0; j < tam; j++) {
            result[i][j] = a[i][j] + b[i][j];
        }
    }
}

/*------------------------------------------------------------------------------*/
//funkcja odejmująca jedną macierz od drugiej
void subtract(double **a, double **b, double **result, int tam) {

    int i, j;

    for (i = 0; i < tam; i++) {
        for (j = 0; j < tam; j++) {
            result[i][j] = a[i][j] - b[i][j];
        }
    }
}

/*------------------------------------------------------------------------------*/
// Ta funkcja alokuje macierz przy użyciu funkcji malloc oraz inicjalizuje ją. Jeżeli zmienna losowa
// jest zerem to funkcja inicjalizuje macierz o wartościach zerowych, jeżeli jest jedynką to inicjalizuje
// inicjalizuje macierz z losowymi wartościami. Jeżeli jest jakąkolwiek inną wartością to macierz jest
// inicjalizowana bez żadnych wartości Zmienna tam określa długość macierzy.
double **allocate_real_matrix(int tam, int random) {

    int i, j, n = tam, m = tam;
    double **v, a;         // wskaźnik na wektor

    // alokuje jeden wektor wektoru (macierz)
    v = (double**) malloc(n * sizeof(double*));

    if (v == NULL) {
        printf ("** Blad alokacji macierzy: Niewystarczajaca pamiec **");
        return (NULL);
    }

    // alokacja każdego rzędu macierzy
    for (i = 0; i < n; i++) {
        v[i] = (double*) malloc(m * sizeof(double));

        if (v[i] == NULL) {
            printf ("** Blad: Niewystarczajaca pamiec **");
            free_real_matrix(v, n);
            return (NULL);
        }

        // inicjalizacja macierzy o wartościach zerowych
        if (random == 0) {
            for (j = 0; j < m; j++)
                v[i][j] = 0.0;
        }

        // inicjalizacja macierzy o wartościach losowych z przedziału od 0 do 10
        else {
            if (random == 1) {
                for (j = 0; j < m; j++) {
                    a = rand();
                    v[i][j] = (a - (int)a) * 10;
                }
            }
        }
    }

    return (v);     // zwraca wskaźnik na wektor.
}

/*------------------------------------------------------------------------------*/
// Ta funkcja dealokuje macierz (zwalnia pamięć)
double **free_real_matrix(double **v, int tam) {

    int i;

    if (v == NULL) {
        return (NULL);
    }

    for (i = 0; i < tam; i++) {
        if (v[i]) {
            free(v[i]); // zwalnia rząd macierzy
            v[i] = NULL;
        }
    }

    free(v);         // zwalnia wskaźnik /
    v = NULL;

    return (NULL);   //zwraca wskaźnik NULL /
}

/*------------------------------------------------------------------------------*/

Bibliografia edytuj

  • Volker Strassen, Gaussian Elimination is not Optimal, Numer. Math. 13, s. 354–356, 1969.
  • Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein. Wprowadzenie do algorytmów, wyd. siódme, Wydawnictwa Naukowo-Techniczne, s. 751–758, Warszawa 2005.