A*算法求解八数码(九数码)问题(JavaScript版)

网上的代码大多使用C++实现。由于要实现可视化的缘故,因此使用HTML+JS重新实现了一遍。

TEST_MODE 表示使用测试样例进行测试,设置为False即可实现随机输入。

MODE代表使用的启发函数,1表示放错位置个数作为估值函数;2代表曼哈顿距离。

isNineMode代表九数码问题。

s代表开始状态。

e代表结束状态。

AStarAlgorithm函数是A*算法的主体,其他的函数用于作业要求的输出显示,可以忽略。

算法中还实现了JS版的优先队列,但是效率不算高,可以考虑使用堆算法进行优化。

A*算法的Javascript代码

// index.js
var s = [0,1,2,3,4,5,6,7,8];
var e = [1,2,3,8,0,4,7,6,5];
var finalRoad;
var isNineMode = false;
var MODE = 1;
var expandCount = 0;
var S0 = 0;
var TEST_MODE = true;

// 测试用状态
function testData(){
    var data = [[6,1,5,0,2,7,3,8,4],
                [0,8,7,5,3,4,1,2,6],
                [6,3,2,4,5,7,1,0,8],
                [3,8,0,1,4,2,6,7,5],
                [4,1,5,0,3,2,7,6,8],
                [6,7,8,0,3,4,1,2,5],
                [2,1,7,4,8,3,5,0,6],
                [3,4,7,5,6,2,1,8,0],
                [3,8,1,5,0,7,6,2,4],
                [7,8,1,5,6,2,4,3,0]];
    var len = data.length;
    return data[Math.floor(Math.random()*len)];
};

window.onload = function() {
    initial();
}

function initial() {
    if(TEST_MODE) {
        s = testData();
    }
    else{
        s = [0,1,2,3,4,5,6,7,8];
        s.sort(function(){
            return 0.5 - Math.random();
        });
    }
    showInitial(s);
}

document.getElementById("reset8").onclick = function() {
    isNineMode = false;
    MODE = 1;
    initial();
}

document.getElementById("reset9").onclick = function() {
    isNineMode = true;
    MODE = 1;
    initial();
}

document.getElementById("begin8_1").onclick = function() {
    MODE = 1;
    S0 = 0;
    isNineMode = false;
    AStarAlgorithm();
}

document.getElementById("begin8_2").onclick = function() {
    MODE = 2;
    S0 = 0;
    isNineMode = false;
    AStarAlgorithm();
}

document.getElementById("begin9_1").onclick = function() {
    MODE = 1;
    S0 = 0;
    isNineMode = true;
    AStarAlgorithm();
}

document.getElementById("begin9_2").onclick = function() {
    MODE = 2;
    S0 = 0;
    isNineMode = true;
    AStarAlgorithm();
}

function AStarAlgorithm() {
    console.clear();
    var a = new AStar();
    a.setMode(MODE);
    a.init(s, e);
    if(a.getStart().canSolved(a.getEnd())){
        a.AStarAlgorithm();
        a.display(a.getEnd());
        finalRoad = a.getRoad();
        show(0);
    }
    else{
        alert("此状态下无解");
    }
}

function show(i) {
    if(i == 0){
        document.getElementById("result").textContent = "";
        document.getElementById("result").textContent += '总拓展节点数: ' + expandCount + '\n\n';
    }
    if(i < finalRoad.length) {
        if(finalRoad[i].getFValue() > S0) {
            alert("步骤 "+i+" 不满足条件 f(n)≤ f*(S0)");
        }
        if(i >= 1 && MODE == 1 &&
		   finalRoad[i-1].getHValue() > 1+finalRoad[i].getHValue()) {
            alert("步骤 "+i+" 不满足条件 h(ni)≤ 1+h(nj)");
        }
        for(var j = 0; j < finalRoad[i].getCodeMap().length; j++) {
            var b = 'pos'+j;
            document.getElementById(b).style.opacity = 1;
            if(!isNineMode && finalRoad[i].getCodeMap()[j] == 0){
                document.getElementById(b).style.opacity = 0;
            }
            else{
                document.getElementById(b).textContent = "" + finalRoad[i].getCodeMap()[j];
            }
        }
        document.getElementById("result").textContent += "步骤 " + i + ':\n';
        document.getElementById("result").textContent += "评估函数值(F): " + finalRoad[i].getFValue() + "\n";
        if(MODE == 1) {
           document.getElementById("result").textContent += "评估函数值(H): " + finalRoad[i].getHValue() + "\n";
        }
        document.getElementById("result").textContent += "估值最小节点:  " + finalRoad[i].getCodeMap().toString() + '\n\n';
        i++;
        setTimeout('show(' + i +')', 250);
    }
}

