1、理论基础
忘记概念的有:完全二叉树,平衡二叉搜索树
完全二叉树:除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层节点数的取值范围为 [1, 2^(h-1) ]
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
二叉树可以链式存储(链表,更好理解),也可以顺序存储(数组)。
二叉树的遍历方式:
- 深度优先遍历
- 前序遍历(递归法,迭代法)
- 中序遍历(递归法,迭代法)
- 后序遍历(递归法,迭代法)
- 广度优先遍历
- 层次遍历(迭代法)
2、递归遍历
看完想法:刚开始写又不会了( :( )。牢记递归三部曲:
- 确定递归函数的参数和返回值。确定哪些参数是递归中需要处理的,那么就在递归函数里加上这个参数,并且还要明确每次递归的返回值,进而确定递归函数的类型
- 确定终止条件。
- 确定单层递归的逻辑。
//此处仅列出前序遍历
void Traversal(TreeNode* root, vector<int>& result){
//因为参数列表引用了result,所以能够修改主函数中result的值
if(root == nullptr) return ;
result.push_back(root->val);
Traversal(root->left, result);
Traversal(root->right, result);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
Traversal(root, result);
return result;
}
在递归遍历的题目中,因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,
3、迭代遍历
看完想法:是用栈来做二叉树的进入进出。具体来说
vector<int> preorderTraversal(TreeNode* root) {
//使用栈来存储,栈的数据机构可以为二叉树*,不必为Int
stack<TreeNode*> st;
vector<int> result;
if(root == nullptr) return result;
st.push(root);
while( !st.empty()){
//当st不为空时,记录栈顶元素并弹出
TreeNode* tmp = st.top();
st.pop();
//用result记录下弹出元素的数值
result.push_back(tmp->val);
//然后进行下一步的遍历
//记得左右反过来,因为栈是先入后出的
if(tmp->right) st.push(tmp->right);
if(tmp->left) st.push(tmp->left);
}
return result;
}
这只是前序遍历,到了中序遍历,又不一样了!因为中序遍历处理顺序和处理顺序是不一样的。(前序遍历遍历到中节点,处理的也是中节点,所以代码简洁)
在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode* > st;//stack用来遍历
vector<int> result;//result用来记录
TreeNode* cur = root;
if(root == nullptr) return result;
//st.push(root);
//这里不需要预先放入root,后面可以放进去
while( !st.empty() || cur!=NULL ){
if(cur != NULL){
//一直先遍历到左边底部
st.push(cur);
cur=cur->left;
}
else{
//遍历到头后开始处理节点
cur = st.top();
st.pop();
result.push_back(cur->val);//中
cur = cur->right;//右
}
}
return result;
}
后序遍历只需将前序遍历得到的数组反过来就行,因为左→右→中 —→ 中→右→左
4、统一迭代
看完想法:统一迭代的思路是将访问的节点放入栈中,把要处理的节点也放入栈中但是要用空指针做标记。
vector<int> preorderTraversal(TreeNode* root) {
//使用栈来存储,栈的数据机构可以为二叉树*,不必为Int
stack<TreeNode*> st;
vector<int> result;
//TreeNode* cur = root;
//不能这么定义,cur要在while中更新成为.top()
if(root == nullptr) return result;
st.push(root);
while(!st.empty()){
TreeNode* cur = st.top();
if(cur !=NULL){
//后面一次性将右中左放入,这是为了避免重复操作和防止打乱顺序
st.pop();
if(cur->right) st.push(cur->right);
if(cur->left) st.push(cur->left);
st.push(cur);
st.push(NULL);//做标记
}
else{//这里是处理阶段
st.pop();
cur = st.top();
st.pop();//还需要弹出一次
result.push_back(cur->val);
}
}
return result;
}
5、层序遍历
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode* > que;
vector<vector<int>> result;//层序的数据格式是这样
if(root==nullptr) return result;
que.push(root);
while(!que.empty()){
int size = que.size();
vector<int> vec;
//每一层遍历完后都要记录下一层节点的值
//这样才知道每层的节点数量
for(int i = 0; i<size; i++){
TreeNode* cur = que.front();
que.pop();
vec.push_back(cur->val);
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
//每次pop都把当前节点的左右孩子加入que
}
result.push_back(vec);
}
return result;