【问题标题】:Calculate the f cost in A*(A-star) algorithm on coordinated undirected graph在协调无向图上计算 A*(A-star) 算法中的 f 成本
【发布时间】:2021-12-19 03:03:16
【问题描述】:

我正在尝试在 react.js 中实现 A* 算法,但在实现 fScore 函数时我很困惑。我知道 f=g+h 其中 g 是从起始节点到当前节点的 gScore,h 是从 currentNode 到结束节点的启发式距离。我使用欧几里德距离计算了启发式,我在其中发送了当前节点和结束节点的坐标,但我不知道如何计算 gScore。 我图中的每个节点都有: ID, 姓名, X, 是的, connectedToIds:[] //neihbours 或 connectedNodes 列表。 更新:我为每个节点添加了变量 parentId、fscore、gscore、hscore。所以现在每个节点都有变量:id, 姓名, X, 是的, connectedToIds:[], fscore: 0, gscore: 0, hscore: 0, 父标识:空。 Update2: originLocationId 是起始节点的id。 destinationLocationId 是结束节点的 id。位置是所有节点的列表。 我的代码:

export default class TurnByTurnComponent extends React.PureComponent {
    constructor(props) {
        super(props);
    }

    render() {
        const {
            destinationLocationId,
            locations,
            originLocationId
        } = this.props;
        console.log(locations)
        console.log(originLocationId)
        console.log(destinationLocationId)


        var openedList = [];
        var closedList = [];

        if (destinationLocationId != null && originLocationId != null) {
            openedList.push(originLocationId);
            while (openedList.length != 0) {
                var currentLoc = openedList[0]; //minFvalue
                const currIndex = openedList.indexOf(currentLoc);
                openedList.splice(currIndex, 1); //deleting currentNode from openedList
                closedList.push(currentLoc) //adding currentNode to closedList

                if (currentLoc == destinationLocationId) {
                    //return path
                }

                

            }

        }

        function heuristic(currentNode, endNode) { //euclidean distance
            var x = Math.pow(endNode.x - currentNode.x, 2);
            var y = Math.pow(endNode.y - currentNode.y, 2);
            var dist = Math.sqrt(x + y);
            return dist;
        }

        function gScore(startNode, currentNode) {

        }




        return (
            <div className="turn-by-turn-component">
                {locations.map(loc => (
                    <li key={loc.id}>
                        {loc.name}
                    </li>
                ))}

                <TodoList
                    title="Mandatory work"
                    list={[
                      
                    ]}
                />
                <TodoList
                    title="Optional work"
                    list={[
                      
                    ]}
                />
            </div>
        );
    }
}

TurnByTurnComponent.propTypes = {
    destinationLocationId: PropTypes.number,
    locations: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.number.isRequired,
        name: PropTypes.string.isRequired,
        x: PropTypes.number.isRequired,
        y: PropTypes.number.isRequired,
        connectedToIds: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired
    })),
    originLocationId: PropTypes.number
};

Update3:我的代码的新版本

