您的位置:首页 > Web前端 > JQuery

js 迷宫最短路,使用uikit和jQuery dijkstra

2017-09-24 00:00 295 查看
uikit美化 + 栈寻找所有路径

dijkstra优化,但是由于路径数目增加太快,所以还是有限制

增加了一个判断路径数目是否过多的函数,如果路径数目过多则不进行查找



<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/uikit.min.css"/>
<script src="js/jquery.js"></script>
<script src="js/uikit.min.js"></script>
<script src="js/uikit-icons.min.js"></script>

<meta name="viewport" content="width=device-width, initial-scale=1">

<meta charset="UTF-8">
<title>迷宫寻路--阿豪</title>
<script type="text/javascript">

//全局颜色
colors = ['black',   //0障碍物颜色,不能走
'lightgray',    //1,普通路颜色
'yellow',      //2,端点颜色
'deepskyblue',         //3,路径颜色
]

rows = 10   //行数
cols = 10   //列数
cell_width = 20   //格子宽度
cell_height = 20//格子高度
g = getMat(rows, cols, 1)
cur_num = 0
all_num = 0
paths = new Array()
ps = new Array()      //保存两个端点
dir = [[-1, 0], [0, 1], [1, 0], [0, -1]] //方向,上右下左

//添加字符串格式化函数,方便使用
String.prototype.format = function () {
var args = arguments;
return this.replace(/\{(\d+)\}/g,
function (m, i) {
return args[i];
});
}

//创建矩阵的辅助函数
function getMat(x, y, val) {
var arr = new Array()
for (var i = 0; i < x; i++) {
arr.push(new Array())
for (var j = 0; j < y; j++)
arr[i].push(val)
}
return arr
}

//显示矩阵信息
function showMat(g, rows, cols) {
for (var i = 0; i < rows; i++) {
for (var j = 0; j < cols; j++)
console.log(g[i][j])
}
}

//重新绘制r,c图像,重置端点数组,矩阵数组
function reset(r, c) {
$("#maze").remove()
$('#cur_num').text('0')
$('#all_num').text('0')

x = parseInt(r)
y = parseInt(c)
//先拼接字符串,最后全部加入
str = "<div id='maze' style='margin-top: 5px'>"

var btn_str = "<button class='uk-button uk-button-primary uk-button-small elem' style='height: 30px;width: 30px;margin-bottom: 1px;margin-right: 1px;background-color: lightgray' posx='{0}' posy='{1}'></button>"
for (var i = 0; i < x; i++) {
for (var j = 0; j < y; j++) {
str += (btn_str.format(i, j))
}
str += "<br>"
}
str += "</div>"
$("#in").after(str)
//阻止浏览器默认右键点击事件
//button元素的右击事件就被屏蔽了,而浏览器其他区域不受影响
$("button.elem").bind("contextmenu", function () {
return false;
})

//为所有的格子加上左右键单击事件
$("button.elem").each(function () {
$(this).mousedown(function (e) {
var x = parseInt($(this).attr('posx'))
var y = parseInt($(this).attr('posy'))
//右键为3
if (3 == e.which) {
//                        alert('右键点击:' + x + "--" + y + "-" + color)
right_click((this), x, y)

} else if (1 ==
3ff8
e.which) {
//左键为1
//                        alert('左键键点击:' + x + "--" + y + "-" + color)
left_click((this), x, y)
}
})
});

g = getMat(rows, cols, 1)
ps = new Array()
cur_num = 0
all_num = 0
paths = new Array()
}

//左键点击函数
function left_click(self, x, y) {
if (g[x][y] == 1) {
//                var color = $(self).css("background-color")
//                alert(color)
$(self).css("background-color", colors[0])
g[x][y] = 0
}
else if (g[x][y] == 0) {
$(self).css("background-color", colors[1])
g[x][y] = 1
}
}

//右键点击函数
function right_click(self, x, y) {
if (g[x][y] == -1) {
//是端点的话
g[x][y] = 1
$(self).css("background-color", colors[1])
if (ps.length == 1) {
ps = new Array()
}
else {
if (ps[0][1] == x && ps[0][2] == y) {
ps.splice(0, 1)
} else {
ps.splice(1, 1)
}
}
} else {
//不是端点
g[x][y] = -1
$(self).css("background-color", colors[2])
ps.push([self, x, y])
//                alert(ps.length)
if (ps.length > 2) {
g[ps[0][1]][ps[0][2]] = 1
$(ps[0][0]).css("background-color", colors[1])
ps.splice(0, 1)
}
//                alert(ps.length)
}
//输出端点信息
//            for (var i = 0; i < ps.length; i++) {
//                console.log(ps[i])
//            }
}

