1.二叉树理论
1.种类:满二叉树、 完全二叉树、二叉搜索树、平衡二叉搜索树
(搜索一个结点的时间复杂度是long n级别的)
2.存储方式:顺序存储、链式存储
3.遍历方式:
深度优先遍历 和 广度优先遍历
深度优先遍历:前序遍历、中序遍历、后序遍历(1.递归法 2.迭代法)
广度优先遍历:层序遍历(迭代法)
其中,栈 -> 递归 -> 深度优先遍历;队列 -> 迭代 -> 广度优先遍历
4.二叉搜索树 对结点的布局结构并无要求,仅对结点上的元素有顺序要求。
5.平衡二叉搜索树的应用: map、set、multimap、multiset
这些容器中的key和value 均是有序的,因为底层实现是平衡二叉搜索树,它是有序树。
2.递归遍历
写递归有三要素:
1.确定递归函数的参数和返回值;
2.确定终止条件;
3.确定单层循环的逻辑。
前序遍历、中序遍历、后序遍历
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void traversal(TreeNode* cur,vector<int>& vec){
if(cur == NULL)
return; //终止条件
//前序(中左右)
vec.push_back(cur -> val);
traversal(cur -> left,vec);
traversal(cur -> right,vec);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root,result);
return result;
}
};
补存:
vector的操作:
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 创建空向量
vector<int> myVector;
// 向向量尾部添加元素
myVector.push_back(10); //添加元素时,使用的是push_back()
myVector.push_back(20);
myVector.push_back(30);
// 访问元素
cout << myVector[0] << endl; // 输出第一个元素
// 删除元素
myVector.pop_back(); // 删除最后一个元素
//弹出元素时,使用的是pop_back()
int firstElement = myVector.front(); //获得第一个元素
// 获取大小
cout << myVector.size() << endl; // 输出向量大小
// 修改元素
myVector[0] = 5;
// 清空向量
myVector.clear();
return 0;
}
3.二叉树的非递归 ---- 迭代法
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
用栈也可以是实现二叉树的前后中序遍历了。
注:前后序遍历的迭代法代码相似,但是中序遍历的迭代法的代码有较大的不同。是两种不同的代码风格。
理论上,所有的递归逻辑均可以使用栈来模拟出来。
前序:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
//迭代法
stack<TreeNode*> st;
vector<int> result;
//空结点不入栈
if(root == NULL) return result;
//前序(中左右)
st.push(root);
while(!st.empty()){ //注意终止条件,迭代法的终止条件是看栈 或者 队列是否为空
TreeNode* node = st.top();
st.pop();
result.push_back(node -> val);
// if(node -> left) st.push(node -> left);
if(node -> right) st.push(node -> right);
if(node -> left) st.push(node -> left); //一定注意顺序!!!
//先处理结点的右孩子,后处理结点的左孩子。唯有这样,出栈的时候才是中左右的顺序。
}
return result;
}
};
后序:
后序的代码与前序类似,仅仅交换了几句代码的顺序。
前序,中左右;后序,左右中。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
if (node->right) st.push(node->right); // 空节点不入栈
}
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
中序:
1.二叉树的遍历主要是两个操作:访问(遍历结点) 和 处理(将元素放入result数组)。
2.前序和后序 的进行访问和处理操作时,两个操作是同步的,操作的是同一个结点,要访问的元素和要处理的元素顺序是一致的,都是中间节点;
但是中序 的访问和处理操作时,操作的结点不同。所以逻辑上不同,访问的顺序和处理的顺序不同。
在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
指针遍历结点,栈记录我们遍历过的元素。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
//迭代法(中序:左中右)
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = 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;
}
总结:
用迭代法写出了二叉树的前后中序遍历,可以看出前序和中序是完全两种代码风格,并不像递归写法那样代码稍做调整,就可以实现前后中序。
这是因为前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步!