C++ 进阶知识点详细教程 - 第3部分

C++ 进阶知识点详细教程 - 第3部分

11. 搜索算法

11.1 深度优先搜索(DFS)

11.1.1 基本概念

DFS是一种递归的搜索算法,沿着一条路径深入到底,然后回溯。

基本模板

void dfs(int state) {
    // 1. 终止条件
    if (满足条件) {
        处理结果;
        return;
    }
    
    // 2. 剪枝
    if (不满足要求) {
        return;
    }
    
    // 3. 尝试所有可能
    for (每个可能的选择) {
        做选择;
        dfs(下一个状态);
        撤销选择;  // 回溯
    }
}

11.1.2 全排列问题

题目描述:给定一个正整数n,输出1到n的所有排列。

输入:一个正整数n (1 ≤ n ≤ 8)
输出:所有可能的排列,每行一个排列

示例

输入:3
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
#include <iostream>
using namespace std;

int n;
int arr[10];
bool used[10];

void dfs(int depth) {
    // 终止条件:已经选择了n个数字
    if (depth == n) {
        for (int i = 0; i < n; i++) {
            cout << arr[i] << " ";
        }
        cout << endl;
        return;
    }
    
    // 尝试每个数字
    for (int i = 1; i <= n; i++) {
        if (!used[i]) {
            arr[depth] = i;     // 做选择
            used[i] = true;     // 标记已使用
            dfs(depth + 1);     // 递归到下一层
            used[i] = false;    // 撤销选择(回溯)
        }
    }
}

int main() {
    cout << "请输入n: ";
    cin >> n;
    cout << "所有排列:" << endl;
    dfs(0);
    return 0;
}

11.1.3 组合问题

题目描述:从1到n这n个数中选择k个数的所有组合。

输入:两个正整数n和k (1 ≤ k ≤ n ≤ 20)
输出:所有可能的组合,每行一个组合

示例

输入:4 2
输出:
1 2
1 3
1 4
2 3
2 4
3 4
#include <iostream>
using namespace std;

int n, k;
int path[25];

void dfs(int start, int depth) {
    // 终止条件:已经选择了k个数
    if (depth == k) {
        for (int i = 0; i < k; i++) {
            cout << path[i] << " ";
        }
        cout << endl;
        return;
    }
    
    // 从start开始选择,保证组合的有序性
    for (int i = start; i <= n; i++) {
        path[depth] = i;        // 选择当前数字
        dfs(i + 1, depth + 1);  // 下一个从i+1开始,避免重复
    }
}

int main() {
    cout << "请输入n和k: ";
    cin >> n >> k;
    cout << "所有组合:" << endl;
    dfs(1, 0);
    return 0;
}

11.1.4 迷宫问题

题目描述:给定一个n×m的迷宫,'.'表示可以通过,'#'表示墙壁。判断是否能从左上角(0,0)走到右下角(n-1,m-1)。

输入

  • 第一行:两个整数n和m (1 ≤ n,m ≤ 100)
  • 接下来n行:每行m个字符,'.'或'#'

输出:如果能到达输出"找到路径",否则输出"无路径"

示例

输入:
4 4
....
.##.
....
###.

输出:找到路径
#include <iostream>
using namespace std;

int n, m;
char maze[105][105];
bool visited[105][105];
int dx[] = {-1, 1, 0, 0};  // 上下左右
int dy[] = {0, 0, -1, 1};

bool dfs(int x, int y) {
    // 到达终点
    if (x == n - 1 && y == m - 1) {
        return true;
    }
    
    visited[x][y] = true;  // 标记当前位置已访问
    
    // 尝试四个方向
    for (int i = 0; i < 4; i++) {
        int nx = x + dx[i];
        int ny = y + dy[i];
        
        // 检查边界、障碍和是否已访问
        if (nx >= 0 && nx < n && ny >= 0 && ny < m 
            && maze[nx][ny] != '#' && !visited[nx][ny]) {
            if (dfs(nx, ny)) {
                return true;
            }
        }
    }
    
    return false;
}

int main() {
    cout << "请输入迷宫大小n m: ";
    cin >> n >> m;
    cout << "请输入迷宫('.表示通路,#表示墙):" << endl;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> maze[i][j];
        }
    }
    
    if (dfs(0, 0)) {
        cout << "找到路径" << endl;
    } else {
        cout << "无路径" << endl;
    }
    
    return 0;
}

11.1.5 N皇后问题

