X Tutup
# 二叉树 ## 知识点 ### 二叉树遍历 **前序遍历**:**先访问根节点**,再前序遍历左子树,再前序遍历右子树 **中序遍历**:先中序遍历左子树,**再访问根节点**,再中序遍历右子树 **后序遍历**:先后序遍历左子树,再后序遍历右子树,**再访问根节点** 注意点 - 以根访问顺序决定是什么遍历 - 左子树都是优先右子树 #### 前序递归 ```c++ void preorderTraversal(TreeNode *root) { if (!root) { return; } cout << root->val << " "; preOrderTraversal(root->left); preOrderTraversal(root->right); } ``` #### 前序非递归 ```c++ // V3:通过非递归遍历 void preorderTraversal(TreeNode *root) { if (!root) { return; } auto stack = vector(); while (root || !stack.empty()) { if (!root) { root = stack.back(); stack.pop_back(); } cout << root->val << " "; if (root->right) { stack.push_back(root->right); } root = root->left; } } ``` #### 中序非递归 ```c++ // 思路:通过stack 保存已经访问的元素,用于原路返回 void inorderTraversal(TreeNode *root) { if (!root) { return; } auto stack = vector(); while (root || !stack.empty()) { while (root) { stack.push_back(root); root = root->left; } root = stack.back(); stack.pop_back(); cout << root->val << " "; root = root->right; } } ``` #### 后序非递归 ```c++ void postorderTraversal(TreeNode *root) { if (!root) { return; } auto stack = vector(); TreeNode *lastPop = nullptr; while (root || !stack.empty()) { // 每次找到最左下节点 while (root) { stack.push_back(root); root = root->left; } root = stack.back(); // 后序遍历,不能直接弹出,先检查是否有右子节点 // 右节点要注意有可能已经遍历过了,又撤回来到当前节点 // 只要保存上次弹出的节点即可!上次弹出的必定是其右子节点 if (root->right && root->right != lastPop) { root = root->right; continue; } cout << root->val << " "; stack.pop_back(); lastPop = root; root = nullptr; } } ``` 注意点 - 核心就是:根节点必须在右节点弹出之后,再弹出 #### DFS 深度搜索-从上到下 ```c++ struct TreeNode { int val; TreeNode *left; TreeNode *right; }; vector preOrderTraversal(TreeNode *root) { vector result; dfs(root, result); return result; } void dfs(TreeNode *root, vector &result) { if (!root) { return; } result.push_back(root->val); dfs(root->left, result); dfs(root->right, result); } ``` #### DFS 深度搜索-从下向上(分治法) ```c++ // V2:通过分治法遍历 vector preOrderTraversal(TreeNode *root) { return divideAndConquer(root); } vector divideAndConquer(TreeNode *root) { vector result; if (!root) { return result; } // 分治(Divide) auto left = divideAndConquer(root->left); auto right = divideAndConquer(root->right); // 合并结果(Conquer) result.push_back(root->val); result.insert(result.end(), left.begin(), left.end()); result.insert(result.end(), right.begin(), right.end()); return result; } ``` 注意点: > DFS 深度搜索(从上到下) 和分治法区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并 #### BFS 层次遍历 ```c++ vector levelOrder(TreeNode *root) { vector result; if (!root) { return result; } queue queue; queue.push(root); while (!queue.empty()) { root = queue.front(); result.push_back(root->val); if (root->left) { queue.push(root->left); } if (root->right) { queue.push(root->right); } queue.pop(); } return result; } ``` ### 分治法应用 先分别处理局部,再合并结果 适用场景 - 快速排序 - 归并排序 - 二叉树相关问题 分治法模板 - 递归返回条件 - 分段处理 - 合并结果 ```c++ ResultType traversal(TreeNode *root) { // nil or leaf if (!root) { // do something and return } // Divide auto left = traversal(root->left); auto right = traversal(root->right); // Conquer auto result = merge(left, right); return result; } ``` #### 典型示例 ```c++ // V2:通过分治法遍历二叉树 vector preOrderTraversal(TreeNode *root) { return divideAndConquer(root); } vector divideAndConquer(TreeNode *root) { vector result; if (!root) { return result; } // 分治(Divide) auto left = divideAndConquer(root->left); auto right = divideAndConquer(root->right); // 合并结果(Conquer) result.push_back(root->val); result.insert(result.end(), left.begin(), left.end()); result.insert(result.end(), right.begin(), right.end()); return result; } ``` #### 归并排序   ```c++ template static void MergeSort(T arr[], int len) { auto tmp = new T[len]; mergeSort(arr, 0, len - 1, tmp); delete[] tmp; } template static void mergeSort(T arr[], int begin, int end, T tmp[]) { if (begin + 1 >= end) { return; } auto mid = begin + (end - begin) / 2; auto begin1 = begin; auto end1 = mid; auto begin2 = mid + 1; auto end2 = end; mergeSort(arr, begin1, end1, tmp); mergeSort(arr, begin2, end2, tmp); // merge two parts auto index = begin; while (begin1 <= end1 && begin2 <= end2) { tmp[index++] = arr[begin1] < arr[begin2] ? arr[begin1++] : arr[begin2++]; } while (begin1 <= end1) { tmp[index++] = arr[begin1++]; } while (begin2 <= end2) { tmp[index++] = arr[begin2++]; } for (int i = begin; i <= end; ++i) { arr[i] = tmp[i]; } } ``` 注意点 > 递归需要返回结果用于合并 #### 快速排序   ```c++ template static void QuickSort(T arr[], int len) { quickSort(arr, 0, len - 1); } template static void quickSort(T arr[], int begin, int end) { if (begin >= end) { return; } auto pivot = partition(arr, begin, end); quickSort(arr, begin, pivot - 1); quickSort(arr, pivot + 1, end); } template static int partition(T arr[], int begin, int end) { auto base = arr[end]; auto lessInsert = begin; for (int i = begin; i < end; ++i) { if (arr[i] < base) { swap(arr[lessInsert++], arr[i]); } } swap(arr[lessInsert], arr[end]); return lessInsert; } ``` 注意点: > 快排由于是原地交换所以没有合并过程 > 传入的索引是存在的索引(如:0、length-1 等),越界可能导致崩溃 常见题目示例 #### maximum-depth-of-binary-tree [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) > 给定一个二叉树,找出其最大深度。 思路:分治法 ```c++ int maxDepth(TreeNode* root) { if (!root) { return 0; } // divide:分左右子树分别计算 auto leftDepth = maxDepth(root->left); auto rightDepth = maxDepth(root->right); // conquer:合并左右子树结果 return (leftDepth > rightDepth ? leftDepth : rightDepth) + 1; } ``` #### balanced-binary-tree [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) > 给定一个二叉树,判断它是否是高度平衡的二叉树。 思路:分治法,左边平衡 && 右边平衡 && 左右两边高度 <= 1, 因为需要返回是否平衡及高度,要么返回两个数据,要么合并两个数据, 所以用-1 表示不平衡,>0 表示树高度(二义性:一个变量有两种含义)。 ```c++ bool isBalanced(TreeNode *root) { return maxDepth(root) != -1; } int maxDepth(TreeNode *root) { if (!root) { return 0; } auto left = maxDepth(root->left); auto right = maxDepth(root->right); if (left == -1 || right == -1 || left - right > 1 || right - left > 1) { return -1; } return (left > right ? left : right) + 1; } ``` 注意 > 一般工程中,结果通过两个变量来返回,不建议用一个变量表示两种含义 #### binary-tree-maximum-path-sum [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) > 给定一个**非空**二叉树,返回其最大路径和。 思路:分治法,分为三种情况:左子树最大路径和最大,右子树最大路径和最大,左右子树最大加根节点最大,需要保存两个变量:一个保存子树最大路径和,一个保存左右加根节点和,然后比较这个两个变量选择最大值即可 ```c++ int maxPathSum(TreeNode *root) { auto maxSum = std::numeric_limits::min(); calcMaxPath(root, maxSum); return maxSum; } int calcMaxPath(TreeNode *&root, int &maxSum) { if (!root) { return 0; } // 如果左右子树为负,直接丢弃该部分(不需要从一个叶子节点走到另一个叶子)设置为0 auto left = max(calcMaxPath(root->left), 0); auto right = max(calcMaxPath(root->right), 0); // 取当前值与当前"闭环"(左右子树+当前节点)的最大值 // 注意,初始值要设置为最小数! // 子树可以不舍弃负数,但是结果不能(必须至少经过一个节点) // 当前节点为叶子时,right + left + root->val = 0 + 0 + root->val maxSum = max(maxSum, left + right + root->val); // 已经处理过同时涉及左右子树+当前节点的可能 // 对于后续处理,只有一种可能,以当前节点为根的这颗树,取一条路径返回——左右子树最大值 + 当前节点 return max(left, right) + root->val; } ``` #### lowest-common-ancestor-of-a-binary-tree [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) > 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 思路:分治法,有左子树的公共祖先或者有右子树的公共祖先,就返回子树的祖先,否则返回根节点 ```c++ TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if (!root) { return nullptr; } // 相等 直接返回root节点即可 if (root == p || root == q) { return root; } // Divide auto left = lowestCommonAncestor(root->left, p, q); auto right = lowestCommonAncestor(root->right, p, q); // Conquer // 左右两边都不为空,则根节点为祖先 if (left && right) { return root; } return left ? left : right; } ``` ### BFS 层次应用 #### binary-tree-level-order-traversal [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) > 给你一个二叉树,请你返回其按  **层序遍历**  得到的节点值。 (即逐层地,从左到右访问所有节点) 思路:用一个队列记录一层的元素,然后扫描这一层元素添加下一层元素到队列(一个数进去出来一次,所以复杂度 O(logN)) ```c++ vector> levelOrder(TreeNode* root) { vector> ret; if (!root) { return ret; } queue queue; queue.push(root); while (!queue.empty()) { vector levelValues; for (auto levelNodeNum = queue.size(); levelNodeNum > 0; --levelNodeNum) { auto node = queue.front(); queue.pop(); levelValues.push_back(node->val); if (node->left) { queue.push(node->left); } if (node->right) { queue.push(node->right); } } ret.push_back(levelValues); } return ret; } ``` #### binary-tree-level-order-traversal-ii [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) > 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 思路:在层级遍历的基础上,翻转一下结果即可 ```c++ vector> levelOrderBottom(TreeNode *root) { auto result = levelOrder(root); reverse(result); return result; } vector> levelOrder(TreeNode *root) { vector> ret; if (!root) { return ret; } queue queue; queue.push(root); while (!queue.empty()) { vector levelValues; for (auto levelNodeNum = queue.size(); levelNodeNum > 0; --levelNodeNum) { auto node = queue.front(); queue.pop(); levelValues.push_back(node->val); if (node->left) { queue.push(node->left); } if (node->right) { queue.push(node->right); } } ret.push_back(levelValues); } return ret; } void reverse(vector> &result) { for (int i = 0, j = result.size() - 1; i < j; ++i, --j) { swap(result[i], result[j]); } } ``` #### binary-tree-zigzag-level-order-traversal [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) > 给定一个二叉树,返回其节点值的锯齿形层次遍历。Z 字形遍历 ```c++ vector> zigzagLevelOrder(TreeNode* root) { vector> ret; if (!root) { return ret; } queue queue; queue.push(root); auto l2r = true; while (!queue.empty()) { vector levelValues; for (auto levelNodeNum = queue.size(); levelNodeNum > 0; --levelNodeNum) { auto node = queue.front(); queue.pop(); levelValues.push_back(node->val); if (node->left) { queue.push(node->left); } if (node->right) { queue.push(node->right); } } if (l2r) { ret.emplace_back(levelValues); } else { ret.emplace_back( levelValues.rbegin(), levelValues.rend() ); } l2r = !l2r; } return ret; } ``` ### 二叉搜索树应用 #### validate-binary-search-tree [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) > 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 思路 1:中序遍历,检查结果列表是否已经有序 思路 2:分治法,判断左 MAX < 根 < 右 MIN ```c++ // v1 bool isValidBST(TreeNode *root) { if (!root) { return true; } // 思路 1:中序遍历,检查结果列表是否已经有序 auto inOrderValues = vector(); inOrder(root, inOrderValues); for (auto iter = inOrderValues.cbegin() + 1; iter != inOrderValues.cend(); ++iter) { if (*(iter - 1) >= *iter) { return false; } } return true; } void inOrder(TreeNode *root, vector &values) { if (!root) { return; } inOrder(root->left, values); values.push_back(root->val); inOrder(root->right, values); } ``` ```c++ // v2分治法 struct Result { TreeNode *maxNode; TreeNode *minNode; bool isValidate; Result(bool validate = true, TreeNode *max = nullptr, TreeNode *min = nullptr) : isValidate(validate), maxNode(max), minNode(min) { } }; bool isValidBST(TreeNode *root) { if (!root) { return true; } return helper(root).isValidate; } Result helper(TreeNode *root) { if (!root) { return {}; } auto left = helper(root->left); auto right = helper(root->right); if (!(left.isValidate && right.isValidate)) { return {false}; } if (left.maxNode && left.maxNode->val >= root->val) { return {false}; } if (right.minNode && right.minNode->val <= root->val) { return {false}; } return { true, right.maxNode ? right.maxNode : root, left.minNode ? left.minNode : root, }; } ``` #### insert-into-a-binary-search-tree [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) > 给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 思路:找到最后一个叶子节点满足插入条件即可 ```c++ // DFS查找插入位置 TreeNode* insertIntoBST(TreeNode* root, int val) { if (!root) { root = new TreeNode(val); return root; } if (root->val > val) { root->left = insertIntoBST(root->left, val); } else { root->right = insertIntoBST(root->right, val); } return root; } ``` ## 总结 - 掌握二叉树递归与非递归遍历 - 理解 DFS 前序遍历与分治法 - 理解 BFS 层次遍历 ## 练习 - [ ] [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) - [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) - [ ] [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) - [ ] [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) - [ ] [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) - [ ] [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) - [ ] [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) - [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) - [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/)
X Tutup