跳转至

启发式搜索

此类算法很少会出现在算法竞赛中,主要应用于人工智能方面

常用的启发函数

对于网格地图来说, 如果只能四方向(上下左右)移动, 曼哈顿距离(Manhattan distance) 是最合适的启发函数.

function Manhattan(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    // 在最简单的情况下, D 可以取 1, 返回值即 dx + dy
    return D * (dx + dy)

如果网格地图可以八方向(包括斜对角)移动, 使用 切比雪夫距离(Chebyshev distance) 作为启发函数比较合适.

function Chebyshev(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    // max(dx, dy) 保证了斜对角的距离计算
    return D * max(dx, dy)
如果地图中允许任意方向移动, 不太建议使用网格 (Grid) 来描述地图, 可以考虑使用 路点 (Way Points) 或者 导航网格 (Navigation Meshes) , 此时使用 欧式距离(Euclidean distance) 来作为启发函数比较合适.

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    // 在最简单的情况下, D 可以取 1, 返回值即 sqrt(dx * dx + dy * dy)
    return D * sqrt(dx * dx + dy * dy)

欧式距离因为有一个 sqrt() 运算, 计算开销会增大, 所以可以使用 Octile 距离 来优化(不知道怎么翻译), Octile 的核心思想就是假定只能做 45 度角转弯.

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    k = sqrt(2) - 1
    return max(dx, dy) + k * min(dx, dy)

IDAStar

基于迭代加深与启发函数算法

问题: 旋转游戏 (类平面魔方)
题目地址https://www.luogu.com.cn/problem/UVA1343

启发函数

贪心思想: 每次操作最优只能减少一个错误值 因此最少需要操作 8 - 中心数最大和数值

AC Code

import java.io.*;
import java.util.*;
import java.lang.*;
import java.math.*;

public class Main {

    Scanner sin = new Scanner(System.in);
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    StreamTokenizer cin = new StreamTokenizer(in);
    PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));

    final int N = 1010, mod = (int)(1e9+7), inf = 0x3f3f3f3f;

    int nextInt() throws Exception {
        cin.nextToken();
        return (int) cin.nval;
    }

    /*
          0     1
          2     3
    4  5  6  7  8  9  10
          11    12
    13 14 15 16 17 18 19
          20    21
          22    23
    */

    int[][] op = {
        {0, 2, 6, 11, 15, 20, 22},
        {1, 3, 8, 12, 17, 21, 23},
        {10, 9, 8, 7, 6, 5, 4},
        {19, 18, 17, 16, 15, 14, 13},
        {23, 21, 17, 12, 8, 3, 1},
        {22, 20, 15, 11, 6, 2, 0},
        {13, 14, 15, 16, 17, 18, 19},
        {4, 5, 6, 7, 8, 9, 10}
    };

    int[] center = {6, 7, 8, 11, 12, 15, 16, 17};
    int[] opposite = {5, 4, 7, 6, 1, 0, 3, 2};

    int[] a = new int[24];
    int[] path = new int[N];
    int max_depth;

    // 启发函数
    int f() {
        int[] sum = new int[4];
        for (int i = 0; i < 4; i ++)    sum[i] = 0;
        for (int i = 0; i < 8; i ++)    sum[a[center[i]]]++;

        int res = 0;
        for (int i = 1; i <= 3; i ++)   res = Math.max(res, sum[i]);
        return 8 - res;
    }

    boolean check() {
        for (int i = 1; i < 8; i ++)
            if (a[center[i]] != a[center[0]])
                return false;
        return true;
    }

    void operation(int x) {
        int t = a[op[x][0]];
        for (int i = 0; i < 6; i ++)    a[op[x][i]] = a[op[x][i + 1]];
        a[op[x][6]] = t;
    }

    boolean dfs(int depth, int last) {
        if (depth + f() > max_depth) return false;
        if (check())    return true;

        for (int i = 0; i < 8; i ++) {
            if (opposite[i] == last)    continue;
            path[depth] = i;
            operation(i);
            if (dfs(depth + 1, i))  return true;
            operation(opposite[i]);
        }
        return false;
    }

    void solve() throws Exception {

        while(true) {
            for (int i = 0; i < 24; i ++) {
                a[i] = nextInt();
                if (a[0] == 0)  return;
            }

            max_depth = 0;
            while(!dfs(0, -1)) max_depth ++;

            if (max_depth == 0) cout.print("No moves needed");
            for (int i = 0; i < max_depth; i ++)    
                cout.print((char)('A' + path[i]));
            cout.println("\n" + a[6]);
        }
    }

    public static void main(String[] args) throws Exception {
        Main cmd = new Main();
        cmd.solve();
        cmd.cout.close();
    }
}