题目描述:在n×n的棋盘上放置n个皇后,使得任意两个皇后都不能相互攻击(即不在同一行、同一列或同一对角线上)。

输入:一个正整数n (1 ≤ n ≤ 10)
输出:所有可能的解决方案和总数

示例

输入:4
输出:
.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..

总共 2 种解法
#include <iostream>
using namespace std;

int n;
int board[20];  // board[i]表示第i行皇后在第几列
int cnt = 0;

bool isValid(int row, int col) {
    for (int i = 0; i < row; i++) {
        // 检查同列或同对角线
        if (board[i] == col || 
            abs(board[i] - col) == abs(i - row)) {
            return false;
        }
    }
    return true;
}

void dfs(int row) {
    if (row == n) {
        cnt++;
        // 输出当前解
        cout << "解法 " << cnt << ":" << endl;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                cout << (board[i] == j ? "Q" : ".");
            }
            cout << endl;
        }
        cout << endl;
        return;
    }
    
    // 尝试在当前行的每一列放置皇后
    for (int col = 0; col < n; col++) {
        if (isValid(row, col)) {
            board[row] = col;    // 放置皇后
            dfs(row + 1);        // 递归到下一行
        }
    }
}

int main() {
    cout << "请输入棋盘大小n: ";
    cin >> n;
    cout << "N皇后问题的所有解:" << endl;
    dfs(0);
    cout << "总共 " << cnt << " 种解法" << endl;
    return 0;
}

11.2 广度优先搜索(BFS)

11.2.1 基本概念

BFS使用队列,一层一层地搜索,适合求最短路径。

基本模板

#include <queue>
void bfs(int start) {
    queue<int> q;
    q.push(start);
    visited[start] = true;
    
    while (!q.empty()) {
        int curr = q.front();
        q.pop();
        
        // 处理当前节点
        
        // 扩展相邻节点
        for (每个相邻节点 next) {
            if (!visited[next]) {
                visited[next] = true;
                q.push(next);
            }
        }
    }
}

11.2.2 迷宫最短路径

题目描述:给定一个n×m的迷宫,求从左上角(0,0)到右下角(n-1,m-1)的最短路径长度。

输入

  • 第一行:两个整数n和m (1 ≤ n,m ≤ 100)
  • 接下来n行:每行m个字符,'.'表示可通过,'#'表示墙壁

输出:最短路径长度,如果无法到达输出-1

示例

输入:
4 4
....
.##.
....
###.

输出:最短路径长度: 6
#include <iostream>
#include <queue>
using namespace std;

struct Point {
    int x, y, dist;
};

int n, m;
char maze[105][105];
bool visited[105][105];
int dx[] = {-1, 1, 0, 0};  // 上下左右
int dy[] = {0, 0, -1, 1};

int bfs() {
    queue<Point> q;
    q.push({0, 0, 0});        // 起点坐标和距离
    visited[0][0] = true;
    
    while (!q.empty()) {
        Point curr = q.front();
        q.pop();
        
        // 到达终点
        if (curr.x == n - 1 && curr.y == m - 1) {
            return curr.dist;
        }
        
        // 尝试四个方向
        for (int i = 0; i < 4; i++) {
            int nx = curr.x + dx[i];
            int ny = curr.y + dy[i];
            
            // 检查边界、障碍和访问状态
            if (nx >= 0 && nx < n && ny >= 0 && ny < m 
                && maze[nx][ny] != '#' && !visited[nx][ny]) {
                visited[nx][ny] = true;
                q.push({nx, ny, curr.dist + 1});
            }
        }
    }
    
    return -1;  // 无法到达
}

int main() {
    cout << "请输入迷宫大小n m: ";
    cin >> n >> m;
    cout << "请输入迷宫('.表示通路,#表示墙):" << endl;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> maze[i][j];
        }
    }
    
    int result = bfs();
    if (result != -1) {
        cout << "最短路径长度: " << result << endl;
    } else {
        cout << "无法到达" << endl;
    }
    
    return 0;
}

11.2.3 图的BFS遍历

题目描述:给定一个无向图,从指定起点开始进行广度优先遍历,输出遍历顺序。

输入

  • 第一行:两个整数n和m,表示n个节点和m条边 (1 ≤ n ≤ 1000, 0 ≤ m ≤ 10000)
  • 接下来m行:每行两个整数u和v,表示节点u和v之间有一条边

输出:从节点1开始的BFS遍历序列

示例

输入:
5 6
1 2
1 3
2 4
2 5
3 4
4 5

