热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++回溯算法深度优先搜索举例分析

回溯在迷宫搜索中使用很常见,就是这条路走不通,然后返回前一个路口,继续下一条路。回溯算法说白了就是穷举法,下面让我们一起来看看回溯算法深度优先搜索吧

扑克牌全排列

假如有编号为1~ 3的3张扑克牌和编号为1~3的3个盒子,现在需要将3张牌分别放到3个盒子中去,且每个盒子只能放一张牌,一共有多少种不同的放法。

解题思路:假定按照牌面值从小到大依次尝试,即将1号牌放入第一个盒子中。按此顺序继续向后走,放完第三个盒子时,手中的牌也已经用完,再继续往后则到了盒子的尽头。此时一种放法已经完成了,即这条路走到了尽头,需要折返,重新回到上一个盒子。这里回到第三个盒子,把第三个盒子中的牌回收,再去尝试能否放其它的牌。但这时手里只有一张3号牌,所以需要继续向后回退,到2号盒子。按照上述步骤依次会产生所有结果。 用一个数组book标记手里是否有这张牌。

Dfs(当前这一步的处理逻辑)
{
1. 判断边界,是否已经一条道走到黑了:向上回退
2. 尝试当下的每一种可能
3. 确定一种可能之后,继续下一步 Dfs(下一步)
}

代码实现:

