使用canvas尋路最小生成樹

xiyuyizhi發表於2017-11-12

online

source

問題引入

在學習了加權無向圖尋找最小生成樹的演算法之後,想通過視覺化的方式來表示一個圖的構造和最小生成樹的尋路過程,就使用canvas來模擬了圖的構造,連線邊和加重顯示最小生成樹組成的邊的過程

效果圖

分析

整個過程可以分為三步

  1. 通過canvas畫圓圈來表示圖的M個頂點,隨機生成頂點的座標

  2. 隨機生成N條邊,通過canvas來連線每條邊對應的兩個頂點

  3. 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)
    }複製程式碼

相關文章