算法笔记 / A LEAF IN THE GARDEN

搜索基础

算法笔记 · 算法理论 / 基础算法 / 搜索。来自 Daily Learning 的学习笔记。

深度优先搜索(DFS)

以遍历单条路径上的所有节点为优先,搜索所有可能的路径。

  • 沿一条状态路径转移,直到没有下一个状态
  • 再退回访问前一状态的其他可能路径。

实现

  • 用栈管理候选状态
  • 递归栈

代码示例

递归栈实现

void dfs(int u){
    // 递归出口(即上一个节点为叶子节点)
    if(!u) return;
    // 访问当前节点
    cout << node[u].val << " ";
    // 递归访问子节点
    for(int v: node[u].next) dfs(v);
}

栈实现

void stack_dfs(int root){
    // 栈中存储待访问的节点,初始时只有根节点
    stack<int> st;
    st.push(root);
    // 还有待访问节点,继续访问
    while(!st.empty()){
        // 取栈顶为当前节点,并弹出
        int u = st.top();
        st.pop();
        // 访问当前节点
        cout << node[u].val << " ";
        // 将子节点压入栈(后进先出,倒序保证访问顺序与递归一致)
        for(int v: node[u].next) if(v) st.push(v);
    }
}

广度优先搜索(BFS)

以遍历单个节点上的所有路径为优先,搜索所有可能的路径。

  • 先访问某状态的所有可能路径
  • 再依次路径上的下一个状态。

实现

  • 队列管理候选状态
  • 优先对列(有特殊要求时)

代码示例

void bfs(int root){
    // 队列中存储待访问的节点,初始时只有根节点
    queue<int> q;
    q.push(root);
    // 还有待访问节点,继续访问
    while(!q.empty()){
        // 取队首为当前节点,并弹出
        int u = q.front();
        q.pop();
        // 访问当前节点
        cout << node[u].val << " ";
        // 将子节点压入队列(先进先出)
        for(int v: node[u].next) if(v) q.push(v);
    }
}

回溯法(Backtracking)

系统地搜索解空间,用于寻找满足特定条件的解(或所有解)

  • 选择可能路径
  • 继续搜索
  • 回退(撤销选择)
  • 继续搜索下一个选择

实现

  • 深度优先搜索搭配递归栈

代码示例

int maxlen = 3; // 目标解的长度
int selection = 5; // 选项的范围
void backtrack(vector<int> &path){
    // 满足条件,处理当前解
    if(path.size() == maxlen){
        for(int num : path) cout << num << ' ';
        cout << endl;
        return;
    }
    // 遍历所有选项
    for(int i = 1; i <= selection; i++){
        // 做选择
        path.push_back(i);
        // 继续搜索
        backtrack(path);
        //* 回退(撤销选择)
        path.pop_back();
    }
}

剪枝法(Pruning)

在搜索中,通过剪枝操作来减少搜索树的大小,以提高搜索效率。

实现

  • 可行性剪枝:当前状态已不可能达到目标
  • 最优性剪枝:当前状态已不可能比之前找到的解更优
  • 去重剪枝(记忆化):已经搜索过的状态不再搜索
  • 顺序剪枝:以某种顺序搜索,使其他剪枝效果最大化
  • 其他:估价函数、贪心初始解等

代码示例

洛谷P1433

double ans = -1;
int n;
vector<bool> vis;// 标记已访问的节点
vector<double> minDis;
// 贪心初始解,用于最优性剪枝(省略)
void greedy(){}
// DFS + 剪枝
void dfs(int pos, int num, double d){
    // 可行性剪枝:递归正常结束
    if(num == n) {
        ans = (ans > 0) ? min(ans, d) : d;
        return;
    }
    // 最优性剪枝:当前距离已经不可能比ans更优
    if(d >= ans && ans > 0) return;
    double minD = 0;
    // 最优性剪枝:即使接下来选最短路,距离也不可能比ans更优
    for(int i = 1;i <= n;i++){
        if(vis[i]) continue;
        if(i == pos) continue;
        minD += minDis[i];
    }
    if(minD + d >= ans && ans > 0) return;
    // 顺序剪枝:优先搜索距离较近的节点,使最优性剪枝剪掉更多分支
    vector<pair<double, int>> order;
    for(int i = 1;i <= n;i++) 
        if(!vis[i]) order.push_back({cut[pos][i], i});
    sort(order.begin(), order.end());
    // dfs主体
    for(auto [_, i]: order){
        // 去重剪枝:已经搜索过的节点不再搜索
        if(vis[i]) continue;
        if(i == pos) continue;
        // 回溯
        vis[i] = 1;
        dfs(i, num + 1, d + cut[pos][i]);
        vis[i] = 0;
    }
}
int main(){
    // 输入(省略)
    // 先用贪心算法得到初始解
    greedy();
    // DFS + 剪枝搜索最优解
    fill(vis.begin(), vis.end(), 0);
    dfs(0, 0, 0);
    cout << ans;
    return 0;
}