二叉树遍历
前序遍历:先访问根节点,再前序遍历左子树,再前序遍历右子树
中序遍历:先中序遍历左子树,再访问根节点,再中序遍历右子树
后序遍历:先后序遍历左子树,再后序遍历右子树,再访问根节点
注意点
前序递归
二叉树的前序遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
class Solution { public: vector<int> ve; vector<int> preorderTraversal(TreeNode* root) { if(root) { ve.push_back(root -> val); preorderTraversal(root -> left); preorderTraversal(root -> right); } return ve; } };
|
前序非递归(迭代法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Solution { public: vector<int> preorderTraversal(TreeNode* root) { vector<int> ve; if(root == NULL) return ve; vector<TreeNode*> stk; stk.push_back(root); while(!stk.empty()){ TreeNode* tmp = stk.back(); stk.pop_back(); ve.push_back(tmp -> val); if(tmp -> right){ stk.push_back(tmp -> right); } if(tmp -> left){ stk.push_back(tmp -> left); } } return ve; } };
|
中序非递归(迭代法)
二叉树的中序遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
class Solution { public: vector<int> inorderTraversal(TreeNode* root) { vector<TreeNode*> ve; vector<int> v; TreeNode* rt = root; while(rt || !ve.empty()) { while(rt) { ve.push_back(rt); rt=rt->left; } rt=ve.back(); ve.pop_back(); v.push_back(rt->val); rt=rt->right; } return v; } };
|
后序非递归(迭代法)
二叉树的后序遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Solution { public: vector<int> postorderTraversal(TreeNode* root) { vector<int> res; if(root == NULL) return res; vector<TreeNode*> stk; stk.push_back(root); while(!stk.empty()){ TreeNode* tmp = stk.back(); stk.pop_back(); if(tmp!=nullptr){ stk.push_back(tmp); stk.push_back(nullptr); if(tmp -> right) stk.push_back(tmp -> right); if(tmp -> left) stk.push_back(tmp -> left); } else{ res.push_back(stk.back()->val); stk.pop_back(); } } return res; } };
|
注意点
DFS 深度搜索-从上到下
二叉树的前序遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Solution { public: void dfs(TreeNode* root, vector<int>* result){ if(root){ result -> push_back(root -> val); dfs(root -> left, result); dfs(root -> right, result); } } vector<int> preorderTraversal(TreeNode* root){ vector<int> res; dfs(root, &res); return res; } };
|
这实际上就是递归的实现
DFS 深度搜索-从下向上(分治法)
二叉树的前序遍历
分治法模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Type traversal(TreeNode* root) { if(root == NULL) { }
Type left = traversal(root -> Left) Type right = traversal(root -> Right)
Type result = Merge from left and right
return result }
|
答案代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Solution { public: vector<int> divideAndConquer(TreeNode* root){ vector<int> result; if(root != NULL){ vector<int> r_left = divideAndConquer(root -> left); vector<int> r_right = divideAndConquer(root -> right); result.push_back(root -> val); for(auto it : r_left){ result.push_back(it); } for(auto it : r_right){ result.push_back(it); } } return result; } vector<int> preorderTraversal(TreeNode* root){ vector<int> res; res = divideAndConquer(root); return res; } };
|
注意点:
DFS 深度搜索(从上到下) 和分治法区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并
BFS 层次遍历
二叉树的层次遍历 II
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
思路:在层级遍历的基础上,翻转一下结果即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { public: vector<vector<int>> levelOrderBottom(TreeNode* root) { queue<TreeNode*> q; vector<vector<int>> result; if(!root){ return result; } q.push(root); while(!q.empty()){ vector<int> ve; int nums = q.size(); for(int i = 0; i < nums; i++){ TreeNode* tmp = q.front(); q.pop(); ve.push_back(tmp->val); if(tmp->left){ q.push(tmp->left); } if(tmp->right){ q.push(tmp->right); } } result.insert(result.begin(), ve); } return result; } };
|
分治法应用
先分别处理局部,再合并结果
适用场景
归并排序
归并排序是典型分治思想的代表——首先把原问题分解为两个或多个子问题,然后求解子问题的解,最后使用子问题的解来构造出原问题的解。
对于归并排序,给定一个待排序的数组,首先把该数组划分为两个子数组,然后对子数组进行排序(递归调用归并排序),最后对两个有序的子数组进行合并,使合并之后的数组为有序状态。
让我们想想,把一个数组不断地划分为子数组,不断地划分……,不断地划分……., 最后停止了划分不下去了。 此时子数组的元素有一个,它们本身就是有序的。接下来,我们就需要执行合并过程,不断地一层层向上合并,…….., 直到原数组。通过这个过程就会发现, 归并排序的核心在于合并有序的子数组,而不是对子数组进行排序,因为最底层的子数组本身就是有序的,上一层子数组如果想要变成有序的,通过合并底层有序的子数组就可以得到, 最终我们使原数组变成了有序的,从而完成了排序操作。
归并排序是用分治思想,时间复杂度为O(NlogN)。分治模式在每一层递归上有三个步骤:
- 分解(Divide):将n个元素分成个含n/2个元素的子序列。
- 解决(Conquer):用合并排序法对两个子序列递归的排序。
- 合并(Combine):合并两个已排序的子序列已得到排序结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| #include <cstring> #include <iostream> typedef bool (*CompareFunc)(int, int);
void Merge(int array[], int nStart_, int nMiddle_, int nEnd_, CompareFunc comp) { if (array == nullptr || nStart_ >= nMiddle_ || nMiddle_ >= nEnd_) return;
int _nIndex = 0; int* _pTempArray = new int[nEnd_ - nStart_];
int _nStartChange = nStart_; int _nMiddleChange = nMiddle_; while (_nStartChange < nMiddle_ && _nMiddleChange < nEnd_) { if (comp(array[_nMiddleChange], array[_nStartChange])) { _pTempArray[_nIndex] = array[_nMiddleChange]; ++_nMiddleChange; } else { _pTempArray[_nIndex] = array[_nStartChange]; ++_nStartChange; } ++_nIndex; }
if (_nStartChange < nMiddle_) { memcpy(_pTempArray + _nIndex, array + _nStartChange, sizeof(int) * (nMiddle_ - _nStartChange)); } else if (_nMiddleChange < nEnd_) { memcpy(_pTempArray + _nIndex, array + _nMiddleChange, sizeof(int) * (nEnd_ - _nMiddleChange)); } else { }
memcpy(array + nStart_, _pTempArray, sizeof(int) * (nEnd_ - nStart_));
delete[] _pTempArray; _pTempArray = nullptr; }
void MergeSort(int array[], int nStart_, int nEnd_, CompareFunc comp) { if (nullptr == array || (nEnd_ - nStart_) <= 1) return;
int _nMiddle = (nStart_ + nEnd_) / 2; MergeSort(array, nStart_, _nMiddle, comp); MergeSort(array, _nMiddle, nEnd_, comp);
Merge(array, nStart_, _nMiddle, nEnd_, comp); }
bool less(int lhs, int rhs) { return lhs < rhs; } bool large(int lhs, int rhs) { return lhs > rhs; }
void PrintArray(int array[], int nLength_) { if (nullptr == array || nLength_ <= 0) return;
for (int i = 0; i < nLength_; ++i) { std::cout << array[i] << " "; } std::cout << std::endl; }
int main(int argc, char* argv[]) { int array[10] = {1, -1, 1, 5, -5, -1, -1, 3, -4, -2}; MergeSort(array, 0, 9, large); PrintArray(array, 10); return 0; }
|
快速排序
快速排序作为20世纪十大算法之一,我们这些玩编程的好像没有理由不去学习它。快速排序是冒泡排序的升级版。
基本思想:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可以分别对这两部分记录继续进行排序,以达到整个序列有序的目的。可参考这位大佬
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include <iostream>
typedef bool (*CompareFunc)(int, int);
bool less(int lhs, int rhs) { return lhs <= rhs; } bool large(int lhs, int rhs) { return lhs >= rhs; }
void quickSort(int left, int right, int arr[], CompareFunc comp) { if (left >= right) return; int i, j, base, temp; i = left, j = right; base = arr[left]; while (i < j) { while (comp(base, arr[j]) && i < j) j--; while (comp(arr[i], base) && i < j) i++; if (i < j) { temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } arr[left] = arr[i]; arr[i] = base; quickSort(left, i - 1, arr, comp); quickSort(i + 1, right, arr, comp); }
void PrintArray(int array[], int nLength_) { if (nullptr == array || nLength_ <= 0) return;
for (int i = 0; i < nLength_; ++i) { std::cout << array[i] << " "; } std::cout << std::endl; }
int main(int argc, char* argv[]) { int array[10] = {1, -1, 1, 5, -5, -1, -1, 3, -4, -2}; quickSort(0, 9, array, less); PrintArray(array, 10); quickSort(0, 9, array, large); PrintArray(array, 10); return 0; }
|
注意点:
快排由于是原地交换所以没有合并过程 传入的索引是存在的索引(如:0、length-1 等),越界可能导致崩溃
常见题目示例
二叉树的最大深度
给定一个二叉树,找出其最大深度。
思路:分治法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
class Solution { public: int maxDepth(TreeNode* root) { if(!root) return 0; if(!root -> left && !root -> right) return 1; int left = maxDepth(root -> left); int right = maxDepth(root -> right); return left > right ? left + 1 : right + 1; } };
|
平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
思路:分治法,左边平衡 && 右边平衡 && 左右两边高度 <= 1, 因为需要返回是否平衡及高度,要么返回两个数据,要么合并两个数据, 所以用-1 表示不平衡,>0 表示树高度(二义性:一个变量有两种含义)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Solution { public: bool isBalanced(TreeNode* root) { if(maxDepth(root) == -1) { return false; } return true; } int maxDepth(TreeNode* root) { if(!root) return 0; if(!root -> left && !root -> right) return 1; int left = maxDepth(root -> left); int right = maxDepth(root -> right); if(abs(left - right) > 1 || left == -1 || right == -1) { return -1; } return left > right ? left + 1 : right + 1; } };
|
注意
一般工程中,结果通过两个变量来返回,不建议用一个变量表示两种含义
二叉树中的最大路径和
给定一个非空二叉树,返回其最大路径和。
思路:分治法,分为三种情况:左子树最大路径和最大,右子树最大路径和最大,左右子树最大加根节点最大,需要保存两个变量:一个保存子树最大路径和,一个保存左右加根节点和,然后比较这个两个变量选择最大值即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Solution { public: struct ResultType { int SinglePath; int MaxPath; }; ResultType helper(TreeNode* root) { if(root == NULL) { return {0,-(1 << 10)}; } ResultType left = helper(root -> left); ResultType right = helper(root -> right);
ResultType result; if(left.SinglePath > right.SinglePath) { result.SinglePath = std::max(left.SinglePath + root -> val, 0); } else { result.SinglePath = std::max(right.SinglePath + root -> val, 0); } int tempMax = std::max(right.MaxPath, left.MaxPath); result.MaxPath = std::max(tempMax, left.SinglePath + right.SinglePath + root -> val); return result; } int maxPathSum(TreeNode* root) { ResultType result = helper(root); return result.MaxPath; } };
|
二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
思路:分治法,有左子树的公共祖先或者有右子树的公共祖先,就返回子树的祖先,否则返回根节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if(root == NULL || root == p || root == q) { return root; }
TreeNode* left = lowestCommonAncestor(root -> left, p, q); TreeNode* right = lowestCommonAncestor(root -> right, p, q);
if(left != NULL && right != NULL) { return root; } if(left != NULL) { return left; } if(right != NULL) { return right; } return NULL; } };
|
BFS 层次应用
二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)
思路:用一个队列记录一层的元素,然后扫描这一层元素添加下一层元素到队列(一个数进去出来一次,所以复杂度 O(logN))。(和之前的二叉树的层次遍历 II类似)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { public: vector<vector<int>> levelOrder(TreeNode* root) { queue<TreeNode*> q; vector<vector<int>> result; if(!root){ return result; } q.push(root); while(!q.empty()){ vector<int> ve; int nums = q.size(); for(int i = 0; i < nums; i++){ TreeNode* tmp = q.front(); q.pop(); ve.push_back(tmp->val); if(tmp->left){ q.push(tmp->left); } if(tmp->right){ q.push(tmp->right); } } result.push_back(ve); } return result; } };
|
二叉树的锯齿形层次遍历
给定一个二叉树,返回其节点值的锯齿形层次遍历。Z 字形遍历
思路:在层次遍历的基础上加个下一层反向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class Solution { public: vector<vector<int>> zigzagLevelOrder(TreeNode* root) { queue<TreeNode*> q; vector<vector<int>> result; if(!root){ return result; } q.push(root); bool reverse_flag = false; while(!q.empty()){ vector<int> ve; int nums = q.size(); for(int i = 0; i < nums; i++){ TreeNode* tmp = q.front(); q.pop(); ve.push_back(tmp->val); if(tmp->left){ q.push(tmp->left); } if(tmp->right){ q.push(tmp->right); } } if(reverse_flag) { std::reverse(ve.begin(), ve.end()); reverse_flag = false; } else { reverse_flag = true; } result.push_back(ve); } return result; } };
|
二叉搜索树应用
验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
思路 1:递归
思路 2:中序遍历,检查结果列表是否已经有序
思路 3:分治法,判断左 MAX < 根 < 右 MIN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| class Solution { public: bool isBSTUtil(TreeNode* root, long min, long max) { if(root == NULL) return true; if(root->val <= min || root->val >= max) return false; return isBSTUtil(root->left, min, root->val) && isBSTUtil(root->right, root->val, max); } bool isValidBST(TreeNode* root) { long v_min = LONG_MIN, v_max = LONG_MAX; return isBSTUtil(root, v_min, v_max); } };
class Solution { public: bool isValidBST(TreeNode* root) { if(root == NULL) { return true; } vector<int> result; inOrder(root, &result); for(int i = 0; i < result.size() - 1; i++) { if(result[i] >= result[i + 1]) { return false; } } return true; } void inOrder(TreeNode* root, vector<int>* result) { if(root == NULL) { return; } inOrder(root -> left, result); result -> push_back(root -> val); inOrder(root -> right, result); } };
class Solution { public: struct ResultType { bool IsValid; TreeNode* Max; TreeNode* Min; }; bool isValidBST(TreeNode* root) { ResultType result = helper(root); return result.IsValid; } ResultType helper(TreeNode* root) { ResultType result = {}; if(root == NULL) { result.IsValid = true; return result; } ResultType left = helper(root -> left); ResultType right = helper(root -> right); if(!left.IsValid || !right.IsValid) { result.IsValid = false; return result; } if(left.Max != NULL && (left.Max -> val) >= (root -> val)) { result.IsValid = false; return result; } if(right.Min != NULL && right.Min -> val <= root -> val) { result.IsValid = false; return result; } result.IsValid = true;
result.Min = root; if(left.Min != NULL) { result.Min = left.Min; } result.Max = root; if(right.Max != NULL) { result.Max = right.Max; } return result; } };
|
二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。
思路:找到最后一个叶子节点满足插入条件即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Solution { public: TreeNode* insertIntoBST(TreeNode* root, int val) { if(root == NULL) { return new TreeNode(val); } if(root -> val > val) { root -> left = insertIntoBST(root -> left, val); } else { root ->right = insertIntoBST(root -> right, val); } return root; } };
|
总结
- 掌握二叉树递归与非递归遍历
- 理解 DFS 前序遍历与分治法
- 理解 BFS 层次遍历