//使用队列,返回一条路径数组
function getPath(g, sx, sy, ex, ey) {
var path = new Array()
var q = new Array()
//0 表示可以访问
var vis = getMat(rows, cols, 0)
vis[sx][sy] = 1
//保存格式 x,y,pre
q.push([sx, sy, -1])
var front = 0
var tail = 1
while (front < tail) {
var t = q[front]

if (t[0] == ex && t[1] == ey) {
var tt = t.slice()
while (tt[2] != -1) {
// 拼接函数(索引位置, 要删除元素的数量, 元素)
path.splice(0, 0, tt.slice());
tt = q[tt[2]]
}
path.splice(0, 0, q[0])
return path
}

for (var i = 0; i < 4; i++) {
var nx = t[0] + dir[i][0]
var ny = t[1] + dir[i][1]

if (nx >= 0 && ny >= 0 && nx < rows && ny < cols &&
g[nx][ny] != 0 && vis[nx][ny] == 0) {
q.push([nx, ny, front])
vis[nx][ny] = 1
tail++
}
}
front += 1
}
return path
}

//使用栈获取所有最短路
function getPaths(g, sx, sy, ex, ey) {

max_len = getPath(g, sx, sy, ex, ey).length
//            alert(max_len)
//0 表示可以走
var vis = getMat(rows, cols, 0)
paths = new Array()
var path = new Array()
path.push([sx, sy, -1])
vis[sx][sy] = 1     //设置起点已经走过
while (path.length > 0) {
//栈顶元素状态
i = path[path.length - 1][0]
j = path[path.length - 1][1]
d = path[path.length - 1][2]

if (i == ex && j == ey) {
//找到一条路径,此时栈中元素既是路径,深拷贝
paths.push(path.slice(0))
//                    alert('suc')
//找到一 个元素后,退栈,并将目标点设为可以走,i,j,k,重新赋值
vis[ex][ey] = 0   //让终点可以走
path.splice(path.length - 1, 1)
i = path[path.length - 1][0]
j = path[path.length - 1][1]
d = path[path.length - 1][2]
}

if (path.length > max_len) {
//退栈
vis[path[path.length - 1][0]][path[path.length - 1][1]] = 0
path.splice(path.length - 1, 1)
continue
}

find = 0
while (d < 3 && find == 0) {
d++;
i = path[path.length - 1][0] + dir[d][0]
j = path[path.length - 1][1] + dir[d][1]

if (i >= 0 && j >= 0 &&
i < rows && j < cols &&
g[i][j] != 0 && vis[i][j] == 0) {
find = 1;
}
}

if (find == 1) {
//入栈
path[path.length - 1][2] = d;
path.push([i, j, -1])
vis[i][j] = 1;
} else {
//退栈
vis[path[path.length - 1][0]][path[path.length - 1][1]] = 0
path.splice(path.length - 1, 1)
}
}

return paths;
}

//使用dijkstra获取所有最短路径
pre = new Array()   //是数组的数组
function dijkstra() {
var cnt = rows * cols //是所有节点的数目
var d = new Array()     //存放起始节点到其他节点的最短距离
var gg = getMat(cnt, cnt, -1)    //-1表示两个点之间不能达到,非0数表示两点距离
var vis = new Array(cnt)
var INF = 99999999
//构建新图
for (var i = 0; i < cnt; i++) {
//将第i个节点变换为r,c的形式,找它所能够到达的所有格子
r = parseInt(i / cols)
c = i % cols
for (var j = 0; j < 4; j++) {
var nx = r + dir[j][0]
var ny = c + dir[j][1]
if (nx >= 0 && ny >= 0 &&
nx < rows && ny < cols &&
g[nx][ny] != 0 && g[r][c] != 0) {
var m = nx * cols + ny
gg[i][m] = 1    //如果相邻两个点都不是障碍点,则两点距离为1
}
}
}

//初始化
for (var i = 0; i < cnt; i++) {
d[i] = INF //距离为无穷大
vis[i] = 0    //表示访问次数
}

pre = new Array()
for (var i = 0; i < cnt; i++) {
pre.push(new Array())
pre[i].push(i)  //每个节点至少指向自己
}
var s = ps[0][1] * cols + ps[0][2]    //起始节点的一维编号
//            console.log('s = ' + s)
d[s] = 0;//起点到自身的距离为0
for (var i = 0; i < cnt; i++) {//循环n次
var u = -1, mind = INF;//u使得d[u]最小,mind存放最小的d[u]
for (var j = 0; j < cnt; j++) //找到未访问的顶点中d[]最小的
if (vis[j] == 0 && d[j] < mind) {
u = j;
mind = d[j];
}

//找不到小于INF的d[j],说明剩下的顶点和起点s不联通
if (u == -1) return;
vis[u] = 1;//标记u已经被访问
for (var v = 0; v < cnt; v++) {
//如果v未访问 u能到达v 以u为中介点可以使得d[v]更小
if (vis[v] == 0 && gg[u][v] != -1 && d[u] + gg[u][v] < d[v]) {
d[v] = d[u] + gg[u][v];//优化d[v]
pre[v] = new Array()
pre[v].push(u);//记录v的前驱顶点是u
} else if (vis[v] == 0 && gg[u][v] != -1 && d[u] + gg[u][v] == d[v]) {
pre[v].push(u);//记录v的前驱顶点是u
}
}
}

//            console.log('cnt=' + cnt)
//            for (var i = 0; i < cnt; i++) {
//                console.log('gg' + i + ':' + gg[i])
//            }
//            for (var i = 0; i < cnt; i++) {
//                console.log('pre' + i + ':' + pre[i])
//            }
//            for (var i = 0; i < cnt; i++) {
//                console.log('d' + i + ':' + d[i])
//            }
}

