通过编程来学习线性代数1-解二元线性方程组

cover

环境

采用的编程方式是网页,会使用javascript来实现线性代数中的计算方法。
比如文件linearAlgebra.html:


写入上面的代码,保存后用浏览器打开,然后右键打开审查元素点击控制台(Console)来查看输出。

更多网页相关知识网上可以搜得到,掌握基本javascript编程知识就行了。

解二元线性方程组

行列式的概念是由解多元线性方程组而引出的。比如下面这个:

二元线性方程组

在坐标系中就是两根直线,分母为零的情况就是两根直线平行不相交。

1. 绘制坐标系

下面是我用canvas绘制的坐标系,一般编程中涉及到坐标系的地方,跟数学里有些不同,y轴方向是向下为正。

canvas坐标系
canvas坐标系

那么来看看如何绘制出坐标系。下面主要是编程方面的东西,可以自己创建一个.html文件试一试。

首先,创建canvas


    
        
    



画布创建好后,接下来着手绘制直角坐标系的两根辅助线。

创建一个函数方便之后的重复调用。


// 因为原点是从左上角开始的,为了方便看直线,将原点偏移到指定位置
// 每一个设置坐标的地方都要(x + originX, y + originY)
var originX = W / 2.0, originY = H / 2.0

// 调用函数,传入之前创建好的画布ctx
drawCoordinateSystem(ctx)

function drawCoordinateSystem(ctx) {
    // 设置绘制线的颜色为black
    ctx.strokeStyle = 'black'

    // 线宽度
    ctx.lineWidth = 1

    // 水平线
    ctx.beginPath()
    ctx.moveTo(0, 0 + originY)  // 画布的左边界中点
    ctx.lineTo(W, 0 + originY)    // 画布的右边界中点
    ctx.closePath()
    ctx.stroke()

    // 垂直线
    ctx.beginPath()
    ctx.moveTo(0 + originX, 0)  // 画布的上边界中点
    ctx.lineTo(0 + originX, H)  // 画布的下边界中点
    ctx.closePath()
    ctx.stroke()
}

moveTo是设置一个起点,lineTo是将线从上一个点连接到该点。

stroke绘制线条,如果fill,那么会将线包围的区域用颜色涂满。途中的黑色三角形方向标就是这样绘制的,设置三个点后调用fill

调用函数后得到下图:

canvas坐标系

阴影样式需要给canvas加上一个box-shadow

2. 绘制坐标系刻度和方向标

同样,分别创建函数来专门绘制刻度和方向标:

// 刻度长度
var scaleD= 10.0

// 绘制刻度
// 传入context和刻度长度
function drawScale(ctx, d) {
    // horizontal
    for (var x = 0; x < W; x += 50) {
        ctx.beginPath()
        ctx.moveTo(x, 0 + originY)
        ctx.lineTo(x, scaleD + originY)
        ctx.stroke()
        ctx.closePath()
    }

    // vertical
    for (var y = 0; y < H; y += 50) {
        ctx.beginPath()
        ctx.moveTo(0 + originX, y)
        ctx.lineTo(scaleD + originX, y)
        ctx.stroke()
        ctx.closePath()
    }
}

//绘制方向标(三角形),底边长度与底边到顶点长度一样的三角形
function drawDirectionArrow(ctx, d) {
    // horizontal
    ctx.beginPath()
    ctx.moveTo(W - d * 2, originY - d)
    ctx.lineTo(W - d * 2, originY + d)
    ctx.lineTo(W, originY)
    ctx.closePath()
    ctx.fill()

    // vertical
    ctx.beginPath()
    ctx.moveTo(originX - d, H - d * 2)
    ctx.lineTo(originX + d, H - d * 2)
    ctx.lineTo(originX, H)
    ctx.closePath()
    ctx.fill()
}

然后在drawCoordinateSystem函数里追加调用drawScale(ctx, scaleD)drawDirectionArrow(ctx, scaleD),运行后如图:

canvas坐标系

如果想要在刻度上绘制数字标记,可以自行搜索相关文档,有一个叫fillText的函数。

3. 绘制直线

a11 * x1 + a12 * x2 = b1 此方程相当于 a * x + b * y = c

我们可以根据直线方程找到两个点,将两个点通过moveTolineTo连接并绘制出来。

