算法知识图谱总览 / 搜索算法族 / IDA*算法

IDA*算法

P1intermediatestandardG-A-14

What · 是什么

迭代加深+A*剪枝,限制路径成本而非深度

IDA*(Iterative Deepening A*)是把迭代加深与 A* 结合的搜索算法:每次迭代做一次限阈值的 DFS,阈值是路径成本估计 f=g+h 而非深度。初始阈值取 h(起点);DFS 中若某分支 f>阈值则剪掉并记录其最小超限值,迭代结束后阈值更新为该最小值继续下一轮。它结合了 A* 的启发剪枝与迭代加深的线性空间,不需要判重和排序,空间需求极小,适合 15数码、埃及分数等状态爆炸但路径不太长的问题。

Why · 为什么

直觉 · Intuition

A* 用优先队列按 f 排序,要存下整个 open 表(内存大)。IDA* 改用 DFS 并只限制'这条路径的 f 值上限':超出阈值就掉头,一轮搜完若没找到,把阈值抬高到刚才所有被卡住分支的最小 f,再来一轮。像'水深探测':先看能走到多深(f 上限),够不到就加深一点。

原理 · Principle

一次迭代对 f=g+h>C 的分支剪枝。因 h 可采纳(乐观),若存在长度≤C 的解,本轮必找到;否则记录所有被剪分支的 min(f),下一轮阈值设为该值,保证阈值单调上升且不漏解(OI Wiki 的 Search 子算法正是返回未探索节点的最小 f)。与 A* 相比优点:无需判重/排序、DFS 空间 O(路径长度);缺点:每次放宽阈值都要从头重搜(重复搜索)。埃及分数题中深度无上界、每层无限分支,只能用 IDA* 类思想,并用剩余分数下界做剪枝(h=(a/b - c/d)/(1/(e+1)))。

历史 · History

IDA* 由 Richard Korf 于 1985 年在《深度优先迭代加深:内存有限下的最优搜索》中提出,是 A* 与迭代加深的经典合成,长期是 15数码等滑动谜题的最优求解标准算法,启发式搜索领域里程碑工作。

How · 实现

伪代码

def IDA*(start):
    limit = h(start)
    while True:
        t = DFS(start, g=0, limit)
        if t == FOUND: return 解
        if t == INF: return 无解
        limit = t   # 阈值提升到最小超限值

def DFS(u, g, limit):
    f = g + h(u)
    if f > limit: return f        # 返回超限值
    if u == 目标: return FOUND
    nxt_min = INF
    for v in children(u):
        t = DFS(v, g+cost(u,v), limit)
        if t == FOUND: return FOUND
        nxt_min = min(nxt_min, t)
    return nxt_min

Python 实现

GOAL = "123804765"


def manhattan(state):
    """曼哈顿距离(可采纳启发)"""
    d = 0
    for i, ch in enumerate(state):
        if ch != '0':
            gi = GOAL.index(ch)
            d += abs(i // 3 - gi // 3) + abs(i % 3 - gi % 3)
    return d


def neighbors(state):
    i = state.index('0')
    r, c = i // 3, i % 3
    for dr, dc in ((-1, 0), (1, 0), (0, -1), (0, 1)):
        nr, nc = r + dr, c + dc
        if 0 <= nr < 3 and 0 <= nc < 3:
            j = nr * 3 + nc
            lst = list(state)
            lst[i], lst[j] = lst[j], lst[i]
            yield ''.join(lst)


def solvable(state):
    s = [int(c) for c in state if c != '0']
    inv = sum(1 for i in range(len(s)) for j in range(i + 1, len(s)) if s[i] > s[j])
    return (inv - 7) % 2 == 0  # 目标123804765逆序数为7(奇)


def ida_star(start):
    """IDA* 解8数码: 返回最少步数; 不可解返回 None"""
    if not solvable(start):
        return None
    limit = manhattan(start)
    found = [None]

    def dfs(u, g, prev):
        f = g + manhattan(u)
        if f > limit:
            return f          # 返回超限值用于提升阈值
        if u == GOAL:
            found[0] = g
            return -1         # FOUND 哨兵
        nxt_min = float('inf')
        for v in neighbors(u):
            if v == prev:
                continue      # 不走回头路(8数码中无意义)
            t = dfs(v, g + 1, u)
            if t == -1:
                return -1
            nxt_min = min(nxt_min, t)
        return nxt_min

    while True:
        t = dfs(start, 0, None)
        if t == -1:
            return found[0]
        if t == float('inf'):
            return None
        limit = t


if __name__ == "__main__":
    assert ida_star(GOAL) == 0
    r = ida_star("283104765")
    assert r is not None and r >= 0
    assert ida_star("123804756") is None  # 不可解

    # 与 BFS 对拍随机可解状态
    import collections, random
    def bfs_ans(start, maxd=25):
        if start == GOAL:
            return 0
        q = collections.deque([(start, 0)])
        seen = {start}
        while q:
            u, d = q.popleft()
            if d >= maxd:
                continue
            for v in neighbors(u):
                if v not in seen:
                    if v == GOAL:
                        return d + 1
                    seen.add(v)
                    q.append((v, d + 1))
        return None

    random.seed(7)
    tested = 0
    while tested < 30:
        s = GOAL
        for _ in range(random.randint(1, 10)):
            nb = list(neighbors(s))
            s = random.choice(nb)
        b = bfs_ans(s)
        if b is None:
            continue
        a = ida_star(s)
        assert a == b, (s, a, b)
        tested += 1
    print(f"idastar all tests passed ({tested} BFS-cross-validated)")
PyPy 提示IDA* 递归+字符串状态在 PyPy 下可行但 15数码会慢;注意深递归 setrecursionlimit。每轮重搜开销大,阈值增量设计(每次 +1 或跳增)影响性能。可保存状态为整数编码加速哈希。
时间复杂度与 A* 同阶(取决于启发质量),最坏 O(b^d) 指数;每轮阈值重搜带来常数开销
空间复杂度O(路径长度) DFS 栈,远优于 A* 的 open 表
注意 · 局限阈值每轮重搜造成重复工作;需要可采纳启发否则无最优性;路径很长时迭代轮数多;状态空间巨大(如15数码难例)仍可能超时。

易错点

例题

OJ题号难度考点链接
LuoguP17634埃及分数:深度无上界+每层无限分支,IDA*+下界剪枝经典https://www.luogu.com.cn/problem/P1763
UVA13434The Rotation Game:旋转游戏,IDA*典型应用https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=4089
步骤迷你图(节点 = 算法步骤 · 边 = 流程 · 绿色 = 起始步骤)
上游 · 本知识点依赖(前置/变体/组合/共现)

信源

✓ = 已核验 · △ = 未核验