二叉树的遍历
二叉树的遍历一般两种,深搜,广搜。
广搜非常简单,只需要一个队列就行了,只要左右孩子不为空,那么就入队,代码就不放了();
主要写一下深搜,也就是先序遍历,中序遍历,后序遍历。
三者的区别在于什么时候输出中间元素,也就是头。
递归版本
(代码展示很抽象,但理解意思即可)
void preOrder(TreeNode* head) {
if (head == nullptr) {
return;
}
cout << head->val << " ";
preOrder(head->left);
preOrder(head->right);
}
// 中序打印所有节点,递归版
void inOrder(TreeNode* head) {
if (head == nullptr) {
return;
}
inOrder(head->left);
cout << head->val << " ";
inOrder(head->right);
}
// 后序打印所有节点,递归版
void posOrder(TreeNode* head) {
if (head == nullptr) {
return;
}
posOrder(head->left);
posOrder(head->right);
cout << head->val << " ";
}
不用指针也可以的,可以把0当作空指针,判断是不是0就行了。
迭代版本(难一些,但是用时更少)
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int v) : val(v), left(nullptr), right(nullptr) {}
};
// 【先序遍历】迭代版
// 顺序:中 -> 左 -> 右
void preOrderUnRecur(TreeNode* head) {
if (head == nullptr) {
return;
}
cout << "先序遍历迭代版: ";
stack<TreeNode*> st;
st.push(head); // 1. 先把头节点放入
while (!st.empty()) {
// 2. 弹出并打印
TreeNode* cur = st.top();
st.pop();
cout << cur->val << " ";
// 3. 有右先压右(这样左边后压,就能先弹出来)
if (cur->right != nullptr) {
st.push(cur->right);
}
// 4. 有左再压左
if (cur->left != nullptr) {
st.push(cur->left);
}
}
cout << endl;
}
// 【中序遍历】迭代版
// 顺序:左 -> 中 -> 右
// 逻辑:每棵子树,都要把左边界一股脑压入栈,压到底后弹出打印,然后去右树
void inOrderUnRecur(TreeNode* head) {
if (head == nullptr) {
return;
}
cout << "中序遍历迭代版: ";
stack<TreeNode*> st;
TreeNode* cur = head;
// 当栈不为空,或者当前节点不为空时,继续循环
while (!st.empty() || cur != nullptr) {
if (cur != nullptr) {
// 1. 只要当前节点有东西,就入栈,并且一直往左走
st.push(cur);
cur = cur->left;
} else {
// 2. 左边走到头了(cur == nullptr),弹出栈顶节点并打印
cur = st.top();
st.pop();
cout << cur->val << " ";
// 3. 往右走一步,然后继续下一轮循环(下一轮又会尝试往左走)
cur = cur->right;
}
}
cout << endl;
}
后序遍历有两种办法,一种是两个栈实现,一种是一个栈实现。
两个栈实现其实就是将一个栈作为输出的工具,因为输出顺序是“左右中”,那么压入顺序就是“中右左”,那么处理的顺序就是“中右左”,所以压入第一个栈的顺序就是“左右中”,
// 【后序遍历】迭代版 (双栈法 - 最容易理解的写法)
// 顺序:左 -> 右 -> 中
// 逻辑:先序是 中左右。如果我们将先序逻辑改成 中右左,
// 放到收集栈里,最后收集栈倒出来就是 左右中。
void posOrderUnRecur(TreeNode* head) {
if (head == nullptr) {
return;
}
cout << "后序遍历迭代版: ";
stack<TreeNode*> s1; // 遍历用的栈
stack<TreeNode*> s2; // 收集结果的栈
s1.push(head);
while (!s1.empty()) {
TreeNode* cur = s1.top();
s1.pop();
// 这里的顺序是:中 -> s2
s2.push(cur);
// 这里的入栈顺序是先左后右
// s1弹出时就是:先右后左
// 结合上面的s2 push,s2里的顺序就是:中、右、左
if (cur->left != nullptr) {
s1.push(cur->left);
}
if (cur->right != nullptr) {
s1.push(cur->right);
}
}
// s2 现在的顺序(从底到顶)是:中 -> 右 -> 左
// 依次弹出打印,就是:左 -> 右 -> 中
while (!s2.empty()) {
cout << s2.top()->val << " ";
s2.pop();
}
cout << endl;
}
这个版本非常好理解,就是先序遍历稍微改一下,但是还有一种只用一个栈的方法。
// 后序打印所有节点,非递归版
// 这是用一个栈的方法
void posOrderOneStack(TreeNode* h) {
if (h != nullptr) {
stack<TreeNode*> st;
st.push(h);
// 如果始终没有打印过节点,h就一直是头节点
// 一旦打印过节点,h就变成打印节点
// 之后h的含义 : 上一次打印的节点
while (!st.empty()) {
TreeNode* cur = st.top();
// 这里的逻辑稍微比较绕,解释一下:
// 1. h != cur->left : 说明 h 不是 cur 的左孩子(即:左子树还没处理完,或者刚处理完回来)
// 2. h != cur->right : 说明 h 不是 cur 的右孩子(即:右子树还没处理完)
// 结合起来:如果 cur 有左孩子,且既不是刚从左边回来,也不是刚从右边回来 -> 那就往左走
if (cur->left != nullptr && h != cur->left && h != cur->right) {
// 有左树且左树没处理过
st.push(cur->left);
}
else if (cur->right != nullptr && h != cur->right) {
// 有右树且右树没处理过
// (代码走到这里隐含了:左树为空 或者 左树已经处理完了即 h == cur->left)
st.push(cur->right);
}
else {
// 左树、右树 没有 或者 都处理过了
cout << cur->val << " ";
// C++ pop() 不返回值,所以分两步写
h = st.top();
st.pop();
}
}
cout << endl;
}
}
再见

浙公网安备 33010602011771号