插播总结C++ STL容器
参考: C++STL【容器】详解 (全站最详细) - 知乎 (zhihu.com)
1. 顺序容器
1.1 vector 可变大小数组
1.2 deque 双端队列
1.3 list 双向循环链表
1.4 forward_list 单向链表
1.5 array 固定数组
2. 关联式容器
2.1 set
2.2 map
3 容器适配器
之所以叫容器适配器,是因为用来适配容器来提供其它不一样的功能。通过对应的容器和成员函数来实现我们需要的功能
3.1 stack 堆栈 先进后出
3.2 queue 队列 先进先出
3.3 pirority_queue 优先队列
队列操作在二叉树中很多
总结一下队列的基本操作:
参考:C++队列queue用法详解_queue c++ 用法-CSDN博客C++ Queue(队列) - 菜鸟教程 (cainiaojc.com)
队列属于先进先出类型,在普通队列中,元素的插入在尾部,而删除则从前面开始。
1. 创建队列
1.1 普通初始化
queue<int> q;
1.2 用容器实现的队列
queue<char, list<char>>q1; //list
queue<int, deque<int>>q2; //deque
//不能用vector
2. 常用操作
- size() 返回队列长度
- empty() 如果队列空则返回true
- push() 在队尾插入一个元素 (注意vector是push_back())
- pop() 删除队列第一个元素,无返回值
- front() 返回队列中的第一个元素
- back() 返回队列中最后一个元素
目前看来队列的操作跟前面遇到的那些容器操作没有太大的区别
有了队列的基础知识,广度优先搜索(BFS)可以理解了。图解可以看深度优先搜索(DFS)和广度优先搜索(BFS)-CSDN博客
以力扣102题二叉树的层序遍历为例
BFS的思路就是,我们将二叉树看作一层一层的节点,按层数去查找,根节点在第一层,第一层的广度就是节点数为1,以此类推。
我们要始终牢记队列是队头出队,队尾入队。
首先将根节点存入队列,此时队列的元素数就是当前层数的广度1,然后将根节点出队,左右子树入队,这是第二层,此时队列中的元素数就是当前层的广度,然后创建一个新节点指向队头节点,将队头节点的左右子树入队,队头出队,循环直至父节点都出队,以此类推,队列为空为止。
代码:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;
if(root == nullptr) return ans;
queue<TreeNode *> q;
q.push(root);
while(!q.empty()){
int n = q.size();
ans.push_back(vector<int>());
for(int i = 1; i<=n; i++){
TreeNode *p = q.front();
q.pop();
ans.back().push_back(p->val);
if(p->left) q.push(p->left);
if(p->right) q.push(p->right);
}
}
return ans;
}
};
之前还提到了深度优先搜索算法里面,除了递归还有栈的方法
总结一下栈的基础操作:
参考:栈的详解(C/C++数据结构)_c++栈-CSDN博客<C++>学习:栈(Stack)操作_c++ stack-CSDN博客 c++数据结构之栈_c++栈-CSDN博客二叉树遍历总结:DFS+BFS【C++】 - Ueeei - 博客园 (cnblogs.com)
栈是先进后出类型的
1. 栈的定义
typedef int ElemType;
typedef struct _SqStack {
ElemType* base; //栈底指针
ElemType* top; //栈顶指针
}SqStack;
2. 初始化栈
stack<TreeNode*> stack; //可以是不同数据类型
3. 栈的基本操作
- top():返回一个栈顶元素的引用,类型为 T&。如果栈为空,返回值未定义。
- size(): 返回栈中元素的个数。
- empty(): 在栈中没有元素的情况下返回 true。
- push(): 让某元素入栈
- pop(): 让栈顶元素出栈
4. 深度优先搜索-二叉树
4.1 前序遍历
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> stk;
while (root != nullptr || !stk.empty()) {
// 从根开始一直往左入栈,直到最左结点入栈结束
while(root){
stk.push(root);
res.push_back(root->val);
root = root->left;
}
//处理栈内节点
root = stk.top();
stk.pop();
root = root->right;
}
return res;
}
4.2 中序遍历
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> stk;
while (root != nullptr || !stk.empty()) {
while (root != nullptr) {
stk.push(root);
root = root->left;
}
root = stk.top();
stk.pop();
res.push_back(root->val);
root = root->right;
}
return res;
}
4.3 后序遍历
后续遍历跟前序和中序都不同,需要两个栈来操作,一个栈进行根->右->左(类似前序遍历)的操作,另一个栈反向输出实现左->右->根
vector<int> postOrderNocur(TreeNode *root) {
vector<int> res;
stack<TreeNode*> s; // 第一个栈,保证前序遍历(根-右-左)的执行
stack<TreeNode*> p; // 第二个栈,实现反向输出左右根
while (root != nullptr || !s.empty()) {
while (r) {
s.push(root);
p.push(root); // p与s同步push
root = root->right;
}
r = s.top();
s.pop(); // p不需要pop;s执行pop的原因是需要将前序遍历执行下去
r = r->left;
}
while (!p.empty()) {
res.push_back(p.top()->val);
p.pop();
}
return res;
}
三种遍历方式最核心部分是相似的,递归的方式虽然简单(上一篇博客有讲),但是要牢记非递归的方式,后面做题会用到。
接着做题了
三十八、LeetCode第三十八题(108)
将有序数组转换为二叉搜索树
给你一个整数数组 nums
,其中元素已经按 升序 排列,请你将其转换为一棵平衡二叉搜索树。
平衡二叉树指的是所有节点左右子树的深度相差都不超过1。
这道题自己做出来了
用的二分法和递归:将给出的数组从中点一分为二,中点为根节点(不考虑奇偶),左边数组作为左子树,右边数组为右子树,接着二分递归。
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
int mid = nums.size()/2;
if(nums.size() == 0) return nullptr;
vector<int> s1;
vector<int> s2;
s1.assign(nums.begin(), nums.begin()+mid);
s2.assign(nums.begin()+mid+1, nums.end());
TreeNode *root = new TreeNode(nums[mid]);
root->left = sortedArrayToBST(s1);
root->right = sortedArrayToBST(s2);
return root;
}
};
三十九、LeetCode第三十九题(98)
验证二叉搜索树
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
这道题也是递归和非递归的方法
根据前面学的非递归的遍历方法,我们考虑中序遍历整个二叉树,得到的应该是一个递增的数列,于是可以有以下解法:在中序遍历的基础上加一个元素值的大小比较:
class Solution {
public:
bool isValidBST(TreeNode* root) {
if(root == nullptr) return false;
vector<int> res;
stack<TreeNode*>stk;
while(root != nullptr || !stk.empty()){
while(root){
stk.push(root);
root = root->left;
}
root = stk.top();
res.push_back(root->val);
if(res.size()>1 && res[res.size()-1] <= res[res.size()-2]) return false;
stk.pop();
root = root->right;
}
return true;
}
};
但是这样做有一个冗余步骤就是,我们并不需要存储整个遍历的结果,于是可以不用vector,而只是比较每次的root->val与前一个的大小,代码如下:. - 力扣(LeetCode)
class Solution {
public:
bool isValidBST(TreeNode* root) {
stack<TreeNode*> stack;
long long inorder = (long long)INT_MIN - 1;
while (!stack.empty() || root != nullptr) {
while (root != nullptr) {
stack.push(root);
root = root -> left;
}
root = stack.top();
stack.pop();
// 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
if (root -> val <= inorder) {
return false;
}
inorder = root -> val;
root = root -> right;
}
return true;
}
};
递归的方式我一开始也写了,但是仅考虑了每个子树的左右子树能够满足要求,但是还要考虑整个二叉树的左子树元素值小于右子树的元素值,于是这里面还是存在一个限制大小范围的过程:
//官方题解
class Solution {
public:
bool helper(TreeNode* root, long long lower, long long upper) {
if (root == nullptr) {
return true;
}
if (root -> val <= lower || root -> val >= upper) {
return false;
}
return helper(root -> left, lower, root -> val) && helper(root -> right, root -> val, upper);
}
bool isValidBST(TreeNode* root) {
return helper(root, LONG_MIN, LONG_MAX);
}
};
注意这里初始化的数据类型都是long long,我想不到这点,所以就还是用了数组存储中序遍历的方式。