leetcode树(图)的广度优先和深度优先---2020第一版

#诗经·秦风·权舆
於我乎,夏屋渠渠,今也每食无奈。于嗟乎,不承权舆。
於我乎,梅食四簋,今也每食不饱。于嗟乎,不承权舆。

0.树

\qquad 递归的时候需要利用树的结构来解释回溯和分治的路线。而树本身就分为广度优先遍历和深度优先遍历。深度优先遍历又细分为前序遍历中序遍历后续遍历

1.树的深度优先遍历

1.1 路径总和

在这里插入图片描述

#include<vector>
void preorder(TreeNode* node, int sum, vector<vector<int>>&result,vector<int>&path,int& path_value)
{
	if(node==NULL) return;
	path.push_back(node->val);
	path_value += node->val;
	if(node->left==NULL && node->right==NULL && path_value==sum)
	{
		result.push_back(path);
	}
	preorder(node->left,sum,result,path,path_value);
	preorder(node->right,sum,result,path,path_value);
	path_value -= node->value;
	path.pop_back();
}
vector<vector<int>> pathSum(TreeNode* root, int sum)
{
	vector<vector<int>> result;
	vector<int> path; //数组栈
	int path_value;
	preorder(root,sum,result,path,path_value);
	return result;	
}

1.2 公共最近父节点在这里插入图片描述

算法思路:
利用前序遍历找到对应节点的路径,再利用指针找到两条路径最后的公共节点。
#include<vector>
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) 
    {
        vector<TreeNode*> path;
        vector<TreeNode*> path_p;
        vector<TreeNode*> path_q;       
        int flag=0;
        preorder(root,p,path,path_p,flag); //利用前序遍历得到p的路径
        path.clear();
        flag = 0;
        preorder(root,q,path,path_q,flag);//利用前序遍历得到q的路径
        TreeNode* res;
        int min_len = min(path_p.size(),path_q.size());
        for(int i=0; i<min_len;++i)  
        {
            if(path_p[i]==path_q[i]) //通过指针后移找到p,q的最后公共节点
            {
                res = path_p[i];  
            }
        }

        return res;
    }
private:
    void preorder(TreeNode* node, TreeNode* search,vector<TreeNode*>&path,vector<TreeNode*>&result,int&flag)
    {//path为过程记录,result为最终路径,//flag为过程中找到与否调节阀
        if(node==NULL || flag==1)  return; 
        //遍历到叶节点或者调节阀关闭,递归出口
        path.push_back(node); //节点入栈
        if(node==search) //在树的某个节点上找到目标
        {
            flag = 1;//将递归调至完成方向
            result = path;//将过程路径作为最终结果
        }
        preorder(node->left,search,path,result,flag);
        preorder(node->right,search,path,result,flag);
        path.pop_back(); 
        //完成左右节点递归后,并且未找到递归出口,则将当前节点弹出
    }
};

1.3 树原地转链表

在这里插入图片描述

//解法一:忽略原地,使用额外的vector辅助来转链表
void flatten(TreeNode* root)
{
	vector<TreeNode*> result;
	preorder(root,result);
	for(int i=1;i<result.size();++i)
	{
		result[i-1].left = NULL; //将左指针置空
		result[i-1].right = result[i]; //右指针指向下一个
	}
}
void preorder(TreeNode* node, vector<TreeNode*>&result)
{
	if(node==NULL) return;
	result.push_back(node);
	preorder(node->left,result);
	preorder(node->right,result);
}

在这里插入图片描述

//解法二,原地操作
void flatten(TreeNode*root)
{
	TreeNode* last = NULL;
	preorder(root,last);
}
void preorder(TreeNode* node, TreeNode*& last)
{
	if(node==NULL) return;
	if(node->left==NULL && node->right==NULL)
	{
		last = node;
		return;
	}	
	TreeNode* left = node->left;//临时记录左节点
	TreeNode* right = node->right;//临时记录右节点
	TreeNode* left_last = NULL;
	TreeNode* right_last = NULL;
	if(left)
	{
		preorder(left,left_last);
		node->left = NULL; //当前节点与
		node->right = left;
		last = left_last; //将根节点的last更新为左孩子最后的left_last
	}
	if(right)
	{
		preorder(right,right_last)
		{
			if(left_last)  left_last = right;
			last = right_last; //将根节点的last更新为右节点的last
		}
	}
}

2.树的广度优先遍历

2.1广度优先遍历基础,借助队列实现

void BFS_print(TreeNode* root)
{
	queue<TreeNode*> que;
	que.push(root);
	while(!que.empty())
	{
		TreeNode* cur = que.front();
		cout<< cur->val <<'\t';
		que.pop();
		if(cur->left)
		{
			que.push(cur);
		}
		if(cur->right)
		{
			que.push(cur);
		}
	}
}

2.2 广度优先搜索的右视树在这里插入图片描述

在广度优先遍历时,利用pair对记录层数和更新层的最后值
vector<int> rightSideView(TreeNode* root)
{
	Queue<pair<TreeNode*,int>> Q;
	//用于记录节点和节点所在层数
	vector<int> result;
	//用于记录每层最后一个数
	if(root)
	{
		Q.push(make_pair(root,0));
	}
	//广度优先遍历,在广度优先搜索的时候记录每层最后的元素
	while(!Q.empty())
	{
		TreeNode* node = Q.front().frist();
		int depth = Q.front.second();
		Q.pop();
		if(depth==result.size())
		{//深度达到result的长度,说明新的一层来到
			result.push_back(node->val);
		}
		else
		{
			result[depth] = node->val;
		}
		if(node->left)
		{
			Q.push(node->left,depth+1);
		}
		if(node->right)
		{
			Q.push(node->right,depth+1);
		}
	}
	return result;
}

