0.二叉树的实现(C++)
未完,待补充
#include <iostream>
#include<iostream>
#include<queue>
#include<stack>
using namespace std;
//二叉树结点的定义
template <typename T>
struct BinTreeNode
{
T value;
BinTreeNode* left; //左孩子
BinTreeNode* right; //右孩子
BinTreeNode(const T &value): //为什么此处声明为const?
//因为声明的是模板,不确定传入参数的大小,声明为数据的常引用,保证头不会太大,
//并且该函数不改变,传入参数的值,在进行列表初始化时,将形参value进行拷贝赋值给属性value
value(value),right(NULL),left(NULL)
//此处列表初始化的赋值顺序,不是按照在此处的书写顺序,
//而是按照前面成员变量的定义顺序,即value,left,right
{}
};
//二叉树类的实现
template <typename T>
class BinTree
{
//私有属性
private:
BinTreeNode* root;
//为用户提供的接口
public:
//默认构造函数
BinTree()
:root(nullptr)
{
}
//有参构造
BinTree(BinTreeNode* root)
{
}
//析构函数
~BinTree()
{
destroyBinTree(root);
}
//赋值运算符重载
BinTree& operator=(const BinTree& binTree)
{
if(this!=binTree)
{
destroyBinTree(root);
copyBinTree(binTree->root);
}
}
//拷贝构造函数
BinTree(const BinTree &binTree)
{
root=copyBinTree(binTree->root);
}
//四种遍历(具体实现见后文)
//中序遍历
//前序遍历
//后序遍历
//层序遍历
//节点个数
//叶节点个数
//树的高度
//第k层结点的个数
//查找函数:在当前树中查询数据值为data的节点
//查找某个结点的父亲结点
//查找某个节点的左孩子节点
//查找某个节点的右孩子节点
}
1.中序遍历
(1)递归实现
//中序遍历递归实现
void inOrderTraverse(BinTreeNode *root)
{
if(root)
{
inOrderTraverse(root->left);
visit(root);
inOrderTraverse(root->right);
}
}
(2)非递归实现(栈实现)
思路:由于二叉树的结点只能通过根结点向下访问,从根结点开始入栈,然后不断迭代让左孩子结点入栈,直到左孩子结点为null时,开始访问栈顶元素(即在出栈前夕开始访问该节点),然后出栈,然后再访问出栈结点的右结点,让右结点入栈
对于每一组父亲结点和两个孩子
第一波入栈顺序:根结点->左孩子,
则出栈顺序:左孩子->根结点
第二波入栈:右孩子
出栈:右孩子
总是在出栈前夕的时候访问就保证了访问与出栈相同的顺序:左孩子->根结点->右孩子
时间复杂度:O(n)
空间复杂度:O(n)
代码实现:
//中序遍历非递归实现
void inOderTraverse(BinTreeNode *root)
{
stack<BinTreeNode *> s;
BinTreeNode *p=root; //定义遍历变量指针p
while(p || !s.empty()) //中序遍历的最终遍历变量指针p指向为空,并且弹出了所有的入栈的结点
{
if(p) //如果指针p指向的结点存在,就将它入栈
{
s.push(p); //选择的每一层都先让根结点先入栈,然后让它的左结点入栈,这样利用栈的
//先进后出的特点,出栈的时候左孩子结点会先出栈,然后根结点再出栈,
//然后根结点出栈之后访问其右结点,所以整个过程正好符合左,根,右
p=p->left;
}else
{
p=s.top();
visit(p); //这样就访问了当前层的根结点
s.pop(p); //从栈中弹出访问过的结点
p=p->right;
}
}
}
(3)Morris方法
//待补充
2.前序遍历
(1)递归实现
//前序遍历递归实现
void preOrderTraverse(BinTreeNode* root)
{
if(root)
{
visit(root);
preOrderTraverse(root->left);
preOrderTraverse(root->right);
}
}
(2)非递归实现
思路:
思路与上述中序遍历相似,入栈和出栈顺序相同,更改的是访问的时机,即在入栈前夕访问结点
对于每一组父亲结点和两个孩子
第一波入栈顺序:根结点->左孩子,
则出栈顺序:左孩子->根结点
第二波入栈:右孩子
出栈:右孩子
总是在入栈前夕的时候访问就保证了访问与入栈相同的顺序:左孩子->根结点->右孩子
时间复杂度:O(n)
空间复杂度:O(n)
代码实现:
//前序遍历非递归实现(栈实现)
void preOrderTraverse(BinTreeNode *root)
{
stack<BinTreeNode *> s;
BinTreeNode *p=root; //定义遍历变量指针p
while(p || !s.empty()) //中序遍历的最终遍历变量指针p指向为空,并且弹出了所有的入栈的结点
{
if(p) //如果指针p指向的结点存在,就将它入栈
{
visit(p); //先访问根结点,再将该结点入栈,直到左孩子为空开始返回
//每次先访问根结点,然后指向本次的左孩子时,对于下一层又是根结点
s.push(p);
p=p->left;
}else
{
p=s.top();
s.pop(p); //在弹出本层根结点时,再去访问右结点
p=p->right;
}
}
}
(3)Morris方法
//待补充
3.后序遍历
(1)递归实现
//后序遍历递归实现
void postOrderTraverse(BinTreeNode* root)
{
if(root)
{
postOrderTraverse(root->left);
postOrderTraverse(root->right);
visit(root);
}
}
(2)非递归实现
思路:
中序遍历和前序遍历的入栈顺序为
第一波入栈顺序:根结点->左孩子,
则出栈顺序:左孩子->根结点
我们没办法在入栈的时候先入栈左孩子,右孩子
但是在出栈的时候可以在左孩子出栈后,插入右孩子入栈,先判断右孩子是否已经被访问,没有被访问的话,就先将右孩子入栈,然后入栈的顺序就存储了同一组的根结点->右孩子
然后出栈顺序就为右孩子->根结点
所以整体思路:
第一波入栈顺序:根结点->左孩子,
则出栈顺序:左孩子
判断根结点是否有右孩子或者有孩子是否已经被访问,
没有被访问的话,第二波入栈:右孩子
则出栈顺序:右孩子->根结点
所以增加了判断就保证了出栈顺序为左孩子->右孩子->根结点
所以我们需要增加一个变量保存刚才已经访问过的结点,如果该结点保存的是左孩子,那么就会进行入栈,如果保存的时右孩子,表明右孩子已经被访问,可以访问根结点了
经验:当我们无法改变入栈的顺序时,我们可以通过判断,先让部分出栈,然后又让其他元素入栈,这样就改变了出栈的顺序
时间复杂度:O(n)
空间复杂度:O(n)
代码实现:
//后序遍历非递归实现(栈实现)
void postOrderTraverse(BinTreeNode *root)
{
BinTreeNode* visited=nullptr;//保存被访问的右结点,用于判断当前结点的右结点是否已被访问
BinTreeNode* p=root;
stack<BinTreeNode*> s;
while(p || !s.empty())
{
//从头结点开始,不断迭代让左孩子入栈
while(p)
{
s.push(p);
p=p->left;
}
//当左孩子全部入栈时,让p栈顶,开始准备出栈
p=s.top();
//当前结点要出栈,要进行访问,先必须保证它的右结点不存在或已经被访问,
//否则,先让它的右结点先入栈
if(p->right && p->right != visited)
{
p=p->right;
}
//当前结点的右结点不存在或已经被访问,就可以访问当前的结点,并且让它出栈
else
{
visit(p);
s.pop();
visited=p;//访问过后就将判断结点设置为刚才访问过的结点,
p=nullptr;//让p为空,是因为对于当前层来说访问完当前结点就结束了
}
}
}
(3)Morris方法
//待补充
4.层次遍历
//待补充