//dfs 由pre数组得到所有路径,s到aim的路径存放在paths中,每次需要初始化paths
//需要使用path 也需要初始化
function dfs(s, aim, path) {
for (var i = 0; i < pre[aim].length; i++) {
if (pre[aim][i] == aim)
continue
path.push([parseInt(pre[aim][i] / cols), pre[aim][i] % cols]);
if (pre[aim][i] == s) {
paths.push(path.slice(0));
path.pop();
continue;
} else {
dfs(s, pre[aim][i], path);
path.pop();
}
}
}

//路径总数
all_path_cnt = 0
//最大路径数
max_path_cnt = 1000000
//计算路径总数,如果太多,则终止
function dfs_cnt(s, aim, path) {
if (all_path_cnt >= max_path_cnt)
return
for (var i = 0; i < pre[aim].length; i++) {
if (pre[aim][i] == aim)
continue
path.push([parseInt(pre[aim][i] / cols), pre[aim][i] % cols]);
if (pre[aim][i] == s) {
all_path_cnt++
path.pop();
continue;
//                    console.log(all_path_cnt)
} else {
dfs_cnt(s, pre[aim][i], path);
path.pop();
}
}
}

//是否路径数目过多
function has_many_paths() {
paths = new Array()
dijkstra()
all_path_cnt = 0
var s = ps[0][1] * cols + ps[0][2]
var aim = ps[1][1] * cols + ps[1][2]
var path = new Array()
dfs_cnt(s, aim, path)
console.log("all_path_cnt : " + all_path_cnt)
return all_path_cnt >= max_path_cnt
}
//设置坐标为x,y的按钮颜色
function setColor(x, y, color) {
//为所有的格子加上左右键单击事件
$("button.elem").each(function () {
var posx = $(this).attr('posx')
var posy = $(this).attr('posy')
posx = parseInt(posx)
posy = parseInt(posy)
if (posx == x && posy == y) {
$(this).css("background-color", color)
}
});
}

//获得x,y坐标的btn引用
function getBtn(x, y) {
$("button.elem").each(function () {
var posx = $(this).attr('posx')
var posy = $(this).attr('posy')
posx = parseInt(posx)
posy = parseInt(posy)
//                alert(posx + "-" + posy)

if (posx == x && posy == y) {
return $(this)
}
});
}

//在图中显示路径path,是[x,y]坐标数组
function setPath(path) {
if (path.length == 0) {
alert('没有路径')
return
}

//控制台输出路径信息
//            for (var i = 0; i < path.length; i++)
//                console.log(path[i])

for (var i = 0; i < rows; i++) {
for (var j = 0; j < cols; j++) {
if (g[i][j] == 0) {
setColor(i, j, colors[0])
} else if (g[i][j] == 1) {
setColor(i, j, colors[1])
} else {
setColor(i, j, colors[2])
}
}
}

for (var i = 1; i < path.length - 1; i++) {
setColor(path[i][0], path[i][1], colors[3])
}
}

