二叉树
做题思路
1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse
函数配合外部变量来实现。
2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值。
3、无论使用哪一种思维模式,你都要明白二叉树的每一个节点需要做什么,需要在什么时候(前中后序)做。
动归/DFS/回溯算法都可以看做二叉树问题的扩展,只是它们的关注点不同:
- 动态规划算法属于分解问题的思路,它的关注点在整棵「子树」。
- 回溯算法属于遍历的思路,它的关注点在节点间的「树枝」。
- DFS 算法属于遍历的思路,它的关注点在单个「节点」。
二叉树的遍历
1.前序遍历
[遍历]:
vector<int> res;
//前序遍历结果
vector<int> preorderTraverse(TreeNode* root){
traverse(root);
return res;
}
void traverse(TreeNode* root){
if(root == nullptr){
return;
}
//前序位置
res.push_back(root->val);
traverse(root->left);
traverse(root->right);
}
[分解]:
vector<int>preOrderTraverse(TreeNode* root){
vector<int> res;
if(root == nullptr){
return res;
}
//前序遍历结果,root->val在第一个
res.push_back(root->val);
//利用函数定义,后面接着左子树的前序遍历结果
vector<int> leftRes = preOrderTraverse(root->left);
res.insert(res.end(),leftRes.begin(),leftRes.end());
//利用函数定义,最后接着右子树的前序遍历结果
vector<int> rightRes = preOrderTraverse(root->right);
res.insert(res.end(),rightRes.begin(),rightRes.end());
return res;
}
2.层序遍历
方法1:
void levelTraverse(TreeNode* root){
if(root == nullptr){
return;
}
queue<TreeNode*> q;
q.push(root);
//从上到下遍历每一层
while(!q.empty()){
int sz = q.size();
//从左到右遍历每一层的节点
for(int i = 0; i < sz; i++){
TreeNode* cur = q.front();
q.pop();
//将下一层节点放入队列
if(cur->left != nullptr){
q.push(cur->left);
}
if(cur->right != nullptr){
q.push(cur->right);
}
}
}
}
方法2:
vector<vector<int>> levelTraverse(TreeNode* root){
if(root == nullptr){
return res;
}
//root视为第0层
traverse(root,0);
return res;
}
void traverse(TreeNode* root, int depth){
if(root == nullptr){
return;
}
if(res.size() <= depth){
//第一次进入depth层
res.push_back(vector<int>());
}
res[depth].push_back(root->val);
traverse(root->left,depth+1);
traverse(root->right,depth+1);
}
方法3:
vector<vector<int>> res;
vector<vector<int>> levelTraverse(TreeNode* root){
if(root == nullptr){
return res;
}
//应用队列实现层序遍历
list<TreeNode*> nodes;
nodes.push_back(root);
traverse(nodes);
return res;
}
//递归函数实现层序遍历,利用双向队列实现BFS
void traverse(list<TreeNode*> curLevelNodes){
//判断传入队列是否为空
if(curLevelNodes.empty()){
return;
}
vector<int> nodeValues; //储存当前队列节点值
//下一层的节点列表
list<TreeNode*> nextLevelNodes;
//从双向队列中取节点
for(auto& node : curLevelNodes){
nodeValues.push_back(node->val);
//如果有左儿子,右儿子都插入队列
if(node->left != nullptr){
nextLevelNodes.push_back(node->left);
}
if(node->right != nullptr){
nextLevelNodes.push_back(node->right);
}
}
//将当前层的节点值添加到结果中
res.push_back(nodeValues);
//继续遍历下一层
traverse(nextLevelNodes);
}
BFS
int BFS(Node start, Node target) {
queue<Node> q;
set<Node> visited;
q.push(start);
visited.insert(start);
while (!q.empty()) {
int sz = q.size();
for (int i = 0; i < sz; i++) {
Node cur = q.front();
q.pop();
if (cur == target)
return step;
for (Node x : cur.adj()) {
if (visited.count(x) == 0) {
q.push(x);
visited.insert(x);
}
}
}
}
// 如果走到这里,说明在图中没有找到目标节点
}
支配树
namespace dtree // 支配树模板
{
const int MAXN = 500020;
vector<int> E[MAXN], RE[MAXN], rdom[MAXN];
int S[MAXN], RS[MAXN], cs;
int par[MAXN], val[MAXN], sdom[MAXN], rp[MAXN], dom[MAXN];
void clear(int n)
{
cs = 0;
for (int i = 0; i <= n; i++)
{
par[i] = val[i] = sdom[i] = rp[i] = dom[i] = S[i] = RS[i] = 0;
E[i].clear();
RE[i].clear();
rdom[i].clear();
}
}
void add_edge(int x, int y) { E[x].push_back(y); }
void Union(int x, int y) { par[x] = y; }
int Find(int x, int c = 0)
{
if (par[x] == x)
return c ? -1 : x;
int p = Find(par[x], 1);
if (p == -1)
return c ? par[x] : val[x];
if (sdom[val[x]] > sdom[val[par[x]]])
val[x] = val[par[x]];
par[x] = p;
return c ? p : val[x];
}
void dfs(int x)
{
RS[S[x] = ++cs] = x;
par[cs] = sdom[cs] = val[cs] = cs;
for (int e : E[x])
{
if (S[e] == 0)
dfs(e), rp[S[e]] = S[x];
RE[S[e]].push_back(S[x]);
}
}
int solve(int s, int *up)
{
dfs(s);
for (int i = cs; i; i--)
{
for (int e : RE[i])
sdom[i] = min(sdom[i], sdom[Find(e)]);
if (i > 1)
rdom[sdom[i]].push_back(i);
for (int e : rdom[i])
{
int p = Find(e);
if (sdom[p] == i)
dom[e] = i;
else
dom[e] = p;
}
if (i > 1)
Union(i, rp[i]);
}
for (int i = 2; i <= cs; i++)
if (sdom[i] != dom[i])
dom[i] = dom[dom[i]];
for (int i = 2; i <= cs; i++)
up[RS[i]] = RS[dom[i]];
return cs;
}
}