输出:1 2 3 4 5
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

vector<int> graph[1005];
bool visited[1005];

void bfs(int start) {
    queue<int> q;
    q.push(start);
    visited[start] = true;
    
    cout << "BFS遍历序列: ";
    while (!q.empty()) {
        int curr = q.front();
        q.pop();
        cout << curr << " ";
        
        // 遍历当前节点的所有邻居
        for (int next : graph[curr]) {
            if (!visited[next]) {
                visited[next] = true;
                q.push(next);
            }
        }
    }
    cout << endl;
}

int main() {
    int n, m;
    cout << "请输入节点数n和边数m: ";
    cin >> n >> m;
    
    cout << "请输入" << m << "条边:" << endl;
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        graph[u].push_back(v);  // 无向图,两个方向都要添加
        graph[v].push_back(u);
    }
    
    bfs(1);  // 从节点1开始遍历
    return 0;
}

11.2.4 岛屿数量

题目描述:给定一个由'1'(陆地)和'0'(水)组成的二维网格,计算岛屿的数量。岛屿被水包围,通过水平或垂直连接相邻的陆地形成。

输入

  • 第一行:两个整数n和m (1 ≤ n,m ≤ 100)
  • 接下来n行:每行m个字符,'1'表示陆地,'0'表示水

输出:岛屿的数量

示例

输入:
4 5
11110
11010
11000
00000

输出:岛屿数量: 1

输入:
4 5
11000
11000
00100
00011

输出:岛屿数量: 3
#include <iostream>
#include <queue>
using namespace std;

int n, m;
char grid[105][105];
bool visited[105][105];
int dx[] = {-1, 1, 0, 0};  // 上下左右
int dy[] = {0, 0, -1, 1};

void bfs(int x, int y) {
    queue<pair<int, int>> q;
    q.push({x, y});
    visited[x][y] = true;
    
    while (!q.empty()) {
        auto [cx, cy] = q.front();
        q.pop();
        
        // 检查四个方向
        for (int i = 0; i < 4; i++) {
            int nx = cx + dx[i];
            int ny = cy + dy[i];
            
            // 如果是相邻的陆地且未访问过
            if (nx >= 0 && nx < n && ny >= 0 && ny < m 
                && grid[nx][ny] == '1' && !visited[nx][ny]) {
                visited[nx][ny] = true;
                q.push({nx, ny});
            }
        }
    }
}

int countIslands() {
    int count = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            // 发现新的未访问陆地,开始BFS
            if (grid[i][j] == '1' && !visited[i][j]) {
                bfs(i, j);  // 标记整个岛屿
                count++;    // 岛屿数量+1
            }
        }
    }
    return count;
}

int main() {
    cout << "请输入网格大小n m: ";
    cin >> n >> m;
    cout << "请输入网格(1表示陆地,0表示水):" << endl;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }
    
    cout << "岛屿数量: " << countIslands() << endl;
    return 0;
}

11.3 DFS vs BFS 对比

特性 DFS BFS
数据结构 栈(递归) 队列
空间复杂度 O(h) 深度 O(w) 宽度
最短路径 不保证 保证
适用场景 全排列、组合 最短路径
实现方式 递归简单 迭代

11.4 剪枝优化

11.4.1 可行性剪枝

题目描述:给定一个数组,判断是否能选择其中一些数字使得它们的和等于目标值target。

示例代码

#include <iostream>
using namespace std;

int n, target;
int arr[20];
bool found = false;

void dfs(int depth, int sum) {
    // 剪枝:当前和已经超过目标,没必要继续
    if (sum > target) return;
    
    // 剪枝:如果已经找到解,不需要继续搜索
    if (found) return;
    
    if (depth == n) {
        if (sum == target) {
            found = true;
            cout << "找到目标和 " << target << endl;
        }
        return;
    }
    
    // 选择当前数字
    dfs(depth + 1, sum + arr[depth]);
    // 不选择当前数字
    dfs(depth + 1, sum);
}

int main() {
    cout << "请输入数组大小n: ";
    cin >> n;
    cout << "请输入数组元素: ";
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    cout << "请输入目标和: ";
    cin >> target;
    
    dfs(0, 0);
    
    if (!found) {
        cout << "无法找到目标和 " << target << endl;
    }
    
    return 0;
}

11.4.2 最优性剪枝

题目描述:旅行商问题简化版 - 从起点出发,访问所有城市后回到起点,求最小路径代价。

示例代码

