环境
采用的编程方式是网页,会使用javascript
来实现线性代数中的计算方法。
比如文件linearAlgebra.html:
写入上面的代码,保存后用浏览器打开,然后右键打开审查元素点击控制台(Console)来查看输出。
更多网页相关知识网上可以搜得到,掌握基本
javascript
编程知识就行了。
解二元线性方程组
行列式
的概念是由解多元线性方程组而引出的。比如下面这个:
在坐标系中就是两根直线,分母为零的情况就是两根直线平行不相交。
1. 绘制坐标系
下面是我用canvas
绘制的坐标系,一般编程中涉及到坐标系的地方,跟数学里有些不同,y轴方向是向下为正。
那么来看看如何绘制出坐标系。下面主要是编程方面的东西,可以自己创建一个.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
加上一个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)
,运行后如图:
如果想要在刻度上绘制数字标记,可以自行搜索相关文档,有一个叫fillText的函数。
3. 绘制直线
a11 * x1 + a12 * x2 = b1
此方程相当于 a * x + b * y = c
。
我们可以根据直线方程找到两个点,将两个点通过moveTo
和lineTo
连接并绘制出来。
//---绘制直线---
// 传入直线方程的三个常数和直线颜色
/*
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')
结果:
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)
结果如图:
总结
上面求两直线交点的思路是根据二阶行列式
来解二元线性方程组
。可以看到,行列式
是根据解多元线性方程组
总结出来的。二阶或三阶行列式我们可以比较轻松的计算出结果,但是随着阶数增加,计算量也会越来越大,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