基础算法总结(三)Algorithm(广度优先搜索 Breadth First Search)

广度优先搜索(Breadth First Search) ------ 一石激起千层浪


Bfs()
{
  1. 建立起始步骤,队列初始化
  2. 遍历队列中的每一种可能,whlie(队列不为空)
  {
    通过队头元素带出下一步的所有可能,并且依次入队
    {
    判断当前情况是否达成目标:按照目标要求处理逻辑
    }
    继续遍历队列中的剩余情况
  }
}


题目目录:

第1题 迷宫问题
第2题 员工的重要性
第3题 N叉树的层序遍历
第4题 腐烂的橘子
第5题 单词接龙
第6题 最小基因变化
第7题 打开转盘锁



第1题 迷宫问题

/*
广度优先搜索(Breadth First Search) ------ 一石激起千层浪

迷宫问题
*/
#include <iostream>
#include <queue>
#include <vector>

using namespace std;

struct node
{
    int x;
    int y;
};

//queue实现
bool Bfs(vector<vector<int>>& graph, int startx, int starty, int endx, int endy)
{
    //迷宫的大小
    int m = graph.size();
    int n = graph[0].size();
    //标记迷宫中的位置是否被走过
    vector<vector<int>> book(m, vector<int>(n, 0));
    //存储迷宫中的位置
    queue<node> q;
    node xy = {startx, starty};
    q.push(xy);
    //标记已经走过
    book[startx][starty] = 1;
    //四个行走的方向,右下左上
    int _nextPosition[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    //标记是否可以出去
    bool flag = false;
    while(!q.empty()){
        //当前位置带出所有新的位置,右下左上
        for (size_t i = 0; i < 4; ++i){
            //计算新的位置
            int newx = q.front().x + _nextPosition[i][0];
            int newy = q.front().y + _nextPosition[i][1];
            //如果新的位置越界,继续下一个
            if(newx < 0 || newx >= m || newy < 0 || newy >= n){
                continue;
            }
            //如果新的位置无障碍并且之前也没走过,保存新的位置
            if(graph[newx][newy] == 0 && book[newx][newy] == 0){
                node newxy = {newx, newy};
                q.push(newxy);
                //标记已被走过
                book[newx][newy] = 1;
            }
            //如果新的位置为目标位置,则结束查找
            if(newx == endx && newy == endy){
                flag = true;
                break;
            }
        }
        if(flag){
            break;
        }
        //否则,用新的位置继续向后走
        q.pop();
    }
    return flag;
}

//vector实现
bool Bfs1(vector<vector<int>>& graph, int startx, int starty, int endx, int endy)
{
    //迷宫的大小
    int m = graph.size();
    int n = graph[0].size();
    //标记迷宫中的位置是否被走过
    vector<vector<int>> book(m, vector<int>(n, 0));
    //存储迷宫中的位置
    vector<node> arr(m * n);

    int head = 0, tail = 1;
    arr[head].x = startx;
    arr[head].y = starty;

    //标记已经走过
    book[startx][starty] = 1;
    //四个行走的方向,右下左上
    int _nextPosition[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    //标记是否可以出去
    bool flag = false;
    while(head < tail){
        //当前位置带出所有新的位置,右下左上
        for (size_t i = 0; i < 4; ++i){
            //计算新的位置
            int newx = arr[head].x + _nextPosition[i][0];
            int newy = arr[head].y + _nextPosition[i][1];
            //如果新的位置越界,继续下一个
            if(newx < 0 || newx >= m || newy < 0 || newy >= n){
                continue;
            }
            //如果新的位置无障碍并且之前也没走过,保存新的位置
            if(graph[newx][newy] == 0 && book[newx][newy] == 0){
                arr[tail].x = newx;
                arr[tail].y = newy;
                //标记已被走过
                book[newx][newy] = 1;
                ++tail;
            }
            //如果新的位置为目标位置,则结束查找
            if(newx == endx && newy == endy){
                flag = true;
                break;
            }
        }
        if(flag){
            break;
        }
        //否则,用新的位置继续向后走
        ++head;
    }
    return flag;
}
/*
总结:广度优先搜索模型
    Bfs()
    {
       1. 建立起始步骤,队列初始化
       2. 遍历队列中的每一种可能,whlie(队列不为空)
      {
           通过队头元素带出下一步的所有可能,并且依次入队
          {
               判断当前情况是否达成目标:按照目标要求处理逻辑
          }
           继续遍历队列中的剩余情况
           
      }
    }
*/

int main(){
    int sx = 0, sy = 0;
    int ex = 7, ey = 9;
    vector<vector<int>> graph ={{0, 0, 0, 1, 0, 0, 1, 0, 1, 0},
                                {0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
                                {0, 1, 1, 0, 1, 1, 0, 0, 1, 0},
                                {0, 1, 0, 1, 0, 0, 0, 1, 0, 0},
                                {0, 1, 0, 0, 0, 1, 1, 1, 0, 1},
                                {0, 1, 1, 1, 0, 1, 0, 0, 0, 1},
                                {0, 0, 0, 0, 0, 1, 0, 1, 1, 1},
                                {0, 1, 0, 0, 1, 1, 0, 1, 0, 0},
                                {1, 1, 1, 0, 1, 0, 0, 0, 0, 1},
                                {0, 0, 0, 0, 0, 1, 0, 1, 0, 0},};
    cout << "是否可以走出迷宫: " << Bfs(graph, sx, sy, ex, ey) << endl;
    cout << "是否可以走出迷宫: " << Bfs1(graph, sx, sy, ex, ey) << endl;
    return 0; 
}

第2题 员工的重要性

/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/employee-importance

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

示例:
    输入: [[1, 5, [2, 3]], [2, 3, []], [3, 3, []]], 1
    输出: 11
    解释: 员工1自身的重要度是5,他有两个直系下属2和3,而且2和3的重要度均为3。因此员工1的总重要度是 5 + 3 + 3 = 11。
注意:一个员工最多有一个直系领导,但是可以有多个直系下属员工数量不超过2000。
*/
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>

using namespace std;

struct Employee
{
    int id;
    int importance;
    vector<int> subid;
};

class Solution
{
public:
    int getImportance(vector<Employee*>& employees, int id) {
        int res = 0;
        queue<int> q;
        //初始化队列
        q.push(id);
        //把员工信息保存在map中,方便查询
        unordered_map<int, Employee*> m;
        for (auto& e : employees){
            m[e->id] = e;
        }
        //遍历队列
        while(!q.empty()){
            int t = q.front();
            q.pop();
            res += m[t]->importance;
            for(auto& num : m[t]->subid){
                q.push(num);
            }
        }
        return res;
    }
};

int main(){
    vector<Employee*> employees(3);
    employees[0] = new Employee, employees[0]->id = 1, employees[0]->importance = 5, employees[0]->subid = {2, 3};
    employees[1] = new Employee, employees[1]->id = 2, employees[1]->importance = 3, employees[1]->subid = {};
    employees[2] = new Employee, employees[2]->id = 3, employees[2]->importance = 3, employees[2]->subid = {};
    Solution S;
    cout << S.getImportance(employees, 1) << endl;
    return 0;
}

第3题 N叉树的层序遍历

/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal

给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
例如,给定一个 3叉树,返回其层序遍历:
        [
            [1],
            [3,2,4],
            [5,6]
        ]
说明:树的深度不会超过 1000。树的节点总数不会超过 5000。
*/
#include <iostream>
#include <vector>
#include <queue>

using namespace std;

class Node{
public:
    int val;
    vector<Node*> children;

    Node(){}

    Node(int _val, vector<Node*> _children){
        val = _val;
        children = _children;
    }
};

class Solution
{
public:
    vector<vector<int>> levelOrder(Node* root){
        //定义一个容器,用于返回结果
        vector<vector<int>> treeVec;
        if (root == nullptr){
            return treeVec;
        }
        //临时存放每一层的元素
        vector<int> newFloor;
        queue<Node*> q;
        q.push(root);
        while(!q.empty()){
            //获取当前层元素个数,即整个队列元素
            int size = q.size();
            //存放新层元素之前先清空
            newFloor.clear();
            while(size--){
                //取队首元素
                Node* node = q.front();
                q.pop();
                //添加到数组中
                newFloor.push_back(node->val);
                //并将其孩子入队
                for(auto& child : node->children){
                    if(child){
                        q.push(child);
                    }
                }
            }
            //新层有元素,则放入vector
            if(!newFloor.empty()){
                treeVec.push_back(newFloor);
            }
        }
        return treeVec;
    }
};
void print_vv(vector<vector<int>>& vv){
    for(int i = 0; i < vv.size(); ++i){
        for(int j = 0; j < vv[i].size(); ++j){//注意这里当前第几行就计算第几行的size
            cout << vv[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
}

int main(){
    Node node5(5, {}), node6(6, {});
    Node node3(3, {&node5, &node6}), node2(2, {}), node4(4, {});
    Node node1(1, {&node3, &node2, &node4});
    Solution S;
    vector<vector<int>> vv = S.levelOrder(&node1);
    print_vv(vv);
    return 0;
}

第4题 腐烂的橘子

/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotting-oranges
*/
#include <iostream>
#include <vector>
#include <queue>

using namespace std;
/*
本题可以先找到所有的腐烂橘子,入队,用第一批带出新一批腐烂的橘子
每以匹橘子都会在一分钟之内腐烂,所以此题可以转化为求BFS执行的大循环的次数
这里的step的更新需要有一个标记,只有新的腐烂的橘子加入,step才能自加
最后BFS执行完之后,说明所有可以被腐烂的都完成了,再去遍历grid,如何还有
值为1的,说明没有办法完全腐烂,返回-1,如果没有,则返回step
*/
class Solution
{
public:
    int orangesRotting(vector<vector<int>>& grid){
        //用pair存放位置
        queue<pair<int, int>> q;
        int row = grid.size();
        int col = grid[0].size();
        for(int i = 0; i < row; ++i){
            for(int j = 0; j < col; ++j){
                if(grid[i][j] == 2){
                    q.push(make_pair(i, j));
                }
            }
        }
        int step = 0;
        while(!q.empty()){
            int n = q.size();
            int flag = 0;
            //用当前这一批已经腐烂的橘子带出下一批要腐烂的橘子
            //故要遍历队列中的所有位置
            while(n--){
                auto rot = q.front();
                q.pop();
                //当前位置向四个方向蔓延
                for(int i = 0; i < 4; ++i){
                    int nx = rot.first + _nextPosition[i][0];
                    int ny = rot.second + _nextPosition[i][1];
                    //如果位置越界或者是空格,或者已经是腐烂的位置,则跳过
                    if(nx < 0 || nx >= row || ny < 0 || ny >= col || grid[nx][ny] != 1){
                        continue;
                    }
                    //标记有新的被腐烂
                    flag = 1;
                    grid[nx][ny] = 2;
                    q.push(make_pair(nx, ny));
                }
            }
            //如果有新的腐烂,才++
            if(flag){
                ++step;
            }
        }
        //判断是否还有无法腐烂的
        for(int i = 0; i < row; ++i){
            for(int j = 0; j < col; ++j){
                if(grid[i][j] == 1){
                    return -1;
                }
            }
        }
        return step;
    }
private:
    //可以蔓延的方向
    int _nextPosition[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
};
void print_vv(vector<vector<int>>& vv){
    cout << endl;
    for(int i = 0; i < vv.size(); ++i){
        for(int j = 0; j < vv[0].size(); ++j){
            cout << vv[i][j] << " ";
        }
        cout << endl;
    }
}

int main(){
    vector<vector<int>> grid = {{2, 1, 1}, {1, 1, 0}, {0, 1, 1}};
    print_vv(grid);
    Solution S;
    cout << S.orangesRotting(grid) << endl;

    vector<vector<int>> grid1 = {{2, 1, 1}, {0, 1, 1}, {1, 0, 1}};
    print_vv(grid1);
    cout << S.orangesRotting(grid1) << endl;

    vector<vector<int>> grid2 = {{0, 2}};
    print_vv(grid2);
    cout << S.orangesRotting(grid2) << endl;

    return 0;
}

第5题 单词接龙

/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-ladder
*/
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unordered_set>

using namespace std;
/*
1.通过BFS, 首先用beginWord带出转换一个字母之后所有可能的结果
2.每一步都要把队列中上一步添加的所有单词转换一遍,最短的转换肯定在这些单词当中, 所有这些词的转换只能算一
次转换,因为都是上一步转换出来的,这里对于每个单词的每个位置都可以用26个字母进行转换,所以一个单词一次转换
的可能有:单词的长度 * 26
3.把转换成功的新词入队,进行下一步的转换
4.最后整个转换的长度就和BFS执行的次数相同
*/
class Solution
{
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList){
        //hash表的查询效率最高
        unordered_set<string> wordDict(wordList.begin(), wordList.end());
        //标记单词是否已经访问过,访问过的不再访问
        unordered_set<string> visited;
        visited.insert(beginWord);
        //初始化队列
        queue<string> q;
        q.push(beginWord);
        int res = 1;
        while(!q.empty()){
            int nextSize = q.size();
            //每一步都要把队列中上一步添加的所有单词转换一遍
            //最短的转换肯定在这些单词当中, 所有这些词的转换只能算一次转换
            //因为都是上一步转换出来的
            while(nextSize--){
                string curWord = q.front();
                q.pop();
                //尝试转换当前单词的每一个位置
                for(int i = 0; i < curWord.size(); ++i){
                    string newWord = curWord;
                    //每一个位置用26个字母分别替换
                    for(char ch = 'a'; ch <= 'z'; ++ch){
                        newWord[i] = ch;
                        //如果列表中没有此单词或者已经访问过(它的转换已经遍历过,无需再次遍历),则跳过
                        if(!wordDict.count(newWord) || visited.count(newWord)){
                            continue;
                        }
                        //转换成功,则在上一步转换的基础上+1
                        if(newWord == endWord){
                            return res + 1;
                        }
                        //还没有转换成功,则新的单词入队
                        visited.insert(newWord);
                        q.push(newWord);
                    }
                }
            }
            res++;
        }
        //转换不成功,返回0
        return 0;
    }
};

int main(){
    string beginWord = "hit";
    string endWord = "cog";
    vector<string> wordList = {"hot", "dot", "dog", "lot", "log", "cog"};
    Solution S;
    cout << S.ladderLength(beginWord, endWord, wordList) << endl;

    string beginWord1 = "hit";
    string endWord1 = "cog";
    vector<string> wordList1 = {"hot", "dot", "dog", "lot", "log"};
    cout << S.ladderLength(beginWord1, endWord1, wordList1) << endl;

    return 0;
}

第6题 最小基因变化

/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-genetic-mutation
*/
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unordered_set>

using namespace std;

class Solution
{
public:
    int minMutation(string startGene, string endGene, vector<string>& bank){
        //hash表的查询效率最高
        unordered_set<string> wordDict(bank.begin(), bank.end());
        //标记基因是否已经访问过,访问过的不再访问
        unordered_set<string> visited;
        visited.insert(startGene);
        //初始化队列
        queue<string> q;
        q.push(startGene);
        int res = 0;
        while(!q.empty()){
            int nextSize = q.size();
            //每一步都要把队列中上一步添加的所有基因变化一遍
            //最短的变化肯定在这些基因当中, 所有这些词的变化只能算一次变化
            //因为都是上一步变化出来的
            while(nextSize--){
                string curWord = q.front();
                q.pop();
                //尝试变化当前基因的每一个位置
                for(int i = 0; i < curWord.size(); ++i){
                    string newWord = curWord;
                    //每一个位置用26个字母分别替换
                    for(char ch = 'A'; ch <= 'Z'; ++ch){
                        newWord[i] = ch;
                        //如果列表中没有此基因或者已经访问过(它的变化已经遍历过,无需再次遍历),则跳过
                        if(!wordDict.count(newWord) || visited.count(newWord)){
                            continue;
                        }
                        //变化成功,则在上一步变化的基础上+1
                        if(newWord == endGene){
                            return res + 1;
                        }
                        //还没有变化成功,则新的基因入队
                        visited.insert(newWord);
                        q.push(newWord);
                    }
                }
            }
            res++;
        }
        //变化不成功,返回-1
        return -1;
    }
};

int main(){
    string startGene = "AACCGGTT";
    string endGene = "AACCGGTA";
    vector<string> bank = {"AACCGGTA"};
    Solution S;
    cout << S.minMutation(startGene, endGene, bank) << endl;

    string startGene1 = "AACCGGTT";
    string endGene1 = "AAACGGTA";
    vector<string> bank1 = {"AACCGGTA", "AACCGCTA", "AAACGGTA"};
    cout << S.minMutation(startGene1, endGene1, bank1) << endl;

    string startGene2 = "AAAAACCC";
    string endGene2 = "AACCCCCC";
    vector<string> bank2 = {"AAAACCCC", "AAACCCCC", "AACCCCCC"};
    cout << S.minMutation(startGene2, endGene2, bank2) << endl;

    return 0;
}

第7题 打开转盘锁

/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/open-the-lock
*/
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unordered_set>

using namespace std;
/*
深度优先不适合解此题,递归深度太大,会导致栈溢出
本题的密码为4位密码,每位密码可以通过拨动一次进行改变,注意这里的数的回环以及拨动的方向
拨动方向:向前,向后
回环:如果当前是9,0时,向前,向后拨动需要变成最小最大,而不是简单的自加自减
*/
class Solution
{
public:
    int openLock(vector<string>& deadends, string target){
        //hash表的查询效率最高
        unordered_set<string> deadendsSet(deadends.begin(), deadends.end());
        //如果"0000"在死亡字符串中,则永远到达不了
        if(deadendsSet.count("0000")){
            return -1;
        }
        //加标记,已经搜索过的字符串不需要再次搜索
        unordered_set<string> book;
        book.insert("0000");
        //初始化队列
        queue<string> q;
        q.push("0000");
        int step = 0;
        while(!q.empty()){
            int n = q.size();
            //从上一步转换之后的字符串都需要进行验证和转换
            //并且只算做一次转换,类似于层序遍历,转换的步数和层相同
            //同一层的元素都是经过一步转换得到的
            for(int i = 0; i < n; ++i){
                string curStr = q.front();
                q.pop();
                if (curStr == target){
                    return step;
                }
                //四位密码锁,每个位置每次都可以转一次
                for(int j = 0; j < 4; ++j){
                    string newStr1 = curStr, newStr2 = curStr;
                    //当前位置可以向前或者向后拨一位
                    newStr1[j] = newStr1[j] == '9' ? '0' : newStr1[j] + 1;
                    newStr2[j] = newStr2[j] == '0' ? '9' : newStr2[j] - 1;
                    //count看在不在集合内,find查找位置,此处用两者均可
                    //if(deadendsSet.find(newStr1) == deadendsSet.end() && book.find(newStr1) == book.end()){
                    if(deadendsSet.count(newStr1) == 0 && book.count(newStr1) == 0){
                        q.push(newStr1);
                        book.insert(newStr1);
                    }
                    if(deadendsSet.find(newStr2) == deadendsSet.end() && book.find(newStr2) == book.end()){
                        q.push(newStr2);
                        book.insert(newStr2);
                    }
                }
            }
            ++step;
        }
        return -1;
    }
};

int main(){
    vector<string> deadends = {"0201", "0101", "0102", "1212", "2002"};
    string target = "0202";
    Solution S;
    cout << S.openLock(deadends, target) << endl;

    vector<string> deadends1 = {"8888"};
    string target1 = "0009";
    cout << S.openLock(deadends1, target1) << endl;

    vector<string> deadends2 = {"8887", "8889", "8878", "8898", "8788", "8988", "7888", "9888"};
    string target2 = "8888";
    cout << S.openLock(deadends2, target2) << endl;

    vector<string> deadends3 = {"0000"};
    string target3 = "8888";
    cout << S.openLock(deadends3, target3) << endl;

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值