export default class TurnByTurnComponent extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = { shortestPath: [] }
    }


    render() {
        const {
            destinationLocationId,
            locations,
            originLocationId
        } = this.props;


        if (destinationLocationId != null && originLocationId != null) {

            if (originLocationId == destinationLocationId) { //check if the startNode node is the end node
                return originLocationId;
            }

            var openList = [];
            let startNode = getNodeById(originLocationId);
            let endNode = getNodeById(destinationLocationId)

            startNode.gcost = 0
            startNode.heuristic = manhattanDistance(startNode, endNode)
            startNode.fcost = startNode.gcost + startNode.heuristic;


            //start A*
            openList.push(startNode); //starting with the startNode 
            while (openList.length) {
                console.log("inside while")

                var currentNode = getNodeOfMinFscore(openList);

                if (currentIsEqualDistanation(currentNode)) {
                    var path = getPath(currentNode)
                    this.setState({
                        shortestPath: path,
                    });
                    return path //todo
                }
                deleteCurrentFromOpenList(currentNode, openList);

                for (let neighbourId of currentNode.connectedToIds) {

                    var neighbourNode = getNodeById(neighbourId);
                    var currentNodeGcost = currentNode.gcost + manhattanDistance(currentNode,         neighbourNode);
                    console.log(currentNodeGcost)
                    console.log(neighbourNode.gcost)
                    if (currentNodeGcost < neighbourNode.gcost) {
                        console.log("Helloooo")
                        neighbourNode.parentId = currentNode.id;
                        // keep track of the path
                        // total cost saved in neighbour.g
                        neighbourNode.gcost = currentNodeGcost;
                        neighbourNode.heuristic = manhattanDistance(neighbourNode, endNode);
                        neighbourNode.fcost = neighbourNode.gcost + neighbourNode.heuristic;
                        if (!openList.includes(neighbourId)) {
                            openList.push(neighbourNode);
                        }
                    }
                }
            }
            return null;
        }


        function deleteCurrentFromOpenList(currentNode, openList) {
            const currIndex = openList.indexOf(currentNode);
            openList.splice(currIndex, 1); //deleting currentNode from openList
        }

        function currentIsEqualDistanation(currentNode) {
            //check if we reached out the distanation node
            return (currentNode.id == destinationLocationId)
        }

        function getNodeById(id) {
            var node;
            for (let i = 0; i < locations.length; i++) {
                if (locations[i].id == id) {
                    node = locations[i]
                }
            }
            return node
        }

        function getPath(endNode) {
            var path = []
            while (endNode.parentId) {
                path.push(endNode.name)
                endNode = endNode.parentId;
            }
            return path;
        }

        function getNodeOfMinFscore(openList) {
            var minFscore = openList[0].fcost; //initValue
            var nodeOfminFscore;
            for (let i = 0; i < openList.length; i++) {

                if (openList[i].fcost <= minFscore) {

                    minFscore = openList[i].fcost //minFvalue
                    nodeOfminFscore = openList[i]
                }
            }

            return nodeOfminFscore
        }

        //manhattan distance is for heuristic and gScore. Here I use Manhattan instead of Euclidean 
        //because in this example we dont have diagnosal path.
        function manhattanDistance(startNode, endNode) {
            var x = Math.abs(endNode.x - startNode.x);
            var y = Math.abs(endNode.y - startNode.y);
            var dist = x + y;
            return dist;
        }


        return (
            <div className="turn-by-turn-component">
                {locations.map(loc => (
                    <li key={loc.id}>
                        {JSON.stringify(loc.name)},
                    </li>
                ))}
                <TodoList
                    title="Mandatory work"
                    list={
                        this.state.shortestPath
                    }
                />
                <TodoList
                    title="Optional work"
                    list={[

                    ]}
                />
            </div>
        );
    }
}

TurnByTurnComponent.propTypes = {
    destinationLocationId: PropTypes.number,
    locations: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.number.isRequired,
        name: PropTypes.string.isRequired,
        x: PropTypes.number.isRequired,
        y: PropTypes.number.isRequired,
        connectedToIds: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired
    })),
    originLocationId: PropTypes.number
};

