广度优先搜索以及多源广度优先搜索 2021-01-24

广度优先搜索以及多源广度优先搜索




前言

         广度优先搜索多用于图和树的遍历,二叉树的层序遍历其实就是一种特殊的广度优先搜索。
         本文先从典型的广度优先搜索开始介绍,之后总结单源点广度优先搜索的应用,最后总结多源点广度优先搜索的基本方法,以及二维网格广度优先搜索的模板。

一、题目描述

主要以如下题目为例(题目来源leetcode)

无脑遍历套模板
1. N叉树的层序遍历
2. 从上到下打印二叉树|
3. 从上到下打印二叉树||
4. 从上到下打印二叉树|||


单源点广度优先搜索的应用
5. 二叉树的堂兄弟节点
6. 填充每一个节点的下一个右侧节点指针


多源点广度优先搜索的应用(多用于二维网格的搜索)
7. 腐烂的橘子
8. 地图分析


全局广度优先搜索(特殊的多源点广度优先搜索,只是应用的场合不同)
9. 水域大小
10. 判断二分图
11. 课程表||

二、方法总结以及代码模板

1.单源点广度优先搜索

    单源点广度优先搜索以一个点为源点,将其优先入队,利用辅助数据结构——队列,在之后实现先进先出顺序的访问。具体伪代码如下:
void bfs(参数1 ...)
{
    
    
	queue<T> q;
	q.push(...);
	while(!q,empty())
	{
    
    
		//先出队 ,实现先进先出的访问
		T temp=q.front();
		q.pop();
		//在考虑刚刚出队的元素的相邻接点,将其相邻节点入队
		//入队时,如果是二叉树,则只需要考虑左右子节点即可
		//对于图或者n叉树,需要结合存储的方式进行遍历(多用for循环)
		for(...)
			q.push(...)		
		//特别注意,在相邻接点入队时需要根据题目要求做出相应调整
		//如当搜索到的某一个节点符合条件时需要及时返回,结束搜索
	}
}
第二种广度优先遍历遍历的模板:带有层次结构的广度优先搜索
void bfs(参数1 ...)
{
    
    
	queue<T> q;
	q.push(...);
	while(!q,empty())
	{
    
    
		int size=q.size();
		for(int i=0;i<size;++i)
		{
    
    
			//先出队 ,实现先进先出的访问
			T temp=q.front();
			q.pop();
			for(...)
				q.push(...)		
		//特别注意,在相邻接点入队时需要根据题目要求做出相应调整
		//如当搜索到的某一个节点符合条件时需要及时返回,结束搜索
		}
	}
}

1.1 第一类典型的层序遍历:N叉树的层序遍历

这道题只需要无脑的套用上述的伪代码模板即可。直接上代码
class Solution {
    
    
public:
    vector<vector<int>> levelOrder(Node* root) 
    {
    
    
        vector<vector<int>> v;
        if(!root)
            return v;
        queue<Node*> q;
        q.push(root);
        while(!q.empty())
        {
    
    
            vector<int> v1;//用于保存每一层的节点的信息
            int size=q.size();
            for(int i=0;i<size;++i)
            {
    
    
                Node* ptr=q.front();
                q.pop();
                v1.push_back(ptr->val);
                //对应上述模板的入队过程
                for(int k=0;k<ptr->children.size();++k)
                    q.push(ptr->children[k]);
            }
            v.push_back(v1);
        }    
        return v;
    }
};

1.2 第二类典型的层序遍历:从上到下打印二叉树|&从上到下打印二叉树||

        二者都属于套用层序遍历模板的题目,不同的是需要注意题目要求。如果要求遍历的结果保持树的层次结构则需要对上述伪代码模板做出改进,即在原来的基础上加入for循环,每一次for循环的作用是清空队列中上一层的元素,并装入下一层元素,使一次for循环遍历的都是同一层的元素,从而使遍历具有层次感。
直接上代码:
class Solution {
    
    
public:
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
    
    
        vector<vector<int>> v;
        if(root==NULL)
            return v;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty())
        {
    
    
            vector<int> v1;
            int size=q.size();
            for(int i=0;i<size;++i)
            {
    
    
                TreeNode* t=q.front();
                q.pop();
                v1.push_back(t->val);    
                if(t->left!=NULL)
                    q.push(t->left);
                if(t->right!=NULL)
                    q.push(t->right);
            }
            v.push_back(v1);
        }
        return v;
    }
};

1.3 第三类层序遍历:

请实现一个函数按照之字形顺序打印二叉树
即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印
第三行再按照从左到右的顺序打印,其他行以此类推。
        只需要在原来模板的基础上维护一个变量,标记当前是奇数层还是偶数 层。从而采取不同的访问策略。另外此题根据需求应该使用双端队列。直接上代码:
class Solution {
    
    
public:
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
    
    
        vector<vector<int>> v;
        if(root==NULL)
            return v;
        deque<TreeNode*> q;
        q.push_back(root);
        int count=1;
        while(!q.empty())
        {
    
    
            int size=q.size();
            vector<int> v1;
            for(int i=0;i<size;++i)
            {
    
    
                if(count%2==0)
                {
    
    
                    TreeNode* ptr=q.back();
                    q.pop_back();
                    //注意从后面取出的,应该从队头插入
     //由于要求插入后左节点在右节点的前面,因此先插入右节点。
     //可以直接想想二叉树的结构进行插入               
                    if(ptr->right) q.push_front(ptr->right);
                    if(ptr->left) q.push_front(ptr->left);
                    v1.push_back(ptr->val);
                }
                else 
                {
    
    
                    TreeNode* ptr=q.front();
                    q.pop_front();
                    if(ptr->left) q.push_back(ptr->left);
                    if(ptr->right) q.push_back(ptr->right);
                    v1.push_back(ptr->val);
                }
            }
            count++;
            v.push_back(v1);
        }
        return v;
    }
};

2. 单源点广度优先搜素的应用

       在此仅仅提供思路:
对于第一道题:额外维护两个变量用户标记待查找元素x和y的父节点。之后根据“堂兄弟”的定义进行判断
对于第二道题:在“带有层次结构的广度优先搜索模板”的基础上,每当开始遍历新的一层时,维护一个头指针,之后利用这根指针将一层的节点串起来。贴一个优雅的代码:

class Solution {
    
    
public:
    Node* connect(Node* root) 
    {
    
    
        if(!root)
            return NULL;
        queue<Node*> q;
        q.push(root);
        Node node;
        while(!q.empty())
        {
    
    
            int size=q.size();
            Node* temp=&node;
            for(int i=0;i<size;++i)
            {
    
    
                Node* ptr=q.front();
                q.pop();
                //典型的链表操作
                temp->next=ptr;
                temp=temp->next;
                if(ptr->left)
                    q.push(ptr->left);
                if(ptr->right)
                    q.push(ptr->right);
            }
        }
        return root;
    }
};

3. 多源点广度优先搜索

3.1先给出定义

       多源广度优先搜索:从多个起点开始同时搜索,将整个原始搜索域分为目标点和源点。每一个源点都可以开始搜索相应的目标点。

3.2 方法

       解决这类题目最简单的方法就是抽象出来一个超级源点,之后多源点广度优先搜索为单源点广度优先搜素。
       二叉树的遍历相当于只有一个源点——root,因此初始化时队列中相当于就只有一个元素root。而多源点搜索抽象出了一个超级源点O,O点的邻接点就是实际的图中的源点。因此初始化队列时需要将所有符合条件的源点全部入队,达到所有源点同时搜索的效果。


详见链接:
腐烂的橘子——官方讲解

地图分析——官方讲解

       在此给出一个二维网格搜索的比较优雅的模板:以题目“地图分析”为例
class Solution {
    
    
public:
	int n,m;//定义成类中的成员,便于不同函数的直接访问
	int a[4][2]={
    
    {
    
    1,0},{
    
    -1,0},{
    
    0,1},{
    
    0,-1}};//用于标记之后可以移动的四个方向

	//vector<vector<int>> v
    //有些题目中可能维护一个等大的二维网格,先初始化为全零,之后用于标
    //记是否到达过了。
    //初始化是直接调用vector类的函数resize
    //v.resize(n,vector<m,0))即可
    int maxDistance(vector<vector<int>>& grid)
    {
    
    //多源广度优先搜索
        int n = grid.size();
        int m = grid[0].size();
        queue<pair<int, int>> q;
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                if (grid[i][j] == 1)
                    q.push({
    
     i,j });
        int count = -1;
        if(q.size()==n*m)
            return -1;
        while (!q.empty())
        {
    
    
            int size = q.size();
            for (int i = 0; i < size; ++i)
            {
    
    
                pair<int, int> cp = q.front();
                q.pop();
                for(int i=0;i<4;++i)
                {
    
    
                	int x=cp.first+a[i][0],y=cp.second+a[i][1];
                	if(x>=0&&x<n&&y>=0&&y<m&&grid[x][y]==0)
                	{
    
    
                		  q.push({
    
    x,y});
                    	  grid[x][y]=1;
                	}
                }
            }
            ++count;
        }
        return count;
    }
};


三. 总结

以上就是广度优先搜索的基本分类,本文仅仅简单介绍了广度优先搜索的基本应用。算法题目多种多样,而广度优先的模板就是应用辅助数据结构——队列,实现先进先访问的需求,即先将源点入队,之后每弹出一个元素都需要考虑该元素的相邻元素是否需要入队。

猜你喜欢

转载自blog.csdn.net/sddxszl/article/details/113066477
今日推荐