A*算法 启发式算法

package com.ex.cy.demo4.alg.graph.ewdi;

import com.ex.cy.demo4.alg.heap.IndexMinPQ;

import java.util.LinkedList;
import java.util.List;

//A* 算法
// 属于一种启发式搜索算法
// 和Djkstra有相似之处(和BFS)
//不同点在于,将周围顶点放入优先队列后的出队条件,不再是让g(v)(从起点到该点距离,v是被考察的顶点) 最小的先出队列
//而是让 g(v) + h(v) 最小的先出队
//h(v) 为 启发函数给出的值,用来估计v点到终点的估计距离,一般有1.欧几里得距离(sqrt((gx-vx)^2 + (gy-vy)^2)) 2.曼哈顿距离(abs(gx-vx)+abs(gy-vy)) (方格子,笛卡尔坐标系,只能上下左右)
//所以每次优先出队的是最接近到达终点最短距离的点(贪心,并不一定是最优解)
//循环退出条件是:只要遍历到终点则退出循环

public class AStart {

    CoordinateEdgeWeightedDigraph cewd;
    IndexMinPQ<Float> impq;
    ASVertex[] vertices;
    int s;
    int g;
    Edge[] fromEdge;      //记录顶点V是从哪条边探索过来的,除了顶点,每个顶点有一条

    //有向无环图,起点,终点
    public AStart(CoordinateEdgeWeightedDigraph cewd, int s, int g) {
        this.cewd = cewd;
        this.s = s;
        this.g = g;
        vertices = new ASVertex[cewd.v()];
        impq = new IndexMinPQ<>(cewd.v());  //最多有全图顶点,都被放进去过
        fromEdge = new Edge[cewd.v()];
        for (int i = 0; i < cewd.v(); i++) {
            vertices[i] = new ASVertex(i, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY);
        }
        vertices[s].g = 0;
        vertices[s].h = 0;

        astartSearch();
    }

    //启发函数
    //估计从一个点到终点的曼哈顿距离
    public float heuristicFunc(Vertex v1, Vertex v2) {
        //欧几里得距离
//        float dx = v2.x - v1.x;
//        float dy = v2.y - v2.y;
//        float dist = (float) Math.sqrt(dx * dx + dy * dy);
//        return dist;

        //曼哈顿距离(省去求平方,开根号,效率会比较快)
        return Math.abs(v2.x - v1.x) + Math.abs(v2.y - v1.y);
    }

    public void astartSearch() {
        impq.insert(s + 1, 0f); //v顶点索引, g(v)+h(v) 起点到该点距离 + 该点到终点估计距离

        while (!impq.isEmpty()) {
            int v = impq.topIndex() - 1;
            float vdst = impq.delTop();
            Vertex fromV = cewd.getVertex(v);
            vertices[v].h = vdst;

            if (v == g)    //当前已到达终点
                break;     //停止探索

            for (Edge e : cewd.adj(v)) {
                int w = e.other(v);
                if (vertices[w].h != Float.POSITIVE_INFINITY)    //已探索过则跳过
                    continue;

                Vertex toW = cewd.getVertex(w);
                //到下一个顶点w的实际距离
                float gDist = vertices[v].g + e.weight;
                float heuDist = gDist + heuristicFunc(toW, fromV);

                if (impq.contain(w + 1) && impq.get(w + 1) < heuDist)
                    continue;                         //包含到w的边,但距离未缩短,则跳过当前这条边

                //新增或修改
                fromEdge[w] = e;
                vertices[w].g = gDist;
                if (!impq.contain(w + 1)) {               //优先队列中不存在,则直接插入新的顶点
                    impq.insert(w + 1, heuDist);
                } else if (impq.get(w + 1) > heuDist) {   //优先队列中存在,但以当前顶点v作为相邻点距离更小
                    impq.change(w + 1, heuDist);          //更新距离
                }
            }
        }
    }

    //返回从起点到终点的路径的权重总和
    public float getCost() {
        return vertices[g].g;
    }

    //获取从起点到终点的路径
    //并不能像djkstra找到全局最优的最短路径,因为没有遍历其他可能的路径,因为第一次探索到终点就结束循环了
    //但是优点是:找了个一个近似最优解,并用了较少的顶点探索次数,倾向性的往终点方向探索;因此可以用于很大的图中,不必遍历完所有顶点,即可找到一条近似最优解的路径,在时间效率和解的近似最优上达到了一种平衡
    public List<Edge> getEdge() {
        List<Edge> edges = new LinkedList<>();

        int v = g;
        Edge e = fromEdge[v];
        while (e != null) {
            edges.add(0, e);
            v = e.other(v);
            e = fromEdge[v];
        }
        return edges;
    }

    public static void main(String[] a) {
        CoordinateEdgeWeightedDigraph cewd = new CoordinateEdgeWeightedDigraph(6);
        //加入顶点
        //顶点索引, x y 坐标
        cewd.addVertex(0, 0, 0);
        cewd.addVertex(1, 1, 0.5f);
        cewd.addVertex(2, 0, 1);
        cewd.addVertex(3, 1, 1);
        cewd.addVertex(4, 5, 5);
        cewd.addVertex(5, 6, 6);

        //加入有向边,权重为两点之间的欧几里得距离
        cewd.addEdge(0, 1);
        cewd.addEdge(0, 2);
        cewd.addEdge(1, 3);
        cewd.addEdge(2, 3);
        cewd.addEdge(3, 4);

        System.out.println(cewd);

        //测试1
        AStart aStart = new AStart(cewd, 0, 4);
        System.out.println(aStart.getEdge());
        System.out.println("Cost: " + aStart.getCost());

        //测试2 不可达顶点
        AStart aStart2 = new AStart(cewd, 0, 5);
        System.out.println(aStart2.getEdge());
        System.out.println("Cost: " + aStart2.getCost());
    }
}

输出

Ver{v=0, x=0.0, y=0.0}: 0-1 1.12, 0-2 1.00, 
Ver{v=1, x=1.0, y=0.5}: 1-3 0.50, 
Ver{v=2, x=0.0, y=1.0}: 2-3 1.00, 
Ver{v=3, x=1.0, y=1.0}: 3-4 5.66, 
Ver{v=4, x=5.0, y=5.0}: 
Ver{v=5, x=6.0, y=6.0}: 

[0-1 1.12, 1-3 0.50, 3-4 5.66]
Cost: 7.274888
[]
Cost: Infinity