Artificial Intelligence: A Modern Approach Chapter 3 Classic Search Part 2

Preface

I think there are two shortcomings in the specific search strategy in this book. One is that the pseudo code is difficult to understand, and the other is that there are no specific examples to help understand. Therefore, this article uses python pseudocode instead, and collects some examples for your reference.

Artificial Intelligence: A Modern Approach Chapter 3 Classic Search Part 2

3.4 Search without information

3.4.1 Breadth-first search

def BFS(graph, start, goal):
    queue = []  # 初始化一个队列
    visited = set()  # 初始化一个已访问节点的集合

    queue.append([start])  # 将起始节点的路径加入队列

    while queue:  # 当队列不为空时
        path = queue.pop(0)  # 从队列中取出一个路径
        node = path[-1]  # 获取路径中的最后一个节点

        if node not in visited:  # 如果该节点没有被访问过
            if node == goal:  # 如果该节点是目标节点
                return path  # 返回该路径

            visited.add(node)  # 将该节点加入已访问节点的集合

            for neighbor in graph[node]:  # 遍历当前节点的所有邻居节点
                new_path = list(path)  # 创建一个新路径
                new_path.append(neighbor)  # 将邻居节点加入新路径
                queue.append(new_path)  # 将新路径加入队列

    return "无解"  # 如果没有从起始节点到目标节点的路径,则返回"无解"

Performance analysis

  • Completeness: If b is finite, the algorithm is complete
  • Optimality: When the action dissipation values ​​are the same, the algorithm is optimal
  • Time complexity
    The target test is performed when the nodes are expanded and generated. The number of nodes is b+b2+b3+…+bd=O(b^d)
    Perform target testing when a node is selected for expansion. The number of nodes is O(b^(d+1))
  • Space complexity
    Exploration set O(b^(d-1)) nodes, edge set O(b^d) nodes

Maze problem
A maze, in which 1 represents the wall and 0 represents the path that can be walked. You can only walk horizontally or vertically, not diagonally. It requires programming to find Find the shortest route from the upper left corner to the lower right corner.

Note: Take advantage of the special optimality of bfs

void bfs () {
    
    
    queue <PII> q;
    q.push ({
    
    n,n});
    memset (pre,-1,sizeof (pre));
    pre[n][n] = {
    
    1,1};
    while (!q.empty ()) {
    
    
        PII t = q.front ();
        q.pop ();
        for (int i = 0;i < 4;i++) {
    
    
            int a = t.x + dx[i],b = t.y + dy[i];
            if (a < 1 || a > n || b < 1 || b > n) continue;
            if (g[a][b]) continue;
            if (pre[a][b].x != -1) continue;
            q.push ({
    
    a,b});
            pre[a][b] = t;
        }
    }
}

3.4.2 Consistent cost search

import heapq  # 用于实现优先队列

def UCS(graph, start, goal):
    queue = []  # 初始化优先队列
    visited = set()  # 初始化已访问节点集合

    # 将起始节点的路径以及到达该节点的总成本加入队列
    # (路径成本,路径)作为元组存入队列,路径成本作为优先级
    heapq.heappush(queue, (0, [start]))

    while queue:  # 当队列不为空时
        (cost, path) = heapq.heappop(queue)  # 取出当前成本最小的路径
        node = path[-1]  # 获取路径中的最后一个节点

        if node not in visited:  # 如果该节点没有被访问过
            if node == goal:  # 如果该节点是目标节点
                return path  # 返回该路径

            visited.add(node)  # 将该节点加入已访问节点的集合

            for neighbor in graph[node]:  # 遍历当前节点的所有邻居节点
                new_cost = cost + graph[node][neighbor]  # 计算新路径的总成本
                new_path = list(path)  # 创建一个新路径
                new_path.append(neighbor)  # 将邻居节点加入新路径
                heapq.heappush(queue, (new_cost, new_path))  # 将新路径加入队列

    return "无解"  # 如果没有从起始节点到目标节点的路径,则返回"无解"