function showInitial(m) {
    for(var j = 0; j < m.length; j++) {
        var b = 'pos'+j;
        document.getElementById(b).style.opacity = 1;
        if(!isNineMode && m[j] == 0){
            document.getElementById(b).style.opacity = 0;
        }
        else{
            document.getElementById(b).textContent = "" + m[j];
        }
    }
}

// Astar part

// 创建一个优先队列数据结构
function PriorityQueue() {
    var items = [];
    // 向队列尾部添加一个(或多个)新的项
    this.push = function(element){
        if(this.isEmpty()) {
            items.push(element);
        }
        else {
            var flag = false;
            for(var i = 0; i < items.length; i++){
                if(items[i].getFValue() >= element.getFValue()) {
                    items.splice(i, 0, element);
                    flag = true;
                    break;
                }
            }
            if(!flag){
                items.push(element);
            }
        }
    };
    // 移除队列的第一(即排在队列最前面的)项,并返回被移除的元素
    this.pop = function(){
        return items.shift();
    };
    // 返回队列中第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动
    this.top = function(){
        return items[0];
    };
    // 如果队列中不包含任何元素,返回 true ,否则返回 false
    this.isEmpty = function(){
        return items.length == 0;
    };
    // 清空队列
    this.clear = function(){
        items = [];
    };
    // 返回队列包含的元素个数,与数组的 length 属性类似
    this.size = function(){
        return items.length;
    };
    this.print = function(){
        console.log(items.toString());
    };
}

function State() {
    var codeMap = [];
    var parent = null;
    var hValue, gValue, fValue, zeroPos;

    this.init = function(code) {
        hValue = gValue = fValue = 0;
        parent = null;
        for(var i = 0; i < code.length; i++) {
            codeMap.push(code[i]);
            if(code[i] == 0){
                zeroPos = i;
            }
        }
    };

    this.initWithState = function(sta) {
        this.init(sta.getCodeMap());
        this.setAllValueWithState(sta);
    };

    this.setParent = function(sta) {
        parent = sta;
    };

    this.setCodeMap = function(code) {
        codeMap = code;
    };

    this.setZeroPos = function(z) {
        zeroPos = z;
    };

    this.setGValue = function(g) {
        gValue = g;
    };

    this.setFValue = function() {
        fValue = gValue + hValue;
    }

    this.getParent = function() {
        return parent;
    };

    this.getFValue = function() {
        return fValue;
    };

    this.getGValue = function() {
        return gValue;
    };

    this.getHValue = function() {
        return hValue;
    };

    this.getCodeMap = function() {
        return codeMap;
    };

    this.getZeroPos = function() {
        return zeroPos;
    };

    this.setHValue1 = function(src) {
        hValue = 0;
        for(var i = 0; i < codeMap.length; i++) {
            if((isNineMode || codeMap[i] != 0) && codeMap[i] == src.getCodeMap()[i]) {
                hValue++;
            }
        }
        hValue = isNineMode? 9 - hValue : 8 - hValue;
    };

    this.setHValue2 = function(src) {
        hValue = 0;
        for(var i = 0; i < codeMap.length; i++) {
            for(var j = 0; j < src.getCodeMap().length; j++) {
                if((isNineMode || codeMap[i] != 0) && codeMap[i] == src.getCodeMap()[j]) {
                    hValue += Math.abs(parseInt((i-j)/3)) + Math.abs(i % 3 - j % 3);
                }
            }
        }
    };

    this.setAllValue = function(src, depth, mode = 1, isSetG = true) {
        if(mode == 1) {
            this.setHValue1(src);
        } else {
            this.setHValue2(src);
        }
        if(isSetG){
            gValue = depth;
        }
        fValue = gValue + hValue;
    };

    this.setAllValueWithState = function(sta) {
        hValue = sta.getHValue();
        gValue = sta.getGValue();
        zeroPos = sta.getZeroPos();
        this.setFValue();
    };

    this.isEqual = function(src) {
        for(var i = 0; i < codeMap.length; i++) {
            if(src.getCodeMap()[i] != codeMap[i]){
                return false;
            }
        }
        return true;
    };

    this.canSolved = function(tar) {
        var res1 = 0, res2 = 0;
        for(var i = codeMap.length-1; i >= 1; i--) {
            for(var j = i-1; j >= 0; j--) {
                if(codeMap[i] != 0 && codeMap[j] != 0
				   && codeMap[j] > codeMap[i]) {
                    res1++;
                }
                if(tar.getCodeMap()[i] != 0 && tar.getCodeMap()[j] != 0
				   && tar.getCodeMap()[j] > tar.getCodeMap()[i]) {
                    res2++;
                }
            }
        }
        return (res1%2) == (res2%2);
    };

    this.showMap = function() {
        console.log(codeMap);
    }
};