【问题讨论】:

    标签: javascript reactjs shortest-path a-star undirected-graph


    【解决方案1】:

    h 是启发式方法,是对到达最终节点所需的可能成本的合理猜测,g 是到达当前节点所花费的实际成本。在您的情况下,它甚至可能与您用于 h 的欧几里得距离相同。

    在真实案例场景中,使用欧几里得距离,连接不同城市的图,h 是两个城市的空中距离,g 是它们的道路距离。

    此外,如果您使用单调启发式(或低估启发式,例如欧几里德距离),则不需要关闭列表,因为已证明第一个找到的路径也是最短的:更长或已经访问过的路径将在被探索之前被丢弃。

    可能令人困惑的是,您需要在探索图形的过程中跟踪g,而h 只是测量当前节点和结束节点之间的直线,g 测量之间的所有线您探索的节点到达当前。

    // h
    function heuristic(n1, n2) {
        return Math.sqrt(
            Math.pow(n1.x - n2.x, 2) +
            Math.pow(n1.y - n2.y, 2)
        );
    }
    
    // g - actually is the same as h
    const cost = heuristic;
    
    function astar(start, end, graph, h, g) {
        if (start.id == end.id) { return [ start.id ] }
    
        // omitted CLEAN-UP of the graph
    
        // close is not needed with an optimistic heuristic 
        // so I've commented the "close" parts.
        // An optimistic heuristic works also if you decomment them
    
        // var close = [];
        var open  = [];
    
        start.g = 0;
        start.h = h(start, end);
        start.f = start.g + start.h;
    
        open.push(start);
    
        while (open.length) {
            // since open is sorted, by popping
            // the last element we take the cheapest node
            var curr = open.pop();
    
            if (curr == end) { return resolvePath(curr, graph); }
            // close.push(curr);
            for (let nid of curr.connectedToIds) {
                // if (close.some(n => n.id == nid)) { continue; }
                var neighbour = graph.find(n => n.id == nid);
                
                // HERE you compute and store
                // the current g of the node 
                var current_g = curr.g + g(curr, neighbour);
    
                var isBetter_g = false;
                var isInOpen = open.some(n => n.id == nid);
    
                // Some implementations skip this check 
                // because they assume that the cost function
                // is a non-negative distance.
                // if so you could check just the two nodes g
                // and insert the node if not already in the open set
                // because the default g of a node is 0
                if (!isInOpen) {
                    // unexplored node, maybe we should explore it
                    // later, so we add to the open set and
                    // we update it with the current path cost
                    open.push(neighbour)
                    isBetter_g = true;
                }
                else if (current_g < neighbour.g) {
                    // the current path is better than
                    // those already explored, we need to 
                    // update the neighbour with the current path cost
                    isBetter_g = true;
                }
                if (isBetter_g) {
                    // === path cost update ===
    
                    // track current path
                    neighbour.parent = curr.id;
               
                    // HERE you keep track of the path
                    // total cost saved in neighbour.g
                    neighbour.g = current_g;
               
                    // neighbour.h is redundant, can be stored directly in f
                    // but keep it for debugging purpose
                    neighbour.h = h(neighbour, end);
    
                    neighbour.f = neighbour.g + neighbour.h;
                    
                    if (!isInOpen) {
                        // sorting makes the last element of
                        // open the cheapest node. We sort after
                        // the path cost update to ensure this property
                        open.sort((n1, n2) => n2.f - n1.f)
                    }
                }
            }
        }
    
        // failure
        return []; // or return null
    }
    
    // utility to retrieve an array of ids
    // from the tracked path
    function resolvePath(end, graph) {
        let path = [ end.id ];
        while (end && end.parent >= 0) {
            path.unshift(end.parent);
            end = graph.find(n => n.id == end.parent);
        }
        return path;
    }
    
    // example of using the function
    astar(startNode, endNode, locations, heuristic, cost);
    

    【讨论】:

    • 谢谢。您能解释一下您使用 isBest_g 变量的方式吗?我的意思是我们为什么需要它?我们不能只检查最低 Fcost 吗?
    • 如果你的移动不允许对角线移动,你的启发式应该使用节点之间的曼哈顿距离,它可以在没有 sqrt 或 pow 的情况下计算。 H = Math.abs(ax - bx) + Math.abs(ay - by)
    • @AdamA isBest_g,也许不是最好的变量名(isBetter_g 可能会“更好”),跟踪是否应该更新或忽略邻居。是的,我们应该检查f 成本,我在移植代码时弄错了,因为开放集必须按f 排序,这样每次您执行open.pop 时,您实际上得到的节点更便宜。请注意,如果您发现一个未探索的节点,您必须将其添加到打开列表中(不检查f),这就是isBest_g 的目标。我将编辑答案以澄清
    • @AdamA 我鼓励阅读以下链接,因为我知道很多人已经根据这些文章编写了 A* 实现。 redblobgames.com
    • @AdamA 我认为你错过/误解了g(curr, neighbour) 部分,如果current_g 从未不同于0。current_g = curr.g + g(curr, neighbour) -=-&gt; 0 + cost{current_node -&gt; child_node}g(curr, neighbour) 与您的代码中的 manhattanDistance(currentNode, neighbourNode) 相同。顺便说一句,您更新的代码对我来说似乎很好,至少在逻辑层面上,只是缺少非负检查(但它不适用于曼哈顿)。
    猜你喜欢
    • 1970-01-01
    • 2011-03-27
    • 1970-01-01
    • 2012-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-08
    • 1970-01-01
    相关资源
    最近更新 更多