function start() {
if (ps.length != 2) {
UIkit.notification("请选择两个端点", {pos: 'top-center', status: 'warning'});
return
}

var path = getPath(g, ps[0][1], ps[0][2], ps[1][1], ps[1][2])

if (path.length == 0) {
$('#cur_num').text(0)
$('#all_num').text(0)
UIkit.notification("路径不存在...", {pos: 'bottom-center', status: 'warning'});
return
}

//            console.log('rows=' + rows)
//            console.log('cols=' + cols)

if (has_many_paths()) {
UIkit.notification("路径过多无法计算,请增加障碍点或者减少路径长度...", {pos: 'bottom-center', status: 'warning'});
return
}

paths = new Array()
//获取pre数组
dijkstra()
var s = ps[0][1] * cols + ps[0][2]
var aim = ps[1][1] * cols + ps[1][2]
var path = new Array()
path.push([parseInt(aim / cols), aim % cols])

//            console.log(ps[0])
//            console.log(ps[1])
//            console.log(path[0])
//            console.log('s=' + s)
//            console.log('aim=' + aim)
//将pre数组转化为路径
dfs(s, aim, path)

//            console.log('paths.len=' + paths.length)
//                    for (var i = 0; i < paths.length; i++)
//                        console.log('paths=' + paths[i])

//                    alert(rows + '-' + cols)
//                    paths = getPaths(g, ps[0][1], ps[0][2], ps[1][1], ps[1][2])
setPath(paths[0])
cur_num = 0
all_num = paths.length
//                    alert('len=' + paths.length)

//                    for (var i = 0; i < paths.length; i++)
//                        alert(paths[i])

$('#cur_num').text(cur_num + 1)
$('#all_num').text(paths.length)
}
$(document).ready(function () {
rows = parseInt($("#form-rows").val())
cols = parseInt($("#form-cols").val())

reset(rows, cols)

$("#reset").click(function () {
reset(rows, cols)
});

$("#start").click(function () {
start()
});

//行数获取
$("#form-rows").change(function () {
//                    alert($(this).val())
rows = parseInt($("#form-rows").val())
cols = parseInt($("#form-cols").val())
reset(rows, cols)
});

//列数获取
$("#form-cols").change(function () {
//                    alert($(this).val())
rows = parseInt($("#form-rows").val())
cols = parseInt($("#form-cols").val())
reset(rows, cols)
});

//上一页
$("#pre_btn").click(function () {
//                    alert(paths.length)
if (cur_num > 0) {
cur_num--

3ff0
setPath(paths[cur_num])
$('#cur_num').text(cur_num + 1)
}

})

//下一页
$("#next_btn").click(function () {
//                    alert(paths.length)
if (cur_num < paths.length - 1) {
cur_num++
$('#cur_num').text(cur_num + 1)
setPath(paths[cur_num])
}
})

}
);
</script>
</head>

<body class="uk-inline uk-container uk-container-large uk-position-center">

<h1 class="uk-heading-line uk-text-center uk-heading-divider"><span>迷宫寻路--阿豪</span></h1>
<h5 class="uk-text-center"><span>左键设置障碍点,右键设置端点,路径总数最多支持1000000条</span></h5>

<!-- 底层div -->
<div class="uk-inline  "
style="vertical-align: middle;align-items: center;justify-content: center">
<!-- 输入div -->
<div style="margin-bottom: 40px">
<form class="uk-form-horizontal ">
<label class="uk-form-label" style="width: 50px;">行数</label>
<div class="uk-form-controls" style="float: left;width: 50px;margin-right: 80px;margin-left:0px ">
<select class="uk-select uk-form-small " id="form-rows">
<option>8</option>
<option>10</option>
<option>12</option>
<option selected="selected">14</option>
<option>16</option>
<option>18</option>
<option>20</option>
</select>
</div>

<label class="uk-form-label " style="width: 50px">列数</label>
<div class="uk-form-controls" style="float: left;width: 50px;margin-left: 0px">
<select class="uk-select uk-form-small" id="form-cols">
<option>8</option>
<option>10</option>
<option>12</option>
<option selected="selected">14</option>
<option>16</option>
<option>18</option>
<option>20</option>
</select>
</div>

</form>
</div>
<!-- 按钮div -->
<div id="in" style="vertical-align: middle;align-items:center;margin-left: 50px;">
<button id='reset' class="uk-button uk-button-primary uk-button-small" style="margin-right: 30px">重置迷宫</button>
<button id='start' class="uk-button uk-button-primary uk-button-small">计算路径</button>
</div>

<div style="margin: 20px;vertical-align: middle;margin-left: 70px">
<button id='pre_btn' class="uk-button uk-button-default uk-button-small">上一条</button>
<label id='cur_num' class="uk-form-label uk-small" style="width: 50px">0</label>
/
<label id='all_num' class="uk-form-label " style="width: 50px">0</label>
<button id='next_btn' class="uk-button uk-button-default uk-button-small">下一条</button>

</div>

</div>

</body>
</html>

比较丑....

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  js uikit jQuery dijkstra