html5 canvas 模拟 迪杰斯特拉算法( Dijkstra )求最短路径

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
   <style type="text/css">
        .active{
            background-color: green;
            color: black;
        }

        .no_active{
            background-color: orange;
            color: black;
            cursor: pointer;
        }
   </style>
</head>
<body id="body">
    <button id="btn_step1" disabled class="active" title="画节点模式:点击红框内任意位置就会在该位置生成一个节点!">画圆</button>
    <button id="btn_step2" class="no_active" title="画路径模式: 前后点击任意两个节点,则会在这两个节点间生成一个连线,并产生一个随机的路径距离!">画线</button>
    <button id="btn_step3" class="no_active" title="计算最短路径模式: 前后点击任意两个节点,则会计算最短路径并用红线标出最短路径!">求最短距离</button>

    <br/>
    <!-- <canvas id="myCanvas" width="200" height="100" style="border:1px solid red;"></canvas> -->
</body>
<script type="text/javascript">
    // 初始化一些常量
    var canvas_width = 500; // 画布宽度
    var canvas_height = 300; //画布高度
    var circleR=20;// 圆的半径

    // 记录一些创建的对象信息
    var circleCenterCoordinateArr = []; //记录创建的圆的圆心座标的数组
    var firstClickCircleIndex = -1;
    var secondClickCircleIndex = -1;

    var infinite = 999999;
    var defaultColor = "black";

    var step1 =true; // 点击生成节点
    var step2=false; //点击生成节点连线
    var step3=false; //点击计算两点之间最短路径

    var graph = [];// 图结构的存储,是个二位数组,graph[ i ][ j ]=10 表示 第i个节点和第j个节点的距离是10,如果这两个节点没有连线,则值为正无穷

    // 初始化  canvas  画布元素
    var dom_canvas = document.createElement( "canvas" );
    dom_canvas.setAttribute( "id","myCanvas" );
    dom_canvas.setAttribute( "width",canvas_width );
    dom_canvas.setAttribute( "height",canvas_height );
    dom_canvas.setAttribute( "style","border:1px solid red;" );
    document.getElementById( "body" ).appendChild( dom_canvas );

    // 初始化 画笔对象 ( 得到画布的画笔对象 )
    var pen = dom_canvas.getContext( "2d" );

    // 圆的角标
    var circleIndex = 0;
    // 为 画布 添加点击事件,点击一下就画一个圆,圆心就是点击的位置
    dom_canvas.onclick = function( event ){
        if( step1 ){
            handleDrawCircleEvent( event );
        }else if( step2 ){
            handleDrawLineEvent( event );
        }else if( step3 ){
            handleComputeShortestPathEvent( event );
        }
    }

    function handleDrawCircleEvent( event ){
        var x = event.clientX - dom_canvas.getBoundingClientRect().left;
        var y = event.clientY - dom_canvas.getBoundingClientRect().top;
       // console.log( "( " + x + "," + y + " )" );
        // 画一个圆形边框
        drawCircleWithText( x,y,circleR,"V" + circleIndex,20,defaultColor );

        // 将圆心座标存储起来
        circleCenterCoordinateArr[ circleIndex ] = [ x,y ];

        // 将该圆的编号存储至图结构中
        /*
            |   | V0| V1| V2| V3| V4|
            | VO| 0 | ∞ | ∞ | ∞ | ∞ |
            | V1| ∞ | 0 | ∞ | ∞ | ∞ | 
            | V2| ∞ | ∞ | 0 | ∞ | ∞ | 
            | V3| ∞ | ∞ | ∞ | 0 | ∞ | 
            | V4| ∞ | ∞ | ∞ | ∞ | 0 |
        */
        graph[ circleIndex ]=[];
        for( var i=0;i<=circleIndex;i++ ){
            if( i == circleIndex ){
                graph[ circleIndex ][ i ] = 0;
            }else{
                graph[ circleIndex ][ i ] = infinite;
                graph[ i ][ circleIndex ] = infinite;
            }
        }
        circleIndex++;
        printGraph();
    }


    function printGraph(){
        var titleRow = "|   |";
        for( var i=0;i<graph.length;i++ ){
            titleRow+=( " V" + i + "|" );
        }
        console.log( titleRow );
        for( var i=0;i<graph.length;i++ ){
            var row = "| V" + i + "|";
            for( var j=0;j<graph[ i ].length;j++ ){
                if( graph[ i ][ j ] == infinite ){
                    row +=( " ∞ |" );
                }else{
                    row +=( " " + graph[ i ][ j ] + " |" );
                }
            }
            console.log( row );
        }
        console.log( "" );
    }

    function handleDrawLineEvent( event ){
        // 判断当前点击的点在哪一个 圆的内部
        var x = event.clientX - dom_canvas.getBoundingClientRect().left;
        var y = event.clientY - dom_canvas.getBoundingClientRect().top;
        var  clickedCircleIndex = -1;
        for( var i=0;i<circleCenterCoordinateArr.length;i++ ){
            var x_circle = circleCenterCoordinateArr[ i ][ 0 ];
            var y_circle = circleCenterCoordinateArr[ i ][ 1 ];
            if( checkInCircle( x,y,x_circle,y_circle,circleR ) ){
                clickedCircleIndex = i;
                break;
            }
        }
        if( clickedCircleIndex == -1 && firstClickCircleIndex == -1 ){
            alert( "请选择连线开始节点!" );
            return;
        }

        if( clickedCircleIndex == -1 && secondClickCircleIndex == -1 ){
            alert( "请选择连线结束节点!" );
            return;
        }

        if( firstClickCircleIndex == -1 ){
            firstClickCircleIndex = clickedCircleIndex;
            console.log("点击的开始节点是  V" + firstClickCircleIndex );
            return;
        }
        if( secondClickCircleIndex == -1 ){
            secondClickCircleIndex = clickedCircleIndex;
            console.log( "点击的结束节点是  V" + secondClickCircleIndex );
            console.log( "" );

            // 画2个圆之间的连线
            var randomDistance = Math.ceil( Math.random() * 10 );
            drawConnectionTwoCircle( firstClickCircleIndex,secondClickCircleIndex,defaultColor,randomDistance );

            // 更新图结构
            graph[ firstClickCircleIndex ][ secondClickCircleIndex ] = randomDistance;
            graph[ secondClickCircleIndex ][ firstClickCircleIndex ] = randomDistance;
            printGraph();

            firstClickCircleIndex = -1;
            secondClickCircleIndex = -1;
            return;
        }
    }

    // 画2个圆之间的连线
    function drawConnectionTwoCircle( circleIndex1,circleIndex2,color,distance ){
        // circleIndex1 和 circleIndex2 都有了,开始连线
        var x1 = circleCenterCoordinateArr[ circleIndex1 ][ 0 ];
        var y1 = circleCenterCoordinateArr[ circleIndex1 ][ 1 ];
        var x2 = circleCenterCoordinateArr[ circleIndex2 ][ 0 ];
        var y2 = circleCenterCoordinateArr[ circleIndex2 ][ 1 ];

        var l=Math.sqrt( ( x1 - x2 ) * ( x1 - x2 ) + ( y1 - y2 ) * ( y1 - y2 ) );
        var x_from = ( x2 - x1 ) * circleR / l + x1;
        var y_from = ( y2 - y1 ) * circleR / l + y1;

        var x_to = x2 - ( x2 - x1 ) * circleR / l;
        var y_to = y2 - ( y2 - y1 ) * circleR / l;

        drawLine( x_from,y_from,x_to,y_to,color );
        // 为此连线生成一个随机的距离,并写到 此连线中点的位置
        var x_middle = ( x1 + x2 )/2;
        var y_middle = ( y1 + y2 )/2;
        
        if( !distance ){
            distance = Math.ceil( Math.random() * 10 );
        }
        if( color ){
            pen.fillStyle = color;
        }else{
            pen.fillStyle = defaultColor;
        }
        pen.fillText( distance,x_middle,y_middle );
    }
    
    // 画一个圆形边框,并在圆心写文字
    function drawCircleWithText( x,y,r,text,fontSize,color ){
        if( color ){
            pen.strokeStyle = color;
            pen.fillStyle = color;
        }else{
            pen.strokeStyle = defaultColor;
            pen.fillStyle = defaultColor;
        }
        pen.beginPath();
        pen.arc(x,y,r,0,2*Math.PI);
        pen.stroke();
        pen.closePath();
        pen.font = fontSize + "px Microsoft JhengHei";
        pen.fillText( text,x - fontSize/2,y + fontSize/2 );
    }

    // 画一根线
    function drawLine( x1,y1,x2,y2,color ){
        if( color ){
            pen.strokeStyle = color;
        }else{
            pen.strokeStyle = defaultColor;
        }
        pen.beginPath();
        pen.moveTo( x1,y1 );
        pen.lineTo( x2,y2 );
        pen.stroke();
        pen.closePath();
    }

    document.getElementById( "btn_step1" ).onclick=function(){
        step1=true;
        step2=false;
        step3=false;
        document.getElementById( "btn_step1" ).setAttribute( "disabled","disabled" );
        document.getElementById( "btn_step1" ).setAttribute( "class","active" )

        document.getElementById( "btn_step2" ).removeAttribute( "disabled" );
        document.getElementById( "btn_step2" ).setAttribute( "class","no_active" );

        document.getElementById( "btn_step3" ).removeAttribute( "disabled" );
        document.getElementById( "btn_step3" ).setAttribute( "class","no_active" );
    }

    document.getElementById( "btn_step2" ).onclick=function(){
        step2=true;
        step1=false;
        step3=false;
        document.getElementById( "btn_step2" ).setAttribute( "disabled","disabled" );
        document.getElementById( "btn_step2" ).setAttribute( "class","active" )

        document.getElementById( "btn_step1" ).removeAttribute( "disabled" );
        document.getElementById( "btn_step1" ).setAttribute( "class","no_active" );

        document.getElementById( "btn_step3" ).removeAttribute( "disabled" );
        document.getElementById( "btn_step3" ).setAttribute( "class","no_active" );
    }

    document.getElementById( "btn_step3" ).onclick=function(){
        step3=true;
        step1=false;
        step2=false;
        document.getElementById( "btn_step3" ).setAttribute( "disabled","disabled" );
        document.getElementById( "btn_step3" ).setAttribute( "class","active" )

        document.getElementById( "btn_step1" ).removeAttribute( "disabled" );
        document.getElementById( "btn_step1" ).setAttribute( "class","no_active" );

        document.getElementById( "btn_step2" ).removeAttribute( "disabled" );
        document.getElementById( "btn_step2" ).setAttribute( "class","no_active" );
    }

    // 检测 点 x,y 是否在 圆内部( 圆心座标为x_circle,y_circle,圆半径为 r  )
    function checkInCircle( x,y,x_circle,y_circle,r ){
        //求点到圆心的距离,用到了勾股定理
        var dis = Math.sqrt( ( x - x_circle ) * ( x - x_circle ) + ( y - y_circle ) * ( y - y_circle ) );//Math.sqrt()求平方跟
        if(dis <= r){
            return true;
        }
        return false;
    }


    var startCircleIndex = -1;
    var endCircleIndex = -1;
    function handleComputeShortestPathEvent( event ){
        // 判断当前点击的点在哪一个 圆的内部
        var x = event.clientX - dom_canvas.getBoundingClientRect().left;
        var y = event.clientY - dom_canvas.getBoundingClientRect().top;
        var  clickedCircleIndex = -1;
        for( var i=0;i<circleCenterCoordinateArr.length;i++ ){
            var x_circle = circleCenterCoordinateArr[ i ][ 0 ];
            var y_circle = circleCenterCoordinateArr[ i ][ 1 ];
            if( checkInCircle( x,y,x_circle,y_circle,circleR ) ){
                clickedCircleIndex = i;
                break;
            }
        }
        if( clickedCircleIndex == -1 && startCircleIndex == -1 ){
            alert( "请选择出发节点!" );
            return;
        }

        if( clickedCircleIndex == -1 && endCircleIndex == -1 ){
            alert( "请选择到达节点!" );
            return;
        }

        if( startCircleIndex == -1 ){
            startCircleIndex = clickedCircleIndex;
            console.log("选择的出发节点是  V" + startCircleIndex );
            return;
        }
        if( endCircleIndex == -1 ){
            endCircleIndex = clickedCircleIndex;
            console.log( "选择的到达节点是  V" + endCircleIndex );
            console.log( "" );

            // startCircleIndex 和 endCircleIndex 都有了,开始计算最短路径
    /*
                |   | V0| V1| V2| V3| V4| V5|
                | V0| 0 | 8 | 6 | ∞ | ∞ | 10 |
                | V1| 8 | 0 | ∞ | 1 | ∞ | 3  |
                | V2| 6 | ∞ | 0 | ∞ | 3 | 5  |
                | V3| ∞ | 1 | ∞ | 0 | 1 | 10 |
                | V4| ∞ | ∞ | 3 | 1 | 0 | 2  |
                | V5| 10 | 3 | 5 | 10 | 2 | 0 |
*/
            //todo  最难的部分来了
            // 解析 graph  二维数组
            // 例如: 计算 v0到 v4的最短路径
            // 分别 计算 v0 到 v1 v2 v3 v4 v5的最短路径
            var distanceObjArr= [];
            var uncheckedMinIndex=-1;
            var minDistance=infinite;
            for( var i = 0; i < graph.length; i++ ){
                if( i == startCircleIndex ){
                    continue;
                }
                var currDistance= graph[ startCircleIndex ][ i ];
                if( currDistance < minDistance ){
                    minDistance = currDistance;
                    uncheckedMinIndex = i;
                }
                distanceObjArr[ i ] = {
                                        "checked":false,
                                        "path": startCircleIndex + "," + i + ",",
                                        "distance":currDistance
                                     }
            }
            printDistanceObjArr( distanceObjArr );

            while( true ){
                // if( distanceObjArr[ endCircleIndex ].checked ){
                if( uncheckedMinIndex== -1 ){
                    break;
                }
                // V0 到  uncheckedMinIndex 节点的最短距离是  distanceObjArr[ uncheckedMinIndex ].distance
                // 所以尝试计算 V0 途径 uncheckedMinIndex 节点会不会使得到达其他节点的距离缩短
                minDistance = infinite;
                var nextUncheckedMinIndex = -1;
                for( var i = 0; i < distanceObjArr.length; i++ ){
                    if( i == startCircleIndex ){
                        continue;
                    }
                    // 检查过就不处理了,比如当前 uncheckedMinIndex 节点是未检查状态,则此 for循环结束后则是 已检查状态,循环时就不会处理它了
                    if( distanceObjArr[ i ].checked ){
                        continue;
                    }
                    if( i == uncheckedMinIndex ){
                        continue;
                    }
                    var  tryMinDistance = distanceObjArr[ uncheckedMinIndex ].distance + graph[ uncheckedMinIndex ][ i ];
                    if( tryMinDistance < distanceObjArr[ i ].distance ){
                        distanceObjArr[ i ].distance =  tryMinDistance;
                        distanceObjArr[ i ].path = distanceObjArr[ uncheckedMinIndex ].path + i + ",";
                    }
                    if( distanceObjArr[ i ].distance < minDistance ){
                        minDistance=distanceObjArr[ i ].distance;
                        nextUncheckedMinIndex = i;
                    }
                }
                distanceObjArr[ uncheckedMinIndex ].checked=true;
                uncheckedMinIndex=nextUncheckedMinIndex;
                printDistanceObjArr( distanceObjArr );
            }
            // V4 到 v6 的最短距离是11,路线是0,4,5,6,
            console.log( "V" + startCircleIndex + " 到 v" + endCircleIndex + " 的最短距离是"
                         + distanceObjArr[ endCircleIndex ].distance 
                         + ",路线是"
                         + distanceObjArr[ endCircleIndex ].path );
            // 最短路径标注红色
            var trailIndexArr = distanceObjArr[ endCircleIndex ].path.split( "," );
            var prevTrailIndex = -1;
            var currTrailIndex = -1;
            for( var i=0;i<trailIndexArr.length;i++ ){
                var trailIndex = trailIndexArr[ i ];
                if( !trailIndex || trailIndex.length == 0 ){
                    continue;
                }
                trailIndex = parseInt( trailIndex );
                if( prevTrailIndex == -1 ){
                    prevTrailIndex = trailIndex;
                    continue;
                }
                currTrailIndex = trailIndex;
                // 画连线
                drawConnectionTwoCircle( prevTrailIndex,currTrailIndex,"red",graph[ prevTrailIndex ][ currTrailIndex ] );
                prevTrailIndex = currTrailIndex;
                currTrailIndex = -1;
            }
        }
        startCircleIndex = -1;
        endCircleIndex = -1;
    }

    function printDistanceObjArr( distanceObjArr ){
        for( var i = 0;i<distanceObjArr.length;i++ ){
            if( distanceObjArr[ i ] ){
                console.log( "checked:" + distanceObjArr[ i ].checked + ", path:" +  distanceObjArr[ i ].path + ", distance:" +  distanceObjArr[ i ].distance );
            }
        }
        console.log( "" );
    }
</script>
</html>

​

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章