#诗经·秦风·权舆
於我乎,夏屋渠渠,今也每食无奈。于嗟乎,不承权舆。
於我乎,梅食四簋,今也每食不饱。于嗟乎,不承权舆。
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.展望
- 关于树(图)目前接触为广度和深度遍历,都要利用递归向下发展,所有的题都是基于这两种遍历模式去开发;
- 非常重要的两点,继续刷题;
- 算法看懂思想比代码实现更重要,自己的知识层面已经到了“郝斌”老师讲过的从写不了到看得懂的过渡;
- 继续加油,2020-6-11终于回学校了,加油减肥,加油刷题。