Performance analysis

  • If there is an action NOOP with a dissipation value of 0, it may fall into an infinite loop and the algorithm is incomplete
    If b is limited and the dissipation value of the action is not 0, then the algorithm complete
  • Optimality: optimal, C* represents the dissipation value of the optimal solution path
  • Time complexity: consistent cost search > breadth first search

When the action dissipation value is the same, except for the different time points of the target test, the consistent cost search degenerates into a breadth-first search.
Please add image description

Single source shortest path problem
There is an undirected graph with n points and m edges. Find the length of the shortest path from s to t.

Note: Use optimality to solve


int dijkstra()
{
    
    
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({
    
    0, 1});

    while (heap.size())
    {
    
    
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
    
    
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
    
    
                dist[j] = dist[ver] + w[i];
                heap.push({
    
    dist[j], j});
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

3.4.3 Depth-first search

def DFS(graph, start, goal):
    stack = []  # 初始化一个栈
    visited = set()  # 初始化一个已访问节点的集合

    stack.append([start])  # 将起始节点的路径加入栈

    while stack:  # 当栈不为空时
        path = stack.pop()  # 从栈中取出一个路径
        node = path[-1]  # 获取路径中的最后一个节点

        if node not in visited:  # 如果该节点没有被访问过
            if node == goal:  # 如果该节点是目标节点
                return path  # 返回该路径

            visited.add(node)  # 将该节点加入已访问节点的集合

            for neighbor in graph[node]:  # 遍历当前节点的所有邻居节点
                new_path = list(path)  # 创建一个新路径
                new_path.append(neighbor)  # 将邻居节点加入新路径
                stack.append(new_path)  # 将新路径加入栈

    return "无解"  # 如果找不到路径,返回“无解”

Performance analysis

  • The time complexity can generate up to O(b^m) nodes, which may be larger than the size of the state space, and m may be infinite.
  • Space complexity (advantage)
    When all the child nodes of a node are explored, the node can be deleted and only one line from the root node to the leaf node is stored. The path of a point, and all unexpanded sibling nodes of each node on the path, O(bm)

A variant of depth-first search - backtracking search (generally DFS is implemented recursively)

nqueen problem
Please add image description

void dfs(int u)
{
    
    
    if (u == n)
    {
    
    
        for (int i = 0; i < n; i ++ ) puts(g[i]);
        puts("");
        return;
    }

    for (int i = 0; i < n; i ++ )
        if (!col[i] && !dg[u + i] && !udg[n - u + i])
        {
    
    
            g[u][i] = 'Q';
            col[i] = dg[u + i] = udg[n - u + i] = true;
            dfs(u + 1);
            col[i] = dg[u + i] = udg[n - u + i] = false;
            g[u][i] = '.';
        }
}

3.4.4 Comparison between DFS BFS UCS

search algorithm Search method Optimality
Depth First Search (DFS) Using the last-in-first-out feature of the stack, starting from the root node, search the branches of the graph as deeply as possible until no more nodes can be accessed from the current branch, then backtrack to the previous node and continue searching for the next branch. There is no guarantee that the shortest path will be found.
Breadth First Search (BFS) Using the first-in-first-out feature of the queue, starting from the root node, all neighbor nodes are visited first, and then the neighbor's neighbors are visited. It always searches the node closest to the root node first, so it can find the shortest path (assuming all edges have the same weight).
Consistency Search (UCS) A priority queue is used to store nodes to be visited, and the priority of a node is determined based on the total weight (cost) of the path from the root node to that node. The node with the smallest path cost is always searched first, so it can find the shortest path even if the edge weights are not the same.

3.4.5 Depth-limited search and iterative deepening

Iterative deepening and depth-limited search are often used together, so this article will explain them together.

Depth restricted search

  • Added depth limit limit (abbreviated as l): nodes with depth l are treated as leaf nodes
  • Completeness: When l<d, the algorithm is incomplete
  • Optimality: When l>d, the algorithm is non-optimal
  • Time complexity: O(b^l)
  • Space complexity: O(bl)

When l=∞, depth-limited search degenerates into depth-first search.

Depth-first search with iterative deepening

  • Completeness: If b is finite, the algorithm is complete
  • Optimality:
    If the path dissipation is a non-decreasing function of the node depth, the algorithm is optimal
    When the action dissipation values ​​are equal At the same time, the algorithm is optimal
  • Time complexity: db+(d-1)b2+(d-2)b3+…+1bd=O(b^d)
  • Space complexity: O(b^d)
def IDS(graph, start, goal):
    depth = 0  # 初始化深度限制

    while True:  # 持续搜索,直到找到目标
        result = DLS(graph, start, goal, depth)  # 进行深度有限搜索
        if result != "无解":  # 如果找到了解
            return result  # 返回结果
        depth += 1  # 增加深度限制


def DLS(graph, node, goal, depth):
    if depth == 0 and node == goal:  # 如果达到深度限制且找到目标
        return [node]  # 返回包含当前节点的路径
    elif depth > 0:  # 如果还没有达到深度限制
        for neighbor in graph[node]:  # 遍历当前节点的所有邻居节点
            result = DLS(graph, neighbor, goal, depth - 1)  # 对邻居节点进行深度有限搜索
            if result != "无解":  # 如果找到了解
                return [node] + result  # 返回包含当前节点和找到的路径的路径

    return "无解"  # 如果没有找到解,返回"无解"

Egyptian fraction problem
Divide a fraction into several different simplest fractions with a numerator of 1. It is required that the denominator of the divided fractions cannot appear

int ok = 0;
for (maxd = 1;; maxd++) {
    
    
    memset(ans, -1, sizeof(ans));
    if (dfs(0, get_first(a, b), a, b)) {
    
    
        ok = 1;
        break;
    }
}

// 如果当前解v比目前最优解ans更优,更新ans
bool better(int d) {
    
    
    for (int i = d; i >= 0; i--) {
    
    
        if (v[i] != ans[i]) {
    
    
            return ans[i] == -1 || v[i] < ans[i];
        }
    }
    return false;
}

// 当前深度为d,分母不能小于from,分数之和恰好为aa/bb
bool dfs(int d, int from, LL aa, LL bb) {
    
    
    if (d == maxd) {
    
    
        if (bb % aa) return false;  // aa/bb必须是埃及分数
        v[d] = bb / aa;
        if (better(d)) memcpy(ans, v, sizeof(LL) * (d + 1));
        return true;
    }

    bool ok = false;
    from = max(from, get_first(aa, bb));  // 枚举的起点
    for (int i = from;; i++) {
    
    
        // 剪枝:如果剩下的maxd+1-d个分数全部都是1/i,加起来仍然不超过aa/bb,则无解
        if (bb * (maxd + 1 - d) <= i * aa) break;
        v[d] = i;
        // 计算aa/bb - 1/i,设结果为a2/b2
        LL b2 = bb * i;
        LL a2 = aa * i - bb;
        LL g = gcd(a2, b2);  // 以便约分
        if (dfs(d + 1, i + 1, a2 / g, b2 / g)) ok = true;
    }
    return ok;
}

3.4.6 Bidirectional search

  • Two searches run simultaneously: one searches forward from the initial state and the other searches backward from the target state until the states meet
  • Target test: Check if two searched edge sets intersect

Performance analysis

  • Optimality: non-optimal
  • Time complexity of bidirectional breadth-first search: O(b^(d/2))
  • Space complexity of bidirectional breadth-first search: O(b^(d/2))
  • Difficulty: It requires an algorithm to calculate the parent node and a clear target state

3.4.7 Comparison of information-free search strategies

Insert image description here

Summarize

This article talks about information-free algorithms in specific searches, and compares the performance of information-free search strategies. I hope you can think carefully about the relationships and differences between them and think deeply about the examples I have collected.
The next article will introduce information search algorithms and heuristic functions. Don't go away, come back soon, everyone, stay tuned.

Guess you like

Origin blog.csdn.net/weixin_61197809/article/details/134245772