题目

题目来源:力扣(LeetCode)

给定一个二叉树

struct Node {
int val;
Node left;
Node
right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。

初始状态下,所有 next 指针都被设置为 NULL。

进阶:

你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。

示例:
image.png

输入:root = [1,2,3,4,5,null,7]
输出:[1,#,2,3,#,4,5,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化输出按层序遍历顺序(由 next 指针连接),’#’ 表示每层的末尾。

广度优先搜索

在树中,广度优先搜索按照层的顺序遍历二叉树,在遍历第 i 层前,一定会先遍历完第 i - 1 层。

我们可以设计如下的算法:初始化一个队列 queue,首先将根节点放入队列中。当队列不为空的时候,记录当前队列大小为 size,从队列中取出这 size 个 节点并通过这 size 个元素拓展下一层新节点。如此循环,直到队列为空。

这样可以保证每次遍历的 size 个节点都是同一层的。我们在遍历每一层的时候修改这一层节点的 next 指针,将每一层连接成一个链表。

代码

  1. /**
  2. * // Definition for a Node.
  3. * function Node(val, left, right, next) {
  4. * this.val = val === undefined ? null : val;
  5. * this.left = left === undefined ? null : left;
  6. * this.right = right === undefined ? null : right;
  7. * this.next = next === undefined ? null : next;
  8. * };
  9. */
  10. /**
  11. * @param {Node} root
  12. * @return {Node}
  13. */
  14. var connect = function (root) {
  15. if (root === null) {
  16. return null;
  17. }
  18. const queue = [root];
  19. while (queue.length) {
  20. // 每一层的节点数量
  21. const size = queue.length;
  22. let last = null;
  23. for (let i = 1; i <= size; ++i) {
  24. // 出队
  25. let f = queue.shift();
  26. // 左子节点如果不为空,则加入队列
  27. if (f.left !== null) {
  28. queue.push(f.left);
  29. }
  30. // 右子节点如果不为空,则加入队列
  31. if (f.right !== null) {
  32. queue.push(f.right);
  33. }
  34. // 当前遍历的节点不是这一层的第一个节点,则用前一个节点的 next 指针指向它
  35. if (i !== 1) {
  36. last.next = f;
  37. }
  38. last = f;
  39. }
  40. }
  41. return root;
  42. };

使用 next 指针

在 「广度优先搜索」方法中,由于使用了队列来辅助遍历树,因此需要队列的额外空间。我们也可以不使用队列,通过 next 指针去访问每一层的所有节点,从而节省空间。

具体做法是:

  • 从根节点开始。因为第 0 层只有一个节点,不需要处理。可以在上一层为下一层建立 next 指针。该方法最重要的一点是:位于第 x 层时为第 x + 1 层建立 next 指针。一旦完成这些连接操作,移至第 x + 1 层为第 x + 2 层建立 next 指针。
  • 当遍历到某层节点时,该层节点的 next 指针已经建立。这样就不需要队列从而节省空间。每次只要知道下一层的最左边的节点,就可以从该节点开始,像遍历链表一样遍历该层的所有节点。

代码

  1. /**
  2. * // Definition for a Node.
  3. * function Node(val, left, right, next) {
  4. * this.val = val === undefined ? null : val;
  5. * this.left = left === undefined ? null : left;
  6. * this.right = right === undefined ? null : right;
  7. * this.next = next === undefined ? null : next;
  8. * };
  9. */
  10. /**
  11. * @param {Node} root
  12. * @return {Node}
  13. */
  14. var connect = function (root) {
  15. if (root == null) return root;
  16. // 把 cur 看做是每一层的链表
  17. let cur = root;
  18. while (cur != null) {
  19. // 遍历当前层的时候,为了方便操作,在下一层前面添加一个虚拟头结点(注意这里是访问
  20. // 当前层的节点,然后把下一层的节点串起来)
  21. let dummy = new Node(-1);
  22. // pre 表示访问下一层节点的前一个节点
  23. let pre = dummy;
  24. // 开始遍历当前层的链表
  25. while (cur != null) {
  26. if (cur.left != null) {
  27. // 如果当前节点的左子节点不为空,就让 pre 的 next 指针指向左子节点,也就是把它串联起来
  28. pre.next = cur.left;
  29. // 移动 pre 到当前节点的左子节点上
  30. pre = pre.next;
  31. }
  32. if (cur.right != null) {
  33. // 如果当前节点的右子节点不为空,就让 pre 的 next 指针指向右子节点,也就是把它串联起来
  34. pre.next = cur.right;
  35. // 移动 pre 到当前节点的右子节点上
  36. pre = pre.next;
  37. }
  38. // 继续访问这一行的下一个季度
  39. cur = cur.next;
  40. }
  41. // 把下一层串联成一个链表之后,让他赋值给cur,
  42. // 然后续继续循环,直到cur为空为止
  43. cur = dummy.next;
  44. }
  45. return root;
  46. };