简介
在二叉树中,每个节点除了有左子节点和右子节点外,还可以有一个 next
指针,指向同一层中右侧的节点。填充 next
指针是一个经典的算法问题,常见于面试和编程竞赛中。本文将详细探讨如何在不同类型的二叉树中填充 next
指针,并分析其时间复杂度和空间复杂度。题目来源:牛客网
问题描述
给定一个二叉树,填充所有节点的 next
指针,使其指向同一层中右侧的节点。如果右侧没有节点,则 next
指针应设置为 NULL
。初始时,所有 next
指针都为 NULL
。
二叉树节点定义
struct TreeLinkNode {
TreeLinkNode *left;
TreeLinkNode *right;
TreeLinkNode *next;
};
问题分类
-
完美二叉树:
- 所有叶子节点都位于同一层。
- 每个父节点都有两个子节点。
-
任意二叉树:
- 节点可能只有一个子节点,或者没有子节点。
- 叶子节点不一定位于同一层。
思路解析
完美二叉树的解法
对于完美二叉树,我们可以利用其结构特点,逐层遍历并连接 next
指针。具体步骤如下:
-
逐层遍历:
- 从根节点开始,逐层向下遍历。
- 对于每一层,从左到右连接
next
指针。
-
连接
next
指针:- 对于每个节点,将其左子节点的
next
指向右子节点。 - 如果当前节点有
next
节点,则将其右子节点的next
指向next
节点的左子节点。
- 对于每个节点,将其左子节点的
代码实现
class Solution {
public:
void connect(TreeLinkNode *root) {
if (!root) return;
TreeLinkNode *p = root, *q;
while (p->left) {
q = p;
while (q) {
q->left->next = q->right;
if (q->next)
q->right->next = q->next->left;
q = q->next;
}
p = p->left;
}
}
};
复杂度分析
- 时间复杂度:O(N),其中 N 是节点数。每个节点只被访问一次。
- 空间复杂度:O(1),只使用了常量级的额外空间。
任意二叉树的解法
对于任意二叉树,由于节点的子节点可能不完整,我们需要更通用的方法来连接 next
指针。具体步骤如下:
-
逐层遍历:
- 从根节点开始,逐层向下遍历。
- 对于每一层,从左到右连接
next
指针。
-
连接
next
指针:- 对于每个节点,根据其子节点情况连接
next
指针。 - 如果当前节点有
next
节点,则将其子节点的next
指向next
节点的子节点。
- 对于每个节点,根据其子节点情况连接
代码实现
class Solution {
public:
void connect(TreeLinkNode *root) {
TreeLinkNode *p = root, *q;
if (!p) return;
while (p) {
q = p;
while (q) {
int ret = abcd(q);
switch (ret) {
case 1:
q->left->next = q->right;
if (q->next) {
q->right->next = getNextChild(q->next);
}
break;
case 2:
if (q->next) {
q->left->next = getNextChild(q->next);
}
break;
case 3:
if (q->next) {
q->right->next = getNextChild(q->next);
}
break;
case 4:
break;
}
q = q->next;
}
p = getNextChild(p);
}
}
// 获取下一个有子节点的节点
TreeLinkNode* getNextChild(TreeLinkNode* node) {
while (node) {
if (node->left) return node->left;
if (node->right) return node->right;
node = node->next;
}
return nullptr;
}
// 判断节点的子节点情况
int abcd(TreeLinkNode *p) {
if (p->left) {
if (p->right) {
return 1;
} else {
return 2;
}
} else {
if (p->right) {
return 3;
} else {
return 4;
}
}
}
};
复杂度分析
- 时间复杂度:O(N),其中 N 是节点数。每个节点只被访问一次。
- 空间复杂度:O(1),只使用了常量级的额外空间。
语法介绍
重要语法点
语法点 | 说明 |
---|---|
switch-case 语句 | 用于多分支选择,根据表达式的值执行不同的代码块。 |
while 循环 | 用于重复执行代码块,直到条件不满足。 |
if-else 语句 | 用于条件判断,根据条件执行不同的代码块。 |
return 语句 | 用于从函数中返回值或结束函数执行。 |
nullptr | 表示空指针,用于初始化或比较指针是否为空。 |
扩展思考
1. 递归解法
对于完美二叉树,我们也可以使用递归的方法来连接 next
指针。递归解法的核心思想是:
- 递归连接子节点:
- 对于每个节点,递归连接其左子节点和右子节点的
next
指针。 - 如果当前节点有
next
节点,则将其右子节点的next
指向next
节点的左子节点。
- 对于每个节点,递归连接其左子节点和右子节点的
代码实现
class Solution {
public:
void connect(TreeLinkNode *root) {
if (!root) return; // 如果当前节点为空,直接返回
if (root->left) { // 如果当前节点有左子节点
root->left->next = root->right; // 左子节点的 next 指向右子节点
if (root->next) { // 如果当前节点有 next 节点
root->right->next = root->next->left; // 右子节点的 next 指向 next 节点的左子节点
}
}
connect(root->left); // 递归处理左子树
connect(root->right); // 递归处理右子树
}
};
复杂度分析
- 时间复杂度:O(N),其中 N 是节点数。每个节点只被访问一次。
- 空间复杂度:O(H),其中 H 是树的高度。递归调用栈的深度为树的高度。
2. 层次遍历解法
对于任意二叉树,我们还可以使用层次遍历的方法来连接 next
指针。层次遍历解法的核心思想是:
- 使用队列逐层遍历:
- 将每一层的节点加入队列,并连接
next
指针。 - 对于每一层,从左到右连接
next
指针。
- 将每一层的节点加入队列,并连接
代码实现
class Solution {
public:
void connect(TreeLinkNode *root) {
if (!root) return; // 如果根节点为空,直接返回
// 使用队列进行层次遍历(BFS)
queue<TreeLinkNode*> q;
q.push(root); // 将根节点加入队列
while (!q.empty()) { // 当队列不为空时,继续遍历
int levelSize = q.size(); // 获取当前层的节点数
TreeLinkNode* prev = nullptr; // 用于记录当前层的前一个节点
// 遍历当前层的所有节点
for (int i = 0; i < levelSize; i++) {
TreeLinkNode* curr = q.front(); // 取出队列中的第一个节点
q.pop(); // 将该节点从队列中移除
// 如果 prev 不为空,将 prev 的 next 指向当前节点
if (prev) {
prev->next = curr;
}
prev = curr; // 更新 prev 为当前节点
// 将当前节点的左子节点加入队列(如果存在)
if (curr->left) q.push(curr->left);
// 将当前节点的右子节点加入队列(如果存在)
if (curr->right) q.push(curr->right);
}
// 当前层遍历结束后,将最后一个节点的 next 置为 NULL
if (prev) {
prev->next = nullptr;
}
}
}
};
复杂度分析
- 时间复杂度:O(N),其中 N 是节点数。每个节点只被访问一次。
- 空间复杂度:O(M),其中 M 是树的最大宽度。队列中最多存储一层的节点。
总结
本文详细探讨了如何在完美二叉树和任意二叉树中填充 next
指针,并提供了多种解法。通过分析不同解法的时间复杂度和空间复杂度,我们可以根据具体问题选择合适的算法。希望本文能帮助你更好地理解二叉树的相关算法,并在实际编程中灵活运用。
关于C++指针,更多内容请参考:C++ 指针大全:从基础到进阶,一篇快速上手!
关于二叉树深度探索,更多内容请参考:C++二叉树深度探索:DFS函数详解与应用实例
关注专栏:每日一题:C++ LeetCode精讲,和我一起进步
点个赞吧~,期待你的关注