var AStar = function() {
    var OpenTable = [], ClosedTable = [];
    var start = null;
    var end = null;
    var road = [];
    var mode = 1;

    this.init = function(startMap, endMap) {
        start = new State();
        start.init(startMap);
        end = new State();
        end.init(endMap);
    };

    this.isInTable = function(table, src) {
        for(var i = 0; i < table.length; i++) {
            if(table[i].isEqual(src)){
                return i;
            }
        }
        return -1;
    };

    this.setMode = function(m) {
        mode = m;
    };

    this.deleteFromOpenTable = function(src) {
        var i = 0;
        for(i = 0; i < OpenTable.length; i++) {
            if(OpenTable[i].isEqual(src)){
                break;
            }
        }
        OpenTable.splice(i,1);
    };

    this.AStarAlgorithm = function() {
        start.setAllValue(end, 0, mode, true);
        var OpenQueue = new PriorityQueue();
        OpenTable.push(start);
        OpenQueue.push(start);
        var move = [[0,1], [1,0], [0,-1], [-1,0]];
        expandCount = 0;

        while(OpenTable.length > 0) {
            var tmp = OpenQueue.top();
            if(end.isEqual(tmp)) {
                end.setParent(tmp.getParent());
                end.setAllValueWithState(tmp);
                console.log("总扩展节点数:", expandCount);
                S0 = end.getFValue();
                return true;
            }

            OpenQueue.pop();
            ClosedTable.push(tmp);
            this.deleteFromOpenTable(tmp);
            console.log("Open表节点数量:", OpenTable.length);
            console.log("评估值最小节点:", tmp.getCodeMap().toString());
            for(var i = 0; i < 4; i++) {
                var y = tmp.getZeroPos() % 3;
                var x = (tmp.getZeroPos() - y) / 3;
                if(x + move[i][0] < 0 || x + move[i][0] >= 3 ||
                   y + move[i][1] < 0 || y + move[i][1] >= 3) {
                    continue;
                }
                var zPos = (x + move[i][0]) * 3 + (y+move[i][1]);

                if(zPos < 0 || zPos >= tmp.getCodeMap().length) {
                    continue;
                }

                var newState = new State();
                newState.initWithState(tmp);
                var newCodeMap = newState.getCodeMap();
                newCodeMap[tmp.getZeroPos()] = newCodeMap[zPos];
                newCodeMap[zPos] = 0;
                newState.setCodeMap(newCodeMap);
                newState.setParent(tmp);
                newState.setZeroPos(zPos);
                newState.setGValue(tmp.getGValue()+1);

                var flag = this.isInTable(OpenTable, newState);
                var flag0 = this.isInTable(ClosedTable, newState);

                if(flag >= 0) {
                    if(newState.getGValue() < OpenTable[flag].getGValue()){
                        OpenTable[flag].setGValue(newState.getGValue());
                        OpenTable[flag].setParent(newState.getParent());
                        OpenTable[flag].setFValue();
                        newState = null;
                    }
                }
                else if(flag0 >= 0) {
                    if(newState.getGValue() < ClosedTable[flag0].getGValue()){
                        ClosedTable[flag0].setGValue(newState.getGValue());
                        ClosedTable[flag0].setParent(newState.getParent());
                        ClosedTable[flag0].setFValue();
                        newState = null;
                        OpenQueue.push(ClosedTable[flag0]);
                        OpenTable.push(ClosedTable[flag0]);
                        ClosedTable.splice(flag0, 1);
                    }
                }
                else {
                    newState.setAllValue(end, 0, mode, false);
                    OpenQueue.push(newState);
                    OpenTable.push(newState);
                }
                expandCount++;
                if(OpenTable.length >= 15000){
                    alert("该问题有解,但是用此算法无法在有限空间内求解");
                    return false;
                }
            }
        }
        return false;
    };

    this.display = function(p) {
        if(p != null) {
            this.display(p.getParent());
            road.push(p);
        }
    };

    this.getRoad = function() {
        return road;
    }

    this.getStart = function() {
        return start;
    };

    this.getEnd = function() {
        return end;
    };
};

