搜索 D - 棋盘问题(dfs)

本文介绍了一种算法,用于解决在给定形状的棋盘上摆放棋子的问题,要求摆放时任意两个棋子不能位于同一行或同一列。通过递归深度优先搜索实现,并附带代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。

Input

输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。

Output

对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。

Sample Input

2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1

Sample Output

21


分析:整体感觉和排列组合差不多,限制条件(1.放旗子的位置不能在同一列,或者同一行)选中的时候标记同一列,也就是说那一列的位置不能再放棋子了。(2..棋子的数够了)当选中的棋子够了 方案书加一。 
             碰到一个位置有两种情况,一个是试一下能不能放,另一种情况是 直接跳过找下一个。


CODE:
#if 0 
#include<iostream>
#include<cstring>
using namespace std;
bool flag[10];
char mapi[10][10]; 
int  n , k; 
int cnt ,num;                                                   //num 已放棋子数 
void dfs(int i){        
    if(k==num){
        cnt++;                                                      //方案数加一
        return ;
    }
    if(i>=n)                                                       //超出棋盘界限
        return ;
    for(int j=0;j<n;j++)
        if(!flag[j] && mapi[i][j]=='#'){                      
            flag[j]=1;                                        //标记那一列
            num++;                                          //棋子数加一
            dfs(i+1);                                           //
            flag[j]=0;                               
            num--;
        }
    dfs(i+1);  
}

int main()
{
 while(cin>>n>>k&&n!=-1&&k!=-1)
 {
  cnt=num=0;
  memset(flag,false,sizeof(flag)); 
  for(int i=0; i<n; i++)
  for(int j=0; j<n; j++)
       cin>>mapi[i][j]; 
       
    dfs(0);    
  cout << cnt <<endl;
  
    } 
 }  
  
#endif 



