問題引入
在學習了加權無向圖尋找最小生成樹的演算法之後,想通過視覺化的方式來表示一個圖的構造和最小生成樹的尋路過程,就使用canvas來模擬了圖的構造,連線邊和加重顯示最小生成樹組成的邊的過程
效果圖
分析
整個過程可以分為三步
通過canvas畫圓圈來表示圖的M個頂點,隨機生成頂點的座標
隨機生成N條邊,通過canvas來連線每條邊對應的兩個頂點
prim演算法尋找最小生成樹,canvas加粗顯示最小生成樹中的邊
實現
主要用到的canvas方法有arc()
,moveTo()
,lineTo()
,fillText()
以及修改樣式和顏色
- 繪製頂點的位置
將canvas畫布表示成一個(M+1)*(M+1)的二維陣列,對於M個頂點,為每個頂點隨機生成不大於M的兩個二維陣列的索引
頂點的表示
_random() {
return {
x: random(vertex) + 1,
y: random(vertex) + 1
}
}複製程式碼
需要注意的是,因為是隨機生成的,兩個頂點在陣列中的位置可能重複,所以遇到重複的情況,應該為頂點重新生成一個位置
大體過程
geneVertexAxis() {
let i = 0
const vertexList = []
while (vertexList.length < vertex) {
const p = this._random()
if (this.axis[p.x][p.y] == -1) {
this.axis[p.x][p.y] = i
this.vertexPoints[i] = p
vertexList.push(i)
i++
}
}
}複製程式碼
- 繪製邊
同樣通過random()來隨機生成兩個需要連線的頂點(不大於M),併為這條邊加一個權重,同時不能生成重複邊, v-w 和 w-v視為同一條邊
geneEdge() {
for (let i = 0; i < edge; i++) {
let v1 = random(vertex)
let v2 = random(vertex)
if (v1 !== v2) {
let e = `${v1}-${v2}-${random(maxWeight)}`
let eReverse = `${v2}-${v1}`
if (!this._repeat(`${v1}-${v2}`) && !this._repeat(eReverse)) {
this.edgeList.push(e)
}
}
}
}複製程式碼
重要的是:
使用canvas來繪製圓,繪製直線都很簡單,但這裡,為了美觀,在將兩個頂點連線(即在兩個圓之間畫一條直線)時,對於直線,不能始於一個圓的圓心,止於另一個圓的圓心,而應該取兩個圓與直線的焦點作為直線的兩個端點
要做到這一點,需要藉助數學中的勾股定理和等比三角形(O(∩_∩)O) 來求出焦點相對於圓心的橫縱方向的偏移量
/**
* 計算過兩圓心的直線與圓的交點
* @param {object} p1 圓心1位置
* @param {object} p2 圓心2位置
* @param {number} R 半徑
*/
export function calculateInters(p1, p2, R) {
const xAixsDis = Math.abs(p1.x - p2.x)
const yAixsDis = Math.abs(p1.y - p2.y)
const disOfTwoPoint = Math.sqrt(xAixsDis * xAixsDis + yAixsDis * yAixsDis).toFixed(2)
const x = (R / disOfTwoPoint * xAixsDis).toFixed(2)
const y = (R / disOfTwoPoint * yAixsDis).toFixed(2)
let p1X = x
let p1Y = y
let p2X = x
let p2Y = y
if (p1.x <= p2.x && p1.y > p2.y) {
p1Y = -p1Y
p2X = -p2X
}
if (p1.x <= p2.x && p1.y <= p2.y) {
p2X = -p2X
p2Y = -p2Y
}
if (p1.x > p2.x && p1.y > p2.y) {
p1X = -p1X
p1Y = -p1Y
}
if (p1.x > p2.x && p1.y <= p2.y) {
p1X = -p1X
p2Y = -p2Y
}
return {
p1Inters: {
x: p1.x + Number(p1X),
y: p1.y + Number(p1Y)
},
p2Inters: {
x: p2.x + Number(p2X),
y: p2.y + Number(p2Y)
}
}複製程式碼
- 加重最小生成樹的邊
最後使用prim演算法求出最小生成樹後,我們可以得到一組最小生成樹中的頂點列表,然後將列表中的頂點對延時加粗繪製出來
_drawMinLine(edge, edges, i, vertexPoints) {
const { ctx } = this
ctx.lineWidth = 3
setTimeout(() => {
ctx.beginPath();
let p1 = edge.v
let p2 = edge.w
this.showMinEdgesWeightInfo(edges, i)
this._drawLine(vertexPoints[p1], vertexPoints[p2])
ctx.stroke();
}, (i + 1) * 1000)
}複製程式碼