简单界面的HTML代码

<!DOCTYPE html>
<html>
<head>
    <title>A*算法</title>
</head>
<body>
    <h1 id="title">A* 算法</h1>
    <div id="container">
        <button id="pos0" class="numblock">
            1
        </button><button id="pos1" class="numblock">
            2
        </button><button id="pos2" class="numblock">
            3
        </button><button id="pos3" class="numblock">
            4
        </button><button id="pos4" class="numblock">
            5
        </button><button id="pos5" class="numblock">
            6
        </button><button id="pos6" class="numblock">
            7
        </button><button id="pos7" class="numblock">
            8
        </button><button id="pos8" class="numblock">
            &nbsp;
        </button>
    </div>
    <div id="controller">
        <button id="begin8_1">八数码(h1)</button>
        <button id="begin8_2">八数码(h2)</button>
        <button id="begin9_1">九数码(h1)</button>
        <button id="begin9_2">九数码(h2)</button>
    </div>
    <div id="resetcontroller">
        <button id="reset8">重置八数码</button>
        <button id="reset9">重置九数码</button>
    </div>
    <div id="resultcontainer">
        <h4 id="resTitle">输出结果显示</h4>
        <textarea id="result" cols="50" rows="25" readonly="true" resize="false"></textarea>
    </div>

    <script type="text/javascript" src="index.js"></script>

    <style type="text/css">
        .numblock {
            display: inline-block;
            width: 80px;
            height: 80px;
            margin: 1px;
            padding: 0;
            font-size: 26pt;
            outline: none;
            background-color: white;
            border-radius: 10px;
            border-color: lightgrey;
        }

        #title {
            width: 400px;
            margin: 40px auto;
            position: relative;
            text-align: center;
        }

        #container {
            width: 260px;
            height: 260px;
            margin: 0 auto;
            position: relative;
            text-align: center;
        }

        #controller {
            width: 500px;
            margin: 20px auto;
            position: relative;
            text-align: center;
        }

        #controller > button {
            background-color: rgb(46,205,113);
            color: white;
            border-radius: 10px;
            border: 2px;
            width: 100px;
            height: 30px;
            cursor: pointer;
            outline: none;
        }

        #resetcontroller {
            width: 400px;
            margin: 20px auto;
            position: relative;
            text-align: center;
        }

        #resetcontroller > button {
            background-color: rgb(76,139,245);
            color: white;
            border-radius: 10px;
            border: 2px;
            width: 100px;
            height: 30px;
            cursor: pointer;
            outline: none;
        }

        #resultcontainer{
            margin: 5px auto;
            position: relative;
            text-align: center;
        }

        #result {
            resize: none;
        }
    </style>
</body>
</html>
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZTao-z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值