Astar

基于dfs与启发函数算法

问题第K短路
题目地址https://www.acwing.com/problem/content/180/

启发函数

使用dijkstra算法求最短距离做估价函数

AC Code

import java.io.*;
import java.util.*;
import java.lang.*;
import java.math.*;

public class Main {

    Scanner sin = new Scanner(System.in);
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    StreamTokenizer cin = new StreamTokenizer(in);
    PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));

    final int N = 1010, mod = (int) (1e9 + 7), inf = 0x3f3f3f3f;

    int nextInt() throws Exception {
        cin.nextToken();
        return (int) cin.nval;
    }

    int n, m, k, S, T, K;

    class node {
        int pos, dis;

        node(int pos, int dis) {
            this.pos = pos;
            this.dis = dis;
        }
    }

    List<node>[] G = new List[N];
    List<node>[] RG = new List[N];

    void init() {
        for (int i = 0; i < N; i++) {
            G[i] = new ArrayList<>();
            RG[i] = new ArrayList<>();
            dis[i] = inf;
        }
    }


    void add(List<node>[] G, int a, int b, int c) {
        G[a].add(new node(b, c));
    }

    boolean[] st = new boolean[N];
    int[] dis = new int[N];

    // dijkstar 求估计函数
    void dijkstra() {

        Queue<node> q = new PriorityQueue<>((node a, node b) -> {
            return a.dis - b.dis;
        });
        q.add(new node(T, 0));
        dis[T] = 0; 

        while(q.size() > 0) {
            node t = q.poll();
            if (st[t.pos]) continue;
            st[t.pos] = true;

            for (node i : RG[t.pos]) {
                if (dis[i.pos] > dis[t.pos] + i.dis) {
                    dis[i.pos] = dis[t.pos] + i.dis;
                    q.add(new node(i.pos, dis[i.pos]));
                }
            }
        }
    }

    class edge {
        int pos, dis, f;
        edge(int pos, int dis, int f) {
            this.pos = pos;
            this.dis = dis;
            this.f = f;
        }
    }

    int[] cnt = new int[N];

    int Astar() {

        Queue<edge> q = new PriorityQueue<>((edge a, edge b) -> {
            return a.f - b.f;
        });

        q.add(new edge(S, 0, dis[S]));


        while (q.size() > 0) {
            edge t = q.poll();
            cnt[t.pos] ++;
            if (cnt[T] == K && t.pos == T)  return t.dis;
            if (cnt[t.pos] > K) continue;

            for (node i : G[t.pos]) {
                q.add(new edge(i.pos, t.dis + i.dis, t.dis + i.dis + dis[i.pos]));
            }

        }

        return -1;
    }

    void solve() throws Exception {

        init();

        String[] op = in.readLine().split(" ");
        n = Integer.parseInt(op[0]);
        m = Integer.parseInt(op[1]);
        while (m-- > 0) {
            op = in.readLine().split(" ");
            int a = Integer.parseInt(op[0]), b = Integer.parseInt(op[1]), c = Integer.parseInt(op[2]);
            add(G, a, b, c);
            add(RG, b, a, c);
        }
        op = in.readLine().split(" ");
        S = Integer.parseInt(op[0]);
        T = Integer.parseInt(op[1]);
        K = Integer.parseInt(op[2]);
        if (S == T) K++;

        dijkstra();
        if (dis[S] == inf) cout.println(-1);
        else cout.println(Astar());

    }

    public static void main(String[] args) throws Exception {
        Main cmd = new Main();
        cmd.solve();
        cmd.cout.close();
    }
}