JavaScript 分而治之(施特拉森矩阵乘法)

给定两个大小分别为 nxn 的方阵 A 和 B,求它们的乘法矩阵。 
朴素方法:以下是两个矩阵相乘的简单方法。

function multiply(A, B, C)
{
    for (var i = 0; i < N; i++)
    {
        for (var j = 0; j < N; j++)
        {
            C[i][j] = 0;
            for (var k = 0; k < N; k++)
            {
                C[i][j] += A[i][k]*B[k][j];
            }
        }
    }

上述方法的时间复杂度为O(N 3 )。 

分而治之 :
以下是两个方阵相乘的简单分而治之方法。 
1、将矩阵 A 和 B 分为 4 个大小为 N/2 x N/2 的子矩阵,如下图所示。 
2、递归计算以下值。 ae + bg、af + bh、ce + dg 和 cf + dh。 

执行:

function multiplyMatrix(matrixA, matrixB) {
  const n = matrixA.length;
  const resultMatrix = new Array(n).fill(null).map(() => new Array(n).fill(0));
 
  if (n === 1) {
    resultMatrix[0][0] = matrixA[0][0] * matrixB[0][0];
    return resultMatrix;
  }
 
  const splitIndex = n / 2;
 
  const a00 = new Array(splitIndex).fill(null).map(() => new Array(splitIndex).fill(0));
  const a01 = new Array(splitIndex).fill(null).map(() => new Array(splitIndex).fill(0));
  const a10 = new Array(splitIndex).fill(null).map(() => new Array(splitIndex).fill(0));
  const a11 = new Array(splitIndex).fill(null).map(() => new Array(splitIndex).fill(0));
 
  const b00 = new Array(splitIndex).fill(null).map(() => new Array(splitIndex).fill(0));
  const b01 = new Array(splitIndex).fill(null).map(() => new Array(splitIndex).fill(0));
  const b10 = new Array(splitIndex).fill(null).map(() => new Array(splitIndex).fill(0));
  const b11 = new Array(splitIndex).fill(null).map(() => new Array(splitIndex).fill(0));
 
  for (let i = 0; i < splitIndex; i++) {
    for (let j = 0; j < splitIndex; j++) {
      a00[i][j] = matrixA[i][j];
      a01[i][j] = matrixA[i][j + splitIndex];
      a10[i][j] = matrixA[splitIndex + i][j];
      a11[i][j] = matrixA[i + splitIndex][j + splitIndex];
 
      b00[i][j] = matrixB[i][j];
      b01[i][j] = matrixB[i][j + splitIndex];
      b10[i][j] = matrixB[splitIndex + i][j];
      b11[i][j] = matrixB[i + splitIndex][j + splitIndex];
    }
  }
 
  const resultMatrix00 = addMatrix(multiplyMatrix(a00, b00), multiplyMatrix(a01, b10), splitIndex);
  const resultMatrix01 = addMatrix(multiplyMatrix(a00, b01), multiplyMatrix(a01, b11), splitIndex);
  const resultMatrix10 = addMatrix(multiplyMatrix(a10, b00), multiplyMatrix(a11, b10), splitIndex);
  const resultMatrix11 = addMatrix(multiplyMatrix(a10, b01), multiplyMatrix(a11, b11), splitIndex);
 
  for (let i = 0; i < splitIndex; i++) {
    for (let j = 0; j < splitIndex; j++) {
      resultMatrix[i][j] = resultMatrix00[i][j];
      resultMatrix[i][j + splitIndex] = resultMatrix01[i][j];
      resultMatrix[splitIndex + i][j] = resultMatrix10[i][j];
      resultMatrix[i + splitIndex][j + splitIndex] = resultMatrix11[i][j];
    }
  }
 
  return resultMatrix;
}
 
function addMatrix(matrixA, matrixB, n) {
  const resultMatrix = new Array(n).fill(null).map(() => new Array(n).fill(0));
 
  for (let i = 0; i < n; i++) {
    for (let j = 0; j < n; j++) {
      resultMatrix[i][j] = matrixA[i][j] + matrixB[i][j];
    }
  }
 
  return resultMatrix;
}
const matrixA = [
  [1, 1, 1, 1],
  [2, 2, 2, 2],
  [3, 3, 3, 3],
  [2, 2, 2, 2]
];
console.log("Array A =>")
console.log(matrixA);
const matrixB = [
  [1, 1, 1, 1],
  [2, 2, 2, 2],
  [3, 3, 3, 3],
  [2, 2, 2, 2]
];
console.log("Array B =>")
console.log(matrixB);
const resultMatrix = multiplyMatrix(matrixA, matrixB);
 
console.log("Result Array =>")
console.log(resultMatrix);

输出
数组A =>
         1 1 1 1
         2 2 2 2
         3 3 3 3
         2 2 2 2


数组 B =>
         1 1 1 1
         2 2 2 2
         3 3 3 3
         2 2 2 2


结果数组=>
         8 8 8 8
        16 16 16 16
        24 24 24 24
        16 16 16 16

在上述方法中,我们对大小为 N/2 x N/2 的矩阵进行 8 次乘法和 4 次加法。两个矩阵相加需要 O(N 2 ) 时间。所以时间复杂度可以写成 

T(N) = 8T(N/2) + O(N 2 )  

根据马斯特定理,上述方法的时间复杂度为 O(N 3 )
不幸的是,这与上面的简单方法相同。

简单的分而治之也导致O(N 3 ),有更好的方法吗? 

        在上面的分而治之的方法中,高时间复杂度的主要成分是8次递归调用。Strassen 方法的思想是将递归调用次数减少到 7 次。Strassen 方法与上述简单的分而治之方法类似,该方法也将矩阵划分为大小为 N/2 x N/2 的子矩阵:如上图所示,但在Strassen方法中,结果的四个子矩阵是使用以下公式计算的。

Strassen 方法的时间复杂度

两个矩阵的加法和减法需要 O(N 2 ) 时间。所以时间复杂度可以写成 

T(N) = 7T(N/2) + O(N 2 )

根据马斯特定理,上述方法的时间复杂度为
O(N Log7 ) 大约为 O(N 2.8074 )

一般来说,由于以下原因,施特拉森方法在实际应用中并不优选。 

1、Strassen 方法中使用的常数很高,对于典型应用,Naive 方法效果更好。 
2、对于稀疏矩阵,有专门为其设计的更好的方法。 
3、递归中的子矩阵占用额外的空间。 
4、由于计算机对非整数值的运算精度有限,Strassen 算法中累积的误差比 Naive 方法中更大。

执行:  

function split(matrix) {
 
    /*
        Splits a given matrix into quarters.
    Input: nxn matrix
    Output: tuple containing 4 n/2 x n/2 matrices corresponding to a, b, c, d
*/
    let row = matrix.length,
        col = matrix[0].length;
    let row2 = Math.floor(row / 2),
        col2 = Math.floor(col / 2);
    return [matrix.slice(0, row2).map(x => x.slice(0, col2)),
        matrix.slice(0, row2).map(x => x.slice(col2)),
        matrix.slice(row2).map(x => x.slice(0, col2)),
        matrix.slice(row2).map(x => x.slice(col2))
    ];
}
 
function strassen(x, y) {
 
    /*
    Computes matrix product by divide and conquer approach, recursively.
    Input: nxn matrices x and y
    Output: nxn matrix, product of x and y
    */
    // Base case when size of matrices is 1x1
    if (x.length === 1) {
        return [
            [x[0][0] * y[0][0]]
        ];
    }
 
 
    // Splitting the matrices into quadrants. This will be done recursively
    // until the base case is reached.
    let [a, b, c, d] = split(x);
    let [e, f, g, h] = split(y);
 
    // Computing the 7 products, recursively (p1, p2...p7)
    let p1 = strassen(a, sub(f, h));
    let p2 = strassen(add(a, b), h);
    let p3 = strassen(add(c, d), e);
    let p4 = strassen(d, sub(g, e));
    let p5 = strassen(add(a, d), add(e, h));
    let p6 = strassen(sub(b, d), add(g, h));
    let p7 = strassen(sub(a, c), add(e, f));
 
    // Computing the values of the 4 quadrants of the final matrix c
    let c11 = add(sub(add(p5, p4), p2), p6);
    let c12 = add(p1, p2);
    let c21 = add(p3, p4);
    let c22 = sub(sub(add(p1, p5), p3), p7);
 
    // Combining the 4 quadrants into a single matrix by stacking horizontally and vertically.
    let top = c11.map((x, i) => x.concat(c12[i]));
    let bottom = c21.map((x, i) => x.concat(c22[i]));
    return top.concat(bottom);
}
 
function add(a, b) {
    return a.map((x, i) => x.map((y, j) => y + b[i][j]));
}
 
function sub(a, b) {
    return a.map((x, i) => x.map((y, j) => y - b[i][j]));
}
 
// Driver's code
let A = [
    [1, 1, 1, 1],
    [2, 2, 2, 2],
    [3, 3, 3, 3],
    [2, 2, 2, 2]
];
 
let B = [
    [1, 1, 1, 1],
    [2, 2, 2, 2],
    [3, 3, 3, 3],
    [2, 2, 2, 2]
];
 
let C = strassen(A, B);
console.log("Array A =>")
console.table(A);
console.log("Array B =>")
console.table(B);
console.log("Result Array =>")
console.table(C);
// This code is contributed by Prajwal Kandekar 

输出
数组A =>
         1 1 1 1
         2 2 2 2
         3 3 3 3
         2 2 2 2


数组 B =>
         1 1 1 1
         2 2 2 2
         3 3 3 3
         2 2 2 2


结果数组=>
         8 8 8 8
        16 16 16 16
        24 24 24 24
        16 16 16 16 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

csdn_aspnet

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值