Worldscope

Doolittle Algorithm: LU Decomposition

Palavras-chave:

Publicado em: 12/08/2025

Doolittle Algorithm: LU Decomposition

LU decomposition is a matrix factorization technique that decomposes a matrix into the product of a lower triangular matrix (L) and an upper triangular matrix (U). The Doolittle algorithm is a specific type of LU decomposition where the diagonal elements of the lower triangular matrix (L) are all ones. This article explains the Doolittle algorithm and provides a C implementation.

Fundamental Concepts / Prerequisites

To understand the Doolittle algorithm, you should be familiar with the following concepts:

  • Matrices: Basic matrix operations (addition, subtraction, multiplication).
  • Triangular Matrices: Understanding of lower triangular and upper triangular matrices. A lower triangular matrix has all elements above the main diagonal equal to zero. An upper triangular matrix has all elements below the main diagonal equal to zero.
  • Linear Algebra: Basic principles of linear algebra.

Implementation in C


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

// Function to perform Doolittle LU decomposition
void luDecomposition(double** A, int n, double** L, double** U) {
    for (int i = 0; i < n; i++) {
        // Upper Triangular Matrix (U)
        for (int k = i; k < n; k++) {
            double sum = 0;
            for (int j = 0; j < i; j++) {
                sum += (L[i][j] * U[j][k]);
            }
            U[i][k] = A[i][k] - sum;
        }

        // Lower Triangular Matrix (L)
        for (int k = i; k < n; k++) {
            if (i == k) {
                L[i][i] = 1; // Diagonal elements of L are 1
            } else {
                double sum = 0;
                for (int j = 0; j < i; j++) {
                    sum += (L[k][j] * U[j][i]);
                }
                L[k][i] = (A[k][i] - sum) / U[i][i];
            }
        }
    }
}

// Function to print a matrix
void printMatrix(double** matrix, int n) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            printf("%lf\t", matrix[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int n = 3;  // Size of the matrix

    // Example matrix A
    double** A = (double**)malloc(n * sizeof(double*));
    for (int i = 0; i < n; i++) {
        A[i] = (double*)malloc(n * sizeof(double));
    }
    A[0][0] = 2; A[0][1] = 1; A[0][2] = 1;
    A[1][0] = 4; A[1][1] = 1; A[1][2] = 0;
    A[2][0] = -2; A[2][1] = 2; A[2][2] = 1;

    // Allocate memory for L and U
    double** L = (double**)malloc(n * sizeof(double*));
    for (int i = 0; i < n; i++) {
        L[i] = (double*)malloc(n * sizeof(double));
    }

    double** U = (double**)malloc(n * sizeof(double*));
    for (int i = 0; i < n; i++) {
        U[i] = (double*)malloc(n * sizeof(double));
    }

    // Perform LU decomposition
    luDecomposition(A, n, L, U);

    // Print L and U matrices
    printf("Lower Triangular Matrix (L):\n");
    printMatrix(L, n);

    printf("\nUpper Triangular Matrix (U):\n");
    printMatrix(U, n);

    // Free allocated memory
    for (int i = 0; i < n; i++) {
        free(A[i]);
        free(L[i]);
        free(U[i]);
    }
    free(A);
    free(L);
    free(U);

    return 0;
}

Code Explanation

The C code implements the Doolittle LU decomposition algorithm. Here's a step-by-step breakdown:

1. Header Files: stdio.h is included for standard input/output operations, and stdlib.h is included for memory allocation.

2. luDecomposition Function:

- Takes the input matrix A, its size n, and pointers to the L and U matrices as arguments.

- The outer loop (i) iterates through rows/columns.

- The first inner loop (k) calculates the elements of the upper triangular matrix U.

- It calculates U[i][k] by subtracting the sum of products of elements from the L and U matrices from A[i][k].

- The second inner loop (k) calculates the elements of the lower triangular matrix L.

- If i == k (diagonal element), L[i][i] is set to 1 (Doolittle's condition).

- Otherwise, it calculates L[k][i] using a similar formula, involving the sum of products from L and U, and dividing by the corresponding element of U (U[i][i]).

3. printMatrix Function: A utility function to print a matrix to the console for verification.

4. main Function:

- Defines the size of the matrix (n = 3).

- Creates a sample matrix A and allocates memory for it and for the L and U matrices.

- Calls the luDecomposition function to perform the decomposition.

- Calls the printMatrix function to display the resulting L and U matrices.

- Frees the dynamically allocated memory to prevent memory leaks.

Complexity Analysis

Time Complexity: The Doolittle algorithm has a time complexity of O(n3), where n is the size of the matrix. This is because the algorithm involves three nested loops. The outer loop iterates `n` times, and the two inner loops also iterate up to `n` times. The dominant operation is the calculation of the sum of products within the inner loops, which takes O(n) time. Therefore, the total time complexity is approximately n * n * n = O(n3).

Space Complexity: The algorithm requires O(n2) space to store the original matrix A, the lower triangular matrix L, and the upper triangular matrix U. Each of these matrices has n2 elements. Therefore, the space complexity is O(n2).

Alternative Approaches

Crout Algorithm: The Crout algorithm is another variant of LU decomposition where the diagonal elements of the upper triangular matrix (U) are all ones. This is an alternative to Doolittle's algorithm, which sets the diagonal of the lower triangular matrix (L) to ones. The choice between Crout and Doolittle often depends on specific application requirements. Crout's algorithm also has a time complexity of O(n3) and a space complexity of O(n2), similar to Doolittle's algorithm.

Conclusion

The Doolittle algorithm provides a method to decompose a matrix into lower and upper triangular matrices, which can be used to solve linear systems of equations efficiently. This article provided a C implementation of the Doolittle algorithm, along with explanations of the code and complexity analysis. Understanding LU decomposition and its variants, such as the Doolittle algorithm, is crucial in various fields, including numerical analysis and scientific computing.