//---绘制直线---
// 传入直线方程的三个常数和直线颜色
/* 
    a * x + b * y = c,

    在 x, y 轴上的点
    x = 0 && b != 0, y = c / b => (0, c / b)
    y = 0 && a != 0, x = c / a => (c / a, 0)

    b != 0, y = c / b => (x, (c - a * x) / b)
    a != 0, x = c / a => ((c - b * y) / a, y)
*/
function drawLine(ctx, a, b, c, color = 'red') {
    // 如果有一个为零,那么直线就是平行于x或y轴的
    if (b != 0 && a != 0) {
        var x1 = c / a, y1 = 0  // x轴上的点
        var x2 = 0, y2 = c / b  // y轴上的点
        var x3 = -originX, y3 = (c - a * x3) / b    //左边界点
        var x4 = originX, y4 = (c - a * x4) / b    //右边界点

        ctx.strokeStyle = color
        ctx.lineWidth = 1

        //  绘制直线
        ctx.beginPath()
        ctx.moveTo(x3 + originX, y3 + originY)
        ctx.lineTo(x4 + originX, y4 + originY)
        ctx.closePath()
        ctx.stroke()
    }
}

然后调用该函数绘制:

drawLine(ctx, 1, 1, 123, 'red')
drawLine(ctx, 1, 5, 999, 'blue')

结果:

canvas坐标系
4. 求出两直线的交点

说了这么多,现在才开始解方程?

大多是基础的绘制工作。数学和编程相结合的地方就是如何根据直线方程绘制直线。

对于方程:

╭ a11*x1 + a12*x2 = b1
╰ a21*x1 + a22*x2 = b2

a11*a22 - a12*a21 != 0时,方程有唯一解:

x1 = (b1*a22 - a12*b2)/(a11*a22 - a12*a21)

x2 = (a11*b2 - b1*a21)/(a11*a22 - a12*a21)
    |a11 a12|
D = |       | = a11*a22 - a12*a21 //分母
    |a21 a22|

     |b1 a12|
D1 = |      | = b1*a22 - a1*b2 //x1分子
     |b2 a22|

     |a11 b1|
D2 = |      | = a11*b2 - b1*a21 //x2分子
     |a21 b2|

x1 = D1/D
x2 = D2/D

那么我们就可以根据这个来得出两直线相交的点(x1, x2)

下面一个函数是根据两直线的常数计算出交点。第二个函数是在以(x, y)为圆心,半径为10,绘制一个圆

/*
    a11 > a1
    a21 > b1
    b1 > c1
    ...
    计算两直线交点
*/
function calculateIntersection(a11, a12, b1, a21, a22, b2) {
    x1 = (b1 * a22 - a12 * b2) / (a11 * a22 - a12 * a21)
    x2 = (a11 * b2 - b1 * a21) / (a11 * a22 - a12 * a21)

    return {
        x: x1,
        y: x2
    }
}

// 绘制交点
function drawIntersection(ctx, x, y) {
    ctx.arc(x+originX, y+originY, 10, 0, 2*Math.PI)
    ctx.fill()
}

最后,我们的绘制函数大概是这样的:


drawCoordinateSystem(ctx)
drawLine(ctx, 1, 1, 123, 'red')
drawLine(ctx, 1, 5, 999, 'blue')

var p = calculateIntersection(1, 1, 123, 1, 5, 999)
drawIntersection(ctx, p.x, p.y)

结果如图:

canvas坐标系

总结

上面求两直线交点的思路是根据二阶行列式来解二元线性方程组。可以看到,行列式是根据解多元线性方程组总结出来的。二阶或三阶行列式我们可以比较轻松的计算出结果,但是随着阶数增加,计算量也会越来越大,n阶乘n*n(n-1)*...*3*2*1

我们来看看求二阶行列式三阶行列式的值的计算:

// 计算二阶行列式的值
/*
|a11 a12|
|a21 a22|
*/
function calculate2Det(a11, a12, a21, a22) {
    // 2!
    return a11*a22 - a12*a21
}

// 计算三阶行列式的值
/*
|a11 a12 a13|
|a21 a22 a23|
|a31 a32 a33|
*/
function calculate3Det(a11, a12, a13, a21, a22, a23, a31, a32, a33) {
    // 3!
    return a11*a22*a33 + a12*a23*a31 + a13*a21*a32 - a13*a22*a31 - a12*a21*a33 - a11*a23*a32
}

如果是四阶行列式,那么我们要写4!=24个。所以我们要想办法简化计算,使用一种通用的方式来计算行列式的值,而不是一个一个全部写出来计算。

那么编程上如何简化这个计算呢?

下一节将会来解决这个问题。

代码

  • tomfriwel/linearAlgebraPro

你可能感兴趣的:(通过编程来学习线性代数1-解二元线性方程组)