#include <iostream>
#include <climits>
using namespace std;

int n;
int dist[10][10];  // 距离矩阵
bool visited[10];
int best = INT_MAX;
int currentPath[10];
int bestPath[10];

void dfs(int depth, int cost, int currentCity) {
    // 剪枝:当前代价已经超过最优解
    if (cost >= best) return;
    
    if (depth == n) {
        // 回到起点的代价
        int totalCost = cost + dist[currentCity][0];
        if (totalCost < best) {
            best = totalCost;
            // 保存最优路径
            for (int i = 0; i < n; i++) {
                bestPath[i] = currentPath[i];
            }
        }
        return;
    }
    
    // 尝试访问每个未访问的城市
    for (int i = 1; i < n; i++) {
        if (!visited[i]) {
            visited[i] = true;
            currentPath[depth] = i;
            dfs(depth + 1, cost + dist[currentCity][i], i);
            visited[i] = false;  // 回溯
        }
    }
}

int main() {
    cout << "请输入城市数量n: ";
    cin >> n;
    cout << "请输入距离矩阵:" << endl;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> dist[i][j];
        }
    }
    
    currentPath[0] = 0;  // 从城市0开始
    dfs(1, 0, 0);
    
    cout << "最小路径代价: " << best << endl;
    cout << "最优路径: ";
    for (int i = 0; i < n; i++) {
        cout << bestPath[i] << " ";
    }
    cout << "0" << endl;  // 回到起点
    
    return 0;
}

11.4.3 记忆化搜索

题目描述:计算从网格左上角(0,0)到右下角(m,n)的路径数量,只能向右或向下移动。

示例代码

#include <iostream>
#include <cstring>
using namespace std;

int memo[1005][1005];

int dfs(int i, int j) {
    // 已经计算过,直接返回结果
    if (memo[i][j] != -1) {
        return memo[i][j];
    }
    
    // 边界条件:到达边界只有一种路径
    if (i == 0 || j == 0) {
        return memo[i][j] = 1;
    }
    
    // 递归计算:从上方来 + 从左方来
    memo[i][j] = dfs(i - 1, j) + dfs(i, j - 1);
    return memo[i][j];
}

int main() {
    int m, n;
    cout << "请输入网格大小m n: ";
    cin >> m >> n;
    
    // 初始化记忆化数组
    memset(memo, -1, sizeof(memo));
    
    int result = dfs(m, n);
    cout << "从(0,0)到(" << m << "," << n << ")的路径数: " << result << endl;
    
    return 0;
}

记忆化搜索的优势

  • 避免重复计算
  • 时间复杂度从指数级降到多项式级
  • 保持递归思路的清晰性

11.5 综合练习题

11.5.1 数独求解

题目描述:给定一个9×9的数独谜题,其中0表示空格,1-9表示已填入的数字。请填入空格使得每行、每列、每个3×3宫格都包含1-9的数字且不重复。

输入:9行,每行9个数字,0表示空格
输出:完整的数独解,如果无解输出"无解"

示例

输入:
5 3 0 0 7 0 0 0 0
6 0 0 1 9 5 0 0 0
0 9 8 0 0 0 0 6 0
8 0 0 0 6 0 0 0 3
4 0 0 8 0 3 0 0 1
7 0 0 0 2 0 0 0 6
0 6 0 0 0 0 2 8 0
0 0 0 4 1 9 0 0 5
0 0 0 0 8 0 0 7 9

输出:
5 3 4 6 7 8 9 1 2
6 7 2 1 9 5 3 4 8
1 9 8 3 4 2 5 6 7
8 5 9 7 6 1 4 2 3
4 2 6 8 5 3 7 9 1
7 1 3 9 2 4 8 5 6
9 6 1 5 3 7 2 8 4
2 8 7 4 1 9 6 3 5
3 4 5 2 8 6 1 7 9
#include <iostream>
using namespace std;

int board[9][9];

bool isValid(int row, int col, int num) {
    // 检查行:该行不能有重复数字
    for (int j = 0; j < 9; j++) {
        if (board[row][j] == num) return false;
    }
    
    // 检查列:该列不能有重复数字
    for (int i = 0; i < 9; i++) {
        if (board[i][col] == num) return false;
    }
    
    // 检查3x3宫格:该宫格不能有重复数字
    int startRow = (row / 3) * 3;
    int startCol = (col / 3) * 3;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (board[startRow + i][startCol + j] == num) {
                return false;
            }
        }
    }
    
    return true;
}

