二叉树总结
节点和树
//树节点
struct _Tree
{
int value = 0;
_Tree* lchild = nullptr;
_Tree* rchild = nullptr;
_Tree(int i = 0) :value(i), lchild(nullptr), rchild(nullptr) {};
};
//建立原始树
_Tree createTree(_Tree root) {
root.value = 4;
static _Tree child2(2), child5(5), child1(1), child7(7), child9(9);
root.lchild = &child2;
child2.rchild = &child1;
root.rchild = &child5;
child5.lchild = &child7;
child5.rchild = &child9;
std::cout << "创建树成功!" << std::endl;
cout << root.value << root.lchild->value << root.rchild->value
<< root.lchild->rchild->value << root.rchild->lchild->value << root.rchild->rchild->value << endl;
return root;
}
深度优先遍历
递归方法
先序,中序,后序
- 先序,中序,后序遍历主要是根据根节点输出位置决定
- 由于递归方法较为简单,这里只总结先序遍历。先序遍历即以“根——左——右”的顺序输出,通过递归方法输出即可。
//先序遍历
void MLR(_Tree* root) {
if (root != nullptr) {
cout << root->value << " ";
MLR(root->lchild);
MLR(root->rchild);
}
return;
}
//中序遍历
void LMR(_Tree* root) {
if (root != nullptr) {
LMR(root->lchild);
cout << root->value << " ";
LMR(root->rchild);
}
return;
}
//后序遍历
void LRM(_Tree* root) {
if (root != nullptr) {
LRM(root->lchild);
LRM(root->rchild);
cout << root->value << " ";
}
return;
}
非递归方法
先序,中序
对于先序和中序遍历,其利用指针遍历节点的逻辑是一致的,逻辑如下:
-
利用栈先进后出的特性,将根节点入栈,然后让指针指向当前节点的左子树,循环直到左子树为空
-
左子树为空时,访问栈顶元素的右子树,然后将栈顶元素出栈
分析一下这样的入栈顺序:先是根节点入栈,然后是左节点,若左节点的左子树为空,则左节点出栈,然后访问左节点的右节点;这样保证了(1)根节点比左节点先入栈(2)同级的左节点比右节点先入栈。
假设我们要将遍历到的树依次存储在一个数组中,区分先序和中序遍历不同点在于先存入谁和后存入谁的问题:
对于先序遍历而言,先存根节点,再存左节点,而入栈顺序就是这样的,所以在入栈时存入即可保证了“根——左——右”的顺序。
对于中序遍历而言,先存左节点,再存根节点,在第二步存入出栈的元素即可(因为第一步完成时,即保证了栈顶元素的左子树为空)
可以简单的理解:在第一步存入得到先序遍历结果,在第二步存入得到中序遍历结果。
代码如下:
//非递归先序遍历二叉树
vector<int>* MLR2(_Tree* root, vector<int>* ans) {
stack<_Tree> stk;
_Tree* root1 = root;
while (root1 != nullptr || !stk.empty()) {
if (root1 != nullptr) {
ans->push_back(root1->value);
stk.push(*root1);
root1 = root1->lchild;
}
else {
_Tree node = stk.top();
stk.pop();
root1 = node.rchild;
}
}
return ans;
}
vector<int>* LMR2(_Tree* root, vector<int>* ans) {
stack<_Tree> stk;
_Tree* node = root;
while (node != nullptr || !stk.empty()) {
if (node != nullptr) {
stk.push(*node);
node = node->lchild;
}
else {
_Tree temp = stk.top();
ans->push_back(temp.value);
node = temp.rchild;
stk.pop();
}
}
return ans;
}
后序遍历
对于后序遍历,其利用指针遍历节点的逻辑与先序,中序是不太一样的,逻辑如下:
-
利用栈先进后出的特性,将根节点入栈,然后让指针指向当前节点的左子树,循环直到左子树为空
-
左子树为空时,将当前指针指向栈顶元素,若栈顶元素的右子树非空且未被访问,则访问栈顶元素的右子树(即让当前指针指向右子树);若其为空或者已经被访问,则将栈顶元素出栈,用一个指针记录当前指针,然后将当前指针置零。
第二步这样做的原因是为了使得某个节点的右子树不为空时重复入栈。
代码如下:
vector<int>* LRM2(_Tree* root, vector<int>* ans) { //非递归后序遍历,比较特殊,需要记录上一时刻是否访问了右节点 //这里不记录左节点是因为tip1处访问的是左节点,先天决定了在根节点pop前左节点就pop了,且不会再访问已经pop的左节点 _Tree* p = root, * r = nullptr; stack<_Tree*> s; while (p || !s.empty()) { if (p) {//走到最左边 s.push(p); p = p->lchild; } else { p = s.top(); if (p->rchild && p->rchild != r)//右子树存在,未被访问 p = p->rchild; else { s.pop(); ans->push_back(p->value); r = p;//记录最近访问过的节点 p = nullptr;//节点访问完后,重置p指针 } }//else }//while return ans; }
广度优先遍历(层次遍历)
层次优先遍历即从上向下,从左往右依次遍历,这时用指针来挨个遍历较为麻烦,指针需要不停的“上窜下跳”,这时可以利用队列。逻辑如下:
- 跟节点入队
- 循环将队头的(非空)左右节点依次入队,然后队头出队,直到队列为空
出队的顺序即为层次遍历的结果,代码如下:
vector<int>* LT(_Tree*root,vector<int>*ans) {
queue<_Tree>qee;
if (root==nullptr) return ans;
qee.push(*root);
while (!qee.empty()) {
_Tree temp = qee.front();
ans->push_back(temp.value);
qee.pop();
if (temp.lchild!=nullptr) {
qee.push(*(temp.lchild));
}
if (temp.rchild!=nullptr) {
qee.push(*(temp.rchild));
}
}
return ans;
}
重建二叉树
递归重建法
这里假设我们已知前序遍历anspre和中序遍历结果ansmid。
- 这颗树的根节点为前序遍历的首个元素k
- 根据k在中序遍历中的位置Index,其左边的是左子树的中序遍历结果ansmidL;右边的是右子树的中序遍历结果ansmidR
- 由元素个数关系结合Index可知,前序遍历的下标**[1-Index]为左子树的前序遍历结果anspreL**,下标**[Index–最后]是右子树的前序遍历结果anspreR**
- 使用递归重建左子树和右子树
代码如下:
//递归重建法
_Tree* rebuildTree(vector<int>anspre, vector<int>ansmid) {
if (anspre.empty() || (anspre.size() != ansmid.size())) return nullptr;
_Tree *node = new _Tree(anspre[0]);
size_t index = 0;
for (int i = 0; i < anspre.size(); i++) {
if (ansmid[i] == anspre[0]) {
index = i;
break;
}
}
//记录左/右子树的前序,后序遍历
vector<int>anspreL, anspreR, ansmidL, ansmidR;
for (size_t i = 0; i < index; i++) {
anspreL.push_back(anspre[i + 1]);
ansmidL.push_back(ansmid[i]);
}
for (size_t i = index + 1; i < anspre.size(); i++) {
anspreR.push_back(anspre[i]);
ansmidR.push_back(ansmid[i]);
}
//递归
node->lchild = rebuildTree(anspreL, ansmidL);
node->rchild = rebuildTree(anspreR, ansmidR);
return node;
}
这里由于重建时使用new将树重建在堆上,所以最后也要将其delete掉,代码如下:
//对于new出来的树要delete
void deleteTree(_Tree* root) {
if (root != nullptr) {
deleteTree(root->lchild);
deleteTree(root->rchild);
delete root;
root = nullptr;
}
return;
}
完整代码
#include<iostream>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
//树节点
struct _Tree
{
int value = 0;
_Tree* lchild = nullptr;
_Tree* rchild = nullptr;
_Tree(int i = 0) :value(i), lchild(nullptr), rchild(nullptr) {};
};
//建立原始树
_Tree createTree(_Tree root) {
root.value = 4;
static _Tree child2(2), child5(5), child1(1), child7(7), child9(9);
root.lchild = &child2;
child2.rchild = &child1;
root.rchild = &child5;
child5.lchild = &child7;
child5.rchild = &child9;
std::cout << "创建树成功!" << std::endl;
cout << root.value << root.lchild->value << root.rchild->value
<< root.lchild->rchild->value << root.rchild->lchild->value << root.rchild->rchild->value << endl;
return root;
}
//先序遍历
void MLR(_Tree* root) {
if (root != nullptr) {
cout << root->value << " ";
MLR(root->lchild);
MLR(root->rchild);
}
return;
}
//中序遍历
void LMR(_Tree* root) {
if (root != nullptr) {
LMR(root->lchild);
cout << root->value << " ";
LMR(root->rchild);
}
return;
}
//后序遍历
void LRM(_Tree* root) {
if (root != nullptr) {
LRM(root->lchild);
LRM(root->rchild);
cout << root->value << " ";
}
return;
}
//非递归先序遍历二叉树
vector<int>* MLR2(_Tree* root, vector<int>* ans) {
stack<_Tree> stk;
_Tree* root1 = root;
while (root1 != nullptr || !stk.empty()) {
if (root1 != nullptr) {
ans->push_back(root1->value);
stk.push(*root1);
root1 = root1->lchild;
}
else {
_Tree node = stk.top();
stk.pop();
root1 = node.rchild;
}
}
return ans;
}
vector<int>* LMR2(_Tree* root, vector<int>* ans) {
stack<_Tree> stk;
_Tree* node = root;
while (node != nullptr || !stk.empty()) {
if (node != nullptr) {
stk.push(*node);
node = node->lchild;
}
else {
_Tree temp = stk.top();
ans->push_back(temp.value);
node = temp.rchild;
stk.pop();
}
}
return ans;
}
vector<int>* LRM2(_Tree* root, vector<int>* ans) {
//非递归后序遍历,比较特殊,需要记录上一时刻是否访问了右节点
//这里不记录左节点是因为tip1处访问的是左节点,先天决定了在根节点pop前左节点就pop了,且不会再访问已经pop的左节点
_Tree* p = root, * r = nullptr;
stack<_Tree*> s;
while (p || !s.empty()) {
if (p) {//走到最左边
s.push(p);
p = p->lchild;
}
else {
p = s.top();
if (p->rchild && p->rchild != r)//右子树存在,未被访问
p = p->rchild;
else {
s.pop();
ans->push_back(p->value);
r = p;//记录最近访问过的节点
p = nullptr;//节点访问完后,重置p指针
}
}//else
}//while
return ans;
}
//层次遍历即广度优先
vector<int>* LT(_Tree*root,vector<int>*ans) {
queue<_Tree>qee;
if (root==nullptr) return ans;
qee.push(*root);
while (!qee.empty()) {
_Tree temp = qee.front();
ans->push_back(temp.value);
qee.pop();
if (temp.lchild!=nullptr) {
qee.push(*(temp.lchild));
//cout << "L:" << endl;
}
if (temp.rchild!=nullptr) {
qee.push(*(temp.rchild));
//cout << "R:" << endl;
}
}
return ans;
}
//重建树
//先序和中序可以确定唯一树
//中序和后序可以确定唯一树
//先序和后序无法确定唯一树
//递归重建法
_Tree* rebuildTree(vector<int>anspre, vector<int>ansmid) {
if (anspre.empty() || (anspre.size() != ansmid.size())) return nullptr;
_Tree *node = new _Tree(anspre[0]);
size_t index = 0;
for (int i = 0; i < anspre.size(); i++) {
if (ansmid[i] == anspre[0]) {
index = i;
break;
}
}
//记录左/右子树的前序,后序遍历
vector<int>anspreL, anspreR, ansmidL, ansmidR;
for (size_t i = 0; i < index; i++) {
anspreL.push_back(anspre[i + 1]);
ansmidL.push_back(ansmid[i]);
}
for (size_t i = index + 1; i < anspre.size(); i++) {
anspreR.push_back(anspre[i]);
ansmidR.push_back(ansmid[i]);
}
//递归
node->lchild = rebuildTree(anspreL, ansmidL);
node->rchild = rebuildTree(anspreR, ansmidR);
return node;
}
//对于new出来的树要delete
void deleteTree(_Tree* root) {
if (root != nullptr) {
deleteTree(root->lchild);
deleteTree(root->rchild);
delete root;
root = nullptr;
}
return;
}
int main() {
vector<int>ansMLR;
vector<int>ansLMR;
vector<int>ansLRM;
//初始树
_Tree root(4);
root=createTree(root);
//递归
cout << "打印先序遍历:";
MLR(&root);
cout << endl;
cout << "打印中序遍历:";
LMR(&root);
cout << endl;
cout << "打印后序遍历:";
LRM(&root);
cout << endl;
//非递归
MLR2(&root, &ansMLR);
cout << "打印非递归先序遍历数组:";
for (int i = 0; i < ansMLR.size(); i++) {
cout << ansMLR[i]<<" ";
}
cout << endl;
LMR2(&root, &ansLMR);
cout << "打印非递归中序遍历数组:";
for (int i = 0; i < ansLMR.size(); i++) {
cout << ansLMR[i] << " ";
}
cout << endl;
LRM2(&root, &ansLRM);
cout << "打印非递归后序遍历数组:";
for (int i = 0; i < ansLRM.size(); i++) {
cout << ansLRM[i] << " ";
}
cout << endl;
//层次遍历
ansLRM.clear();
LT(&root, &ansLRM);
cout << "打印层次遍历数组:";
for (int i = 0; i < ansLRM.size(); i++) {
cout << ansLRM[i] << " ";
}
cout << endl;
//重建树
_Tree* nodeRebuild=rebuildTree(ansMLR, ansLMR);
//打印一下先序遍历结果看看是否正确
cout << "打印先序遍历:";
MLR(nodeRebuild);
cout << endl;
//删除堆上重建的树
deleteTree(nodeRebuild);
return 0;
}