<think>我们正在讨论深度优先搜索DFS)算法的适用问题类型。根据提供的引用材料,DFS是一种用于遍历或搜索树或图的算法,其核心思想是尽可能深地探索分支,直到无法继续深入时回溯。以下将详细分析DFS适用的问题类型,并给出相关示例。 ### 1. 图的连通性问题 DFS可用于检测图的连通性,例如判断图中两个节点是否连通,或统计连通分量的数量。 **示例**:无向图中连通分量的计数[^4] ```cpp void dfs(int v) { visited[v] = true; for (auto neighbor : graph[v]) { if (!visited[neighbor]) dfs(neighbor); } } int countComponents() { int count = 0; for (int i = 0; i < graph.size(); ++i) { if (!visited[i]) { dfs(i); count++; } } return count; } ``` ### 2. 路径搜索问题 当需要寻找从起点到终点的路径(尤其是需要回溯所有可能路径)时,DFS是自然的选择。 **示例**:迷宫路径探索[^3] ```cpp bool dfs(vector<vector<char>>& maze, int x, int y) { if (x < 0 || x >= maze.size() || y < 0 || y >= maze[0].size()) return false; if (maze[x][y] == 'E') return true; // 找到出口 if (maze[x][y] != '.' && maze[x][y] != 'S') return false; maze[x][y] = '#'; // 标记已访问 // 四个方向探索 if (dfs(maze, x+1, y) || dfs(maze, x-1, y) || dfs(maze, x, y+1) || dfs(maze, x, y-1)) return true; maze[x][y] = '.'; // 回溯(可选) return false; } ``` ### 3. 状态空间搜索(组合问题) 对于需要枚举所有可能组合的问题(如排列、子集生成),DFS能系统地遍历状态空间。 **示例**:生成所有子集[^1] ```cpp vector<vector<int>> subsets(vector<int>& nums) { vector<vector<int>> res; vector<int> path; dfs(nums, 0, path, res); return res; } void dfs(vector<int>& nums, int start, vector<int>& path, vector<vector<int>>& res) { res.push_back(path); // 记录当前状态 for (int i = start; i < nums.size(); ++i) { path.push_back(nums[i]); // 选择当前元素 dfs(nums, i+1, path, res); // 递归下一层 path.pop_back(); // 回溯 } } ``` ### 4. 拓扑排序 DFS可输出有向无环图(DAG)的拓扑排序序列,应用于任务调度等场景。 **示例**:课程安排顺序[^2] ```cpp vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) { vector<vector<int>> graph(numCourses); vector<int> indegree(numCourses, 0); // 建图 for (auto& p : prerequisites) { graph[p[1]].push_back(p[0]); indegree[p[0]]++; } vector<int> order; for (int i = 0; i < numCourses; ++i) if (indegree[i] == 0) dfs(i, graph, indegree, order); return order.size() == numCourses ? order : vector<int>(); } void dfs(int u, vector<vector<int>>& graph, vector<int>& indegree, vector<int>& order) { order.push_back(u); indegree[u] = -1; // 标记已访问 for (int v : graph[u]) { indegree[v]--; if (indegree[v] == 0) dfs(v, graph, indegree, order); } } ``` ### 5. 环检测 DFS可高效检测图中是否存在环(通过追踪递归栈中的节点)。 **示例**:有向图环检测[^4] ```cpp bool hasCycle(int v) { if (visited[v] == 1) return true; // 发现环 if (visited[v] == 2) return false; // 已处理完毕 visited[v] = 1; // 标记为访问中 for (int neighbor : graph[v]) { if (hasCycle(neighbor)) return true; } visited[v] = 2; // 标记为已访问 return false; } ``` ### 6. 回溯法求解约束满足问题问题需要在满足约束条件下搜索解(如八皇后、数独)时,DFS+回溯是经典解法。 **示例**:N皇后问题[^3] ```cpp void solveNQueens(int row, vector<string>& board, vector<vector<string>>& res) { if (row == board.size()) { res.push_back(board); return; } for (int col = 0; col < board.size(); ++col) { if (isValid(board, row, col)) { board[row][col] = 'Q'; solveNQueens(row+1, board, res); // 下一行 board[row][col] = '.'; // 回溯 } } } ``` ### 7. 强连通分量(SCC)分解 Kosaraju算法或Tarjan算法均基于DFS实现有向图的强连通分量分解。 **示例**:Tarjan算法伪代码[^2] ``` tarjan(u) { dfn[u] = low[u] = ++index stack.push(u) for each (u, v) in E { if (v not visited) tarjan(v) low[u] = min(low[u], low[v]) else if (v in stack) low[u] = min(low[u], dfn[v]) } if (dfn[u] == low[u]) { // 发现SCC repeat v = stack.pop print v until (u == v) } } ``` ### ⚠️ DFS适用性的关键判断依据 1. **解空间具有树/图结构**:问题可建模为节点与边的集合 2. **需要完整遍历或路径搜索**:而非仅求最优解(此时BFS或Dijkstra更高效) 3. **状态可回溯**:能够撤销选择(显式/隐式回溯) 4. **深度优先特性匹配**:如“尝试所有组合直到满足条件”类问题 ### ⛔ 不适用DFS的场景 | 场景 | 原因 | 替代方案 | |---------------------|--------------------------|------------------| | 无权图最短路径 | DFS可能绕远路 | BFS | | 状态空间过大 | 递归深度可能导致栈溢出 | 迭代加深DFS(IDDFS) | | 最优解靠近根节点 | DFS可能浪费时间在深层 | BFS或启发式搜索 | --- ### 💎 总结:DFS核心应用领域 - **组合枚举**:子集、排列、组合数 - **图论基础**:连通性、环检测、拓扑排序 - **回溯法经典问题**:八皇后、数独、迷宫 - **树形结构操作**:前/中/后序遍历、路径和 --- §§ 相关问题 §§ 1. DFS与BFS在时间复杂度上有何差异? 2. 如何用DFS判断二分图? 3. 迭代加深深度优先搜索(IDDFS)解决了DFS的哪些缺陷? 4. 在哪些情况下DFS会陷入无限循环?如何避免? 5. 如何优化DFS解决大规模状态空间问题(如剪枝技术)?[^2]</think>### 深度优先搜索DFS)适用问题类型详解 DFS 算法特别适合解决以下六类问题,其核心特点是**需要探索所有可能路径**或**处理树/图的结构关系**: --- #### 1. **连通性问题** 当需要判断图中节点的连通性或统计连通分量时,DFS 是最直接的选择。 **典型场景**: - 检测无向图的连通分量数量 [^1] - 判断两点间是否存在路径 - 社交网络中寻找好友关系链 **示例代码**: ```cpp void dfs(int v, vector<bool>& visited, vector<vector<int>>& graph) { visited[v] = true; for (int neighbor : graph[v]) { if (!visited[neighbor]) dfs(neighbor, visited, graph); // 递归探索未访问邻居 } } ``` --- #### 2. **路径搜索问题** 需要枚举从起点到终点的所有可能路径时,DFS 天然支持回溯机制。 **典型场景**: - 迷宫求解(含死胡同处理)[^3] - 棋盘类游戏走法计算(如八皇后) - 语法树解析(编译原理) **关键特性**: - 通过回溯实现路径记录 - 可结合剪枝优化效率(如:记忆化搜索)[^2] --- #### 3. **状态空间搜索** 当问题可建模为**状态转移**且状态数量有限时,DFS 能系统遍历所有状态。 **典型场景**: - 子集生成(如 `[1,2,3]` → `[], [1], [2], [3], [1,2]...`) - 排列组合问题(全排列、组合数) - 数独求解 **数学表示**: 状态空间大小常为阶乘级 $O(n!)$ 或指数级 $O(2^n)$ --- #### 4. **拓扑排序** 对有向无环图(DAG)进行线性排序,DFS 可自然实现后序遍历反转。 **典型场景**: - 课程依赖关系排序 - 任务调度序列 - 软件包依赖解析 **算法特性**: ```mermaid graph LR A[算法导论] --> B[数据结构] B --> C[C++程序设计] C --> D[毕业设计] DFS输出:A → B → C → D ``` --- #### 5. **环检测与图分析** DFS 能高效检测图中环路,并分析图的结构特性。 **典型场景**: - 死锁检测(操作系统) - 判断有向图的强连通分量 [^4] - 金融交易环路分析 **实现关键**: ```cpp bool hasCycle(int v, vector<int>& color) { color[v] = 1; // 标记为"访问中" for (int neighbor : graph[v]) { if (color[neighbor] == 0) { // 未访问 if (hasCycle(neighbor, color)) return true; } else if (color[neighbor] == 1) { // 遇到访问中节点→有环 return true; } } color[v] = 2; // 标记为"已访问" return false; } ``` --- #### 6. **回溯法求解约束问题** 当问题需要在搜索过程中动态剪枝时,DFS + 回溯是黄金组合。 **典型场景**: - N皇后问题 - 0-1背包问题 - 正则表达式匹配 **核心流程**: 1. 做出选择 2. 递归进入下一层 3. 撤销选择(回溯) 4. 用约束条件剪枝无效分支 [^2] --- ### ⚠️ DFS 不适用的情况 | **问题类型** | **原因** | **推荐算法** | |----------------------|--------------------------|---------------| | 最短路径(无权图) | DFS 可能绕远路 | BFS | | 最短路径(带权图) | DFS 无法保证最优性 | Dijkstra | | 状态空间过大 | 递归深度爆炸 | 迭代加深搜索 | | 实时响应需求 | 最坏时间复杂度高 ($O(n!)$) | 启发式搜索 | --- ### 💡 选择 DFS 的关键判断依据 1. 是否需要**遍历所有解**(而非单一最优解) 2. 问题结构是否呈现**树形/图状依赖** 3. 能否通过**剪枝**有效减少搜索空间 [^2] 4. 数据规模是否允许**递归深度**(栈空间限制) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值