bool solveSudoku() {
    // 寻找第一个空格
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            if (board[i][j] == 0) {
                // 尝试填入1-9
                for (int num = 1; num <= 9; num++) {
                    if (isValid(i, j, num)) {
                        board[i][j] = num;      // 填入数字
                        if (solveSudoku()) {    // 递归求解
                            return true;
                        }
                        board[i][j] = 0;        // 回溯
                    }
                }
                return false;  // 1-9都不能填入,无解
            }
        }
    }
    return true;  // 所有格子都填满,找到解
}

int main() {
    cout << "请输入9x9数独谜题(0表示空格):" << endl;
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            cin >> board[i][j];
        }
    }
    
    cout << "求解中..." << endl;
    if (solveSudoku()) {
        cout << "数独解:" << endl;
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                cout << board[i][j] << " ";
            }
            cout << endl;
        }
    } else {
        cout << "无解" << endl;
    }
    
    return 0;
}

11.5.2 单词搜索

题目描述:给定一个二维字符网格和一个单词,判断单词是否存在于网格中。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中"相邻"单元格是水平或垂直方向上相邻的。同一个单元格内的字母不允许被重复使用。

输入

  • 第一行:两个整数n和m,表示网格大小 (1 ≤ n,m ≤ 10)
  • 接下来n行:每行m个字符,表示网格
  • 最后一行:要搜索的单词

输出:如果找到单词输出"找到单词",否则输出"未找到"

示例

输入:
3 4
ABCE
SFCS
ADEE
ABCCED

输出:找到单词

输入:
3 4
ABCE
SFCS
ADEE
SEE

输出:找到单词

输入:
3 4
ABCE
SFCS
ADEE
ABCB

输出:未找到
#include <iostream>
#include <string>
using namespace std;

char board[10][10];
bool visited[10][10];
int n, m;
string word;
int dx[] = {-1, 1, 0, 0};  // 上下左右
int dy[] = {0, 0, -1, 1};

bool dfs(int x, int y, int index) {
    // 成功匹配整个单词
    if (index == word.length()) {
        return true;
    }
    
    // 边界检查、访问检查、字符匹配检查
    if (x < 0 || x >= n || y < 0 || y >= m 
        || visited[x][y] || board[x][y] != word[index]) {
        return false;
    }
    
    visited[x][y] = true;  // 标记当前位置已访问
    
    // 尝试四个方向
    for (int i = 0; i < 4; i++) {
        if (dfs(x + dx[i], y + dy[i], index + 1)) {
            return true;
        }
    }
    
    visited[x][y] = false;  // 回溯,取消标记
    return false;
}

bool exist() {
    // 从每个位置开始尝试匹配单词
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (dfs(i, j, 0)) {
                return true;
            }
        }
    }
    return false;
}

int main() {
    cout << "请输入网格大小n m: ";
    cin >> n >> m;
    cout << "请输入网格:" << endl;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> board[i][j];
        }
    }
    cout << "请输入要搜索的单词: ";
    cin >> word;
    
    if (exist()) {
        cout << "找到单词" << endl;
    } else {
        cout << "未找到" << endl;
    }
    
    return 0;
}

11.6 搜索算法总结

DFS适用场景

  • 全排列、组合问题
  • 连通性问题
  • 路径问题(不要求最短)
  • 回溯问题

BFS适用场景

  • 最短路径问题
  • 层次遍历
  • 状态转移最少步数

优化技巧

  1. 剪枝:减少无效搜索
  2. 记忆化:避免重复计算
  3. 双向搜索:从两端同时搜索
  4. 启发式搜索:A*算法

总结

本教程涵盖了C++的重要进阶知识点:

  1. do while循环:至少执行一次的循环
  2. switch语句:多分支选择结构
  3. 流程图:算法可视化工具
  4. string字符串:强大的字符串处理
  5. 引用:高效的参数传递
  6. 联合体:节省内存的数据结构
  7. 文件重定向:输入输出到文件
  8. 调试技巧:输出调试和GDB调试
  9. 断言调试:检查程序假设
  10. 二分答案:优化搜索问题
  11. 搜索算法:DFS和BFS

掌握这些知识点,你的C++编程能力将大幅提升!


练习建议

  1. 每个知识点都要动手实践
  2. 多做题巩固理解
  3. 学会调试和优化代码
  4. 理解算法思想,不要死记硬背
posted @ 2025-11-14 17:26  surprise_ying  阅读(0)  评论(0)    收藏  举报