3.图的广度和深度遍历

3.1图的基础概念

\quad 图(Graph)由顶点(vertex)和边(edge)构成,简记为G(V,E),又分为无向图和有向图,根据边的长度又分为带权图和无权图。
\quad 对于图的描述可以使用邻接矩阵邻接表来表示,在邻接矩阵的表示中通常为稀疏矩阵,空间复杂度较高,所以通常选用邻接表来描述图。

3.2 图的深度优先遍历

在这里插入图片描述

struct GraphNode
{
	int val; //顶点
	vector<GraphNode*> neighbor; //邻接表
	GraphNode(int x): val(x){	} 	
};
void DFS_graph(GraphNode* node, int visit[])
{
	visit[node->val] = 1; //当前节点访问阀记为1
	cout<<node->val<<'\t';
	for(int i=0; i<node->neighbor.size();++i)
	{//遍历当前节点的邻接表
		if(visit[node->neighbor->val]==0)
		{
			DFS_graph[node->neighbor[i],visit];
		}
	}	
}
int visit[MAX_LEN] = {0}; //遍历没有控制阀
for(int i=0; i<MAX_LEN; ++i)
{
	if(visit[i]==0)
	{
		cout<<"form"<<Graph[i]->val);
		DFS_graph(Graph[i],visit);
		cout<<'\n';
	}	
}

3.3 图的宽度优先遍历在这里插入图片描述

void BFS_graph(GraphNode* node,int visit[])
{
	queue<GraphNode*> Q;
	Q.push(node);
	visit[node->val]==1;
	while(!Q.empty())
	{
		GraphNode* node = Q.front();
		Q.pop();
		cout<<node->val<<'\n';
		for(int i=0; i<node->neighbor.size();++i)
		{
			if(visit[node->neighbor[i]->val]==0)
			{
				Q.push(node->neighbor[i]);
				visit[node->neighbor[i]->val]=1;
			}
		}		
	}
}

3.4 图的深度(广度优先)判断环路

在这里插入图片描述

用visit记录三种转态:
-1:未访问节点,
0: 正在访问
1: 访问过

深度优先遍历,利用visit数组设置-1,0,1阀,记录节点的当前状态,从0到0即视为有环路

bool canFinish(int numCourses, vector<vector<int>>& prerequisites) 
{
    vector<int> flag(numCourses,-1); //控制阀 -1:表示未,0:正在,1:搜索完成
    vector<vector<int>> graph(numCourses);//创建图
    if(prerequisites.empty()) return true;
    for(int i=0; i<prerequisites.size();++i)
    {
        graph[prerequisites[i][0]].push_back(prerequisites[i][1]); //邻接表
    }
    bool result = true;//与 存结果
    for(int i=0; i<graph.size();++i)
    {
        result = result&DFS(i,flag,graph);//遍历图的每一个节点
    }
    return result;
}
bool DFS(int i, vector<int>& flag, vector<vector<int>>&graph)
{
    if(flag[i]==1)
        return true;
    if(flag[i]==0)
        return false;
    
    // 遍历i所在的邻接表
    flag[i] = 0;
    for(int j=0; j<graph[i].size(); ++j)
    {
        if(DFS(graph[i][j],flag,graph))
            continue; //可以遍历,说明无环
        return false;           
    }
    flag[i] = 1; //遍历邻接表完成
    return true;
}

广度优先遍历:
利用度来衡量环路,只将入度为0的点入队,
完成一个节点的搜索,将它指向的所有节点的度均减1,
再将所有指向节点中度数为0的节点入队,
完成广搜后,所有节点的度数均为0,则无环路,否则有环。(j记录入队数和总数是否相等)。

  class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) 
    {
        vector<int> indegree(numCourses,0);
        vector<vector<int>> graph(numCourses,vector<int>());
        for(int i=0; i<prerequisites.size(); ++i)
        {
            ++indegree[prerequisites[i][0]];//建立入度表
            graph[prerequisites[i][1]].push_back(prerequisites[i][0]); //建立邻接表
        }
        queue<int> Q;  //建立入队队列
        int count = 0; //统计入队数
        for(int i=0; i<numCourses;++i)
        {
            if(indegree[i]==0)
                Q.push(i);   //将入度为0的节点入队
        }
        while(!Q.empty())
        {
            int temp = Q.front();
            Q.pop();
            ++count;
            for(int j=0;j<graph[temp].size();++j)
            {
                --indegree[graph[temp][j]];
                if(indegree[graph[temp][j]]==0)
                    Q.push(graph[temp][j]);
            }
        }
        return count==numCourses;
    }
};

4.展望

  1. 关于树(图)目前接触为广度和深度遍历,都要利用递归向下发展,所有的题都是基于这两种遍历模式去开发;
  2. 非常重要的两点,继续刷题;
  3. 算法看懂思想比代码实现更重要,自己的知识层面已经到了“郝斌”老师讲过的从写不了看得懂的过渡;
  4. 继续加油,2020-6-11终于回学校了,加油减肥,加油刷题。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值