void DFS(vector& boxs, vector& books, int n, int index)
{
	if (index >= n + 1)
	{
		for (int i = 1; i <= n; i++)
			cout <

员工的重要性

问题描述:

给定一个保存员工信息的数据结构,它包含了员工 唯一的 id ,重要度 和 直系下属的 id 。 比如,员工 1 是员工 2 的领导,员工 2 是员工 3 的领导。他们相应的重要度为 15 , 10 , 5 。那么员工 1 的数据结构是 [1, 15, [2]] ,员工 2的 数据结构是 [2, 10, [3]] ,员工 3 的数据结构是 [3, 5, []] 。注意虽然员工 3 也是员工 1 的一个下属,但是由于 并不是直系 下属,因此没有体现在员工 1 的数据结构中。

现在输入一个公司的所有员工信息,以及单个员工 id ,返回这个员工和他所有下属的重要度之和。

解题思路:

边界:下属为空

每次先加第一个下属的重要性

按照相同的操作再去加下属的第一个下属的重要性。

代码实现:

/*
// Definition for Employee.
class Employee {
public:
    int id;
    int importance;
    vector subordinates;
};
*/

class Solution {
public:
    int DFS(unordered_map& info, int id)
    {
        int curImpo = info[id]->importance;
        for(const auto& sid : info[id]->subordinates)
        {
            curImpo += DFS(info, sid);
        }

        return curImpo;
    }

    int getImportance(vector employees, int id) {
        if(employees.empty())
            return 0;
        unordered_map info;
        for(const auto& e : employees)
        {
            info[e->id] = e;
        }

        return DFS(info, id);
    }
};

图像渲染

问题描述:

有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。 给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。 为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为新的颜色值。

最后返回经过上色渲染后的图像。

解题思路:

从所给坐标开始,向上下左右四个方向渲染,只要渲染点的颜色值和原坐标相同,则继续向外渲染。 边界:位置是否越界。

这里需要用标记避免重复修改,使时间复杂度不超过O(row*col)

代码实现:

int nextP[4][2] = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};

class Solution {
public:
    void DFS(vector>& image, vector>& book, int sr, int sc, int newColor, int oldColor, int row, int col)
    {
        image[sr][sc] = newColor;
        book[sr][sc] = 1;
        for(int i = 0; i <4; i++)
        {
            int curX = sr + nextP[i][0];
            int curY = sc + nextP[i][1];

            //判断是否越界
            if(curX <0 || curX >= row || curY <0 || curY >= col)
                continue;
            //颜色符合要求且之前没被渲染过则继续渲染
            if(image[curX][curY] == oldColor && book[curX][curY] == 0)
            {
                DFS(image, book, curX, curY, newColor, oldColor, row, col);
            }
        }
    }

    vector> floodFill(vector>& image, int sr, int sc, int newColor) {
        int row = image.size();
        int col = image[0].size();
        int oldColor = image[sr][sc];
        vector> book(row, vector(col, 0));

        DFS(image, book, sr, sc, newColor, oldColor, row, col);

        return image;
    }
};

被围绕的区域

问题描述:

给你一个 m x n 的矩阵 board ,由若干字符 ‘X' 和 ‘O' ,找到所有被 ‘X' 围绕的区域,并将这些区域里所有的 ‘O' 用 ‘X' 填充。

解题思路: 从每个边缘的O开始,只要和边缘的O连通,则它就没有被包围。

1.首先寻找边上的每一个O,如果没有,表示所有的O都被包围。

2.对于边上的每一个O进行dfs扩散,先把边上的每一个O用特殊符号标记(除了X和O以外)。把和它相邻的O都替换为特殊符号,每一个新的位置都做相同的dfs操作。

3.所有扩散结束之后,把特殊符号的位置(和边界连通)还原为O,原来为O的位置(和边界不连通)替换为X即可。

代码实现:

int nextP[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

class Solution {
public:
    void DFS(vector>& board, int curX, int curY, int row, int col)
    {
        board[curX][curY] = 'A';
        for(int i = 0; i <4; i++)
        {
            int x = curX + nextP[i][0];
            int y = curY + nextP[i][1];

            if(x <0 || x >= row
               ||y <0 || y >= col)
                continue;

            if(board[x][y] != 'A' && board[x][y] != 'X')
                DFS(board, x, y, row, col);
        }
    }

    void solve(vector>& board) {
        if(board.empty())
            return;
        int row = board.size();
        int col = board[0].size();

        //第一行和最后一行
        for(int i = 0; i 

岛屿数量

问题描述:

给你一个由 ‘1'(陆地)和 ‘0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [ [“1”,“1”,“1”,“1”,“0”], [“1”,“1”,“0”,“1”,“0”], [“1”,“1”,“0”,“0”,“0”], [“0”,“0”,“0”,“0”,“0”] ]

输出:1

解题思路:

本题可以采用类似渲染的做法,尝试以每个点作为渲染的起点,可以渲染的陆地都算作一个岛屿,最后看渲染了多少次,即深度优先算法执行了多少次,就是岛屿的数量。

代码实现:

int nextP[4][2] = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };

class Solution {
public:
    void DFS(vector>& grid, vector>& book, int x, int y, int row, int col)
    {
        book[x][y] = 1;
        for(int i = 0; i <4; i++)
        {
            int curX = x + nextP[i][0];
            int curY = y + nextP[i][1];

            if(curX <0 || curX >= row || curY <0 || curY >= col)
                continue;
            
            if(grid[curX][curY] == '1' && book[curX][curY] == 0)
                DFS(grid, book, curX, curY, row, col);
        }
    }

    int numIslands(vector>& grid) {
        if(grid.empty())
            return 0;
        int row = grid.size();
        int col = grid[0].size();
        int num = 0;
        vector> book(row, vector(col, 0));
        for(int i = 0; i 

电话号码的字母组合

问题描述:

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

解题思路:

首先使用数组(也可使用哈希表)存储每个数字对应的所有可能的字母,然后进行回溯操作。回溯算法用于寻找所有的可行解,如果发现一个解不可行,则会舍弃不可行的解。在这道题中,由于每个数字对应的每个字母都可能进入字母组合,因此不存在不可行的解,直接穷举所有的解即可。

代码实现:

string mapString[] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

class Solution {
public:
    void DFS(string& digits, vector& result, string curStr, int curDepth)
    {
        //边界,找到一种组合,放入数组中,结束此路径,向上回溯
        if(curDepth == digits.size())
        {
            if(!curStr.empty())
                result.push_back(curStr);
            return;
        }
        //找到当前字符映射在mapString种的位置
        int curMapIndex = digits[curDepth] - '0';
        string curMap = mapString[curMapIndex];
        //遍历每一种可能
        for(const auto& ch : curMap)
        {
            DFS(digits, result, curStr+ch, curDepth+1);
        }
    }

    vector letterCombinations(string digits) {
        vector result;
        DFS(digits, result, "", 0);
        return result;
    }
};

组合总数

问题描述:

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

输入:candidates = [2,3,6,7], target = 7

输出:[[2,2,3],[7]]

解题思路:

此题相加的元素可以重复,所以去下一个元素的位置可以从当前位置开始,DFS+回溯。为了保证组合不重复(顺序不同,元素相同,也算重复),不再从当前位置向前看。

1.从第一个元素开始相加

2.让局部和继续累加候选的剩余值

3.局部和等于目标值,保存组合,向上回退,寻找其它组合

代码实现:

class Solution {
public:
    void DFS(vector& candidates, vector>& result, vector curCand, int prePos, int curSum, int target)
    {
        if(curSum >= target)
        {
            if(curSum == target)
                result.push_back(curCand);
            return;
        }
        for(int i = prePos; i > combinationSum(vector& candidates, int target) {
        vector> result;
        vector curCand;
        DFS(candidates, result, curCand, 0, 0, target);
        return result;
    }
};

活字印书

问题描述:

你有一套活字字模 tiles,其中每个字模上都刻有一个字母 tiles[i]。返回你可以印出的非空字母序列的数目。

注意:本题中,每个活字字模只能使用一次。

示例 1:

输入:“AAB”

输出:8

解释:可能的序列为 “A”, “B”, “AA”, “AB”, “BA”, “AAB”, “ABA”, “BAA”。

解题思路:

此题组合的长度不唯一,最小组合长度为1,最大组合长度为tiles的长度。按照题意tiles中每一个位置的字符在组合中只能出现一次,所以可以用一个标记辅助。当组合新的组合时,可以与tiles种的每一个位置组合,但是如果当前位置已经在当前组合中出现过,则跳过。虽然此题种每一个位置的字符在组合中只能出现一次,但是tiles中可能有相同的字符,所以需要考虑重复的组合。而用unordered_set可以天然去重。

DFS+回溯:

1.当前组合不为空,则插入set中

2.继续给当前组合拼接新的组合,尝试拼接tiles每一个位置的字符

3.如果当前位置已经在组合中出现过,返回到2,否则标记当前位置,继续拼接更长的组合

4.回溯,尝试组合其它位置,返回2

当所有位置都已经使用过时,当前递归就结束了,继续向上层DFS回退

最终返回set大小即为组合数目

代码实现:

class Solution {
public:
    void DFS(string& tiles, vector& book, string curStr, unordered_set& totolaString)
    {
        if(!curStr.empty())
        {
            totolaString.insert(curStr);
        }
        for(int i = 0; i  book(tiles.size(), 0);
        unordered_set totolaString;
        DFS(tiles, book, "", totolaString);

        return totolaString.size();
    }
};

N皇后

问题描述:

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q' 和 ‘.' 分别代表了皇后和空位。

解题思路:

DFS+回溯

从第一行开始放置皇后,每确定一个位置,判断是否会冲突(是否在同一列、同一斜线,已经不可能在同一行)

同一列:纵坐标相同

同一斜线:坐标差或坐标和相同。

当前行位置确定后,继续确定下一行的位置。

回退,尝试其他行的其它位置。

代码实现:

class Solution {
public:
    bool isValidPos(vector>& curRet, int row, int col)
    {
        for(pair pos : curRet)
        {
            if(pos.secOnd== col || pos.first + pos.secOnd== row + col
               || pos.first - pos.secOnd== row - col)
                return false;
        }

        return true;
    }

    void DFS(vector>>& allRet, vector>& curRet, int curRow, int n)
    {
        //如果每一行都没有冲突,则是一种可行的方案
        if(curRow == n)
        {
            allRet.push_back(curRet);
            return;
        }
        //确定当前行的每一个位置是否和已确定的位置有冲突
        for(int i = 0; i > transResult(vector>>& allRet, int n)
    {
        vector> allMap;
        //所有方案
        for(vector> curRet : allRet)
        {
            vector curMap(n, string(n, '.'));
            //一种方案中的所有皇后的位置
            for(pair pos : curRet)
            {
                curMap[pos.first][pos.second] = 'Q';
            }
            allMap.push_back(curMap);
        }

        return allMap;
    }

    vector> solveNQueens(int n) {
        vector>> allRet;
        vector> curRet;
        DFS(allRet, curRet, 0, n);

        return transResult(allRet, n);
    }
};

到此这篇关于C++回溯算法深度优先搜索举例分析的文章就介绍到这了,更多相关C++ 回溯算法内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 本文章探讨了如何使用动态规划解决LeetCode第32题——寻找最长的有效括号子串。通过具体示例和代码解析,帮助读者理解算法背后的逻辑。 ... [详细]
  • 本文探讨了Java中有效停止线程的多种方法,包括使用标志位、中断机制及处理阻塞I/O操作等,旨在帮助开发者避免使用已废弃的危险方法,确保线程安全和程序稳定性。 ... [详细]
  • 本文详细介绍了Objective-C中的面向对象编程概念,重点探讨了类的定义、方法的实现、对象的创建与销毁等内容,旨在帮助开发者更好地理解和应用Objective-C的面向对象特性。 ... [详细]
  • ED Tree HDU4812 点分治+逆元
    这道题非常巧妙!!!我们进行点分治的时候,算出当前子节点的所有子树中的节点,到当前节点节点的儿子节点的距离,如下图意思就是当前节点的红色节点,我们要求出红色节点的儿子节点绿色节点, ... [详细]
  • 来自FallDream的博客,未经允许,请勿转载,谢谢。一天一套noi简直了.昨天勉强做完了noi2011今天教练又丢出来一套noi ... [详细]
  • 本文详细介绍了在PHP中如何获取和处理HTTP头部信息,包括通过cURL获取请求头信息、使用header函数发送响应头以及获取客户端HTTP头部的方法。同时,还探讨了PHP中$_SERVER变量的使用,以获取客户端和服务器的相关信息。 ... [详细]
  • 题面:P3178[HAOI2015]树上操作好像其他人都嫌这道题太容易了懒得讲,好吧那我讲。题解:第一个操作和第二个操作本质上是一样的&# ... [详细]
  • 1.打印日历打印日历判断是否是闰年#include<stdio.h>inta[]{0,31,28,31,30,31,30,31,31 ... [详细]
  • 本文详细介绍了在 Windows 7 上安装和配置 PHP 5.4 的 Memcached 分布式缓存系统的方法,旨在减少数据库的频繁访问,提高应用程序的响应速度。 ... [详细]
  • ZOJ 2760 - 最大流问题
    题目链接:How Many Shortest Paths。题目描述:给定一个包含n个节点的有向图,通过一个n*n的矩阵来表示。矩阵中的a[i][j]值为-1表示从节点i到节点j无直接路径;否则,该值表示从i到j的路径长度。输入起点vs和终点vt,计算从vs到vt的所有不共享任何边的最短路径数量。如果起点和终点相同,则输出无穷大。 ... [详细]
  • 近期在研究Java IO流技术时,遇到了一个关于如何正确读取Doc文档而不出现乱码的问题。本文将详细介绍使用Apache POI库处理Doc和Docx文件的具体方法,包括必要的库引入和示例代码。 ... [详细]
  • 深入解析mt_allocator内存分配器(二):多线程与单线程场景下的实现
    本文详细介绍了mt_allocator内存分配器在多线程和单线程环境下的实现机制。该分配器以2的幂次方字节为单位分配内存,支持灵活的配置和高效的性能。文章分为内存池特性描述、内存池实现、单线程内存池实现、内存池策略类实现及多线程内存池实现等部分,深入探讨了内存池的初始化、内存分配与回收的具体实现。 ... [详细]
  • 本文介绍了一道来自LeetCode的编程题——拼写单词。题目要求从给定的词汇表中找出可以由指定字母表中的字母拼写出的单词,并计算这些单词的总长度。文章将展示如何通过使用数组替代哈希表来提高算法的执行效率。 ... [详细]
  • java datarow_DataSet  DataTable DataRow 深入浅出
    本篇文章适合有一定的基础的人去查看,最好学习过一定net编程基础在来查看此文章。1.概念DataSet是ADO.NET的中心概念。可以把DataSet当成内存中的数据 ... [详细]
  • 探索CNN的可视化技术
    神经网络的可视化在理论学习与实践应用中扮演着至关重要的角色。本文深入探讨了三种有效的CNN(卷积神经网络)可视化方法,旨在帮助读者更好地理解和优化模型。 ... [详细]
author-avatar
永不言败LM
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有