二叉树特点
- 每个节点最多有两棵子树;
- 二叉树是有序的,即区分左右子树的次序。
完全二叉树
叶子节点只能出现在最下两层,且最下层的叶子节点都集中在二叉树左侧连续的位置。
如果有度为1的节点,只可能有一个,且该节点只有左孩子。
二叉树实现
这里只讲二叉链表实现,使用C++。
template<class DataType>
struct BiNode
{
DataType data;
BiNode<DataType> *lchild, *rchild;
};
template<class DataType>
class BiTree { public: BiTree() {root = Creat(root);} ~BiTree() {Release(root);} void PreOrder() {PreOrder(root);} void InOrder() {InOrder(root);} void PostOrder() {PostOrder(root);} void LevelOrder(); private: BiNode<DataType> *root; BiNode<DataType> *Creat(BiNode<DataType> *bt); void Release(BiNode<DataType> *bt); void PreOrder(BiNode<DataType> *bt); void InOrder(BiNode<DataType> *bt); void PostOrder(BiNode<DataType> *bt); };
建树:可以递归调用Creat函数来实现,注意对于参数
BiNode<DataType> *bt
bt是形参,对它的修改仅在函数体内生效,如果想修改传进来的指针,可使用引用:
BiNode<DataType> * &bt
析构函数:在释放某节点时,需要先释放其左右子树,故可使用后序遍历的方法释放。
前序遍历
前序遍历先访问根节点再访问左右子树,其递归实现:
void BiTree<DataType>::PreOrder(BiNode<DataType> *bt)
{
if (NULL == bt)
return ;
cout << bt->data << ' ';
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
事实上,我们递归的过程如下:
访问节点->往左->访问节点->往左, 直到左边节点为NULL,之后返回该NULL节点的父节点,并访问该父节点的右儿子,继续重复访问节点->往左……的过程。
于是我们可以利用一个栈写出非递归的版本:
void BiTree<DataType>::PreOrder(BiNode<DataType> *bt)
{
stack<BiNode<DataType> *> s;
while (bt || !s.empty())
{
while (bt)
{
cout << bt->data << ' ';
s.push(bt);
bt = bt->lchild;
}
if (!s.empty())
{
bt = s.top();
s.pop();
bt = bt->rchild;
}
}
}
中序遍历
中序遍历先访问左子树,再访问根节点,最后访问右子树,其递归实现:
void BiTree<DataType>::InOrder(BiNode<DataType> *bt)
{
if (NULL == bt)
return ;
InOrder(bt->lchild);
cout << bt->data << ' ';
InOrder(bt->rchild);
}
非递归版本的中序遍历与前序遍历非常类似:
void BiTree<DataType>::InOrder(BiNode<DataType> *bt)
{
stack<BiNode<DataType> *> s;
while (bt || !s.empty())
{
while (bt)
{
s.push(bt);
bt = bt->lchild;
}
if (!s.empty())
{
bt = s.top();
s.pop();
cout << bt->data << ' '; //将访问移到这里
bt = bt->rchild;
}
}
}
后序遍历
后序遍历先访问左子树,再访问右子树,最后访问根节点,其递归实现:
void BiTree<DataType>::PostOrder(BiNode<DataType> *bt)
{
if (NULL == bt)
return ;
PostOrder(bt->lchild);
PostOrder(bt->rchild);
cout << bt->data << ' ';
}
后序遍历的非递归版本就有点难度了。
对于某个节点,它需要入栈两次,出栈两次:
第一次出栈:只遍历完左子树,右子树尚未遍历,利用栈顶节点找到它的右子树(相当于出栈又进栈),准备遍历它的右子树。
第二次出栈:遍历完右子树,将该节点出栈并访问它。
为区别同一个节点的两次出栈,我们设置标志flag:
flag=1,表示第一次出栈,只遍历完左子树,该节点不能访问;
flag=2,表示第二次出栈,遍历完右子树,该节点能访问;
对于根节点bt,有以下两种情况:
- bt不为NULL,则bt及标志(置为1)入栈,遍历其左子树;
- bt为NULL,若栈为空,说明整个遍历结束;若栈不为空,说明栈顶节点的左子树或右子树遍历完毕,此时若栈顶元素flag=1,表明刚遍历完左子树,所以修改flag=2,遍历右子树,若栈顶元素flag=2,表明刚遍历完右子树,输出栈顶元素。
void BiTree<DataType>::PostOrder(BiNode<DataType> *bt)
{
stack<BiNode<DataType> *> s;
stack<int> s_flag;
while (bt || !s.empty())
{
while (bt)
{
s.push(bt);
s_flag.push(1);
bt = bt->lchild;
}
while (!s.empty() && 2 == s_flag.top())
{
bt = s.top();
s.pop();
s_flag.pop();
cout << bt->data << ' ';
}
if (!s.empty())
{
s_flag.pop();
s_flag.push(2);
bt = s.top()->rchild;
}
}
}
层序遍历
层序遍历是一层一层访问的,一个节点被访问,则在下一层中,其儿子节点将被访问,对于同一层的两个节点A、B,A在B左边,则A的儿子节点先于B的儿子节点被访问。
简而言之,先访问的节点,其左右孩子也要先访问,因此我们可以使用一个队列来维护这个过程:
根节点入队,之后
1. 从队列头取出一个元素进行以下操作:
2. 访问该元素;
3. 若该节点的左右孩子非空,将其左右孩子入队;
4. 回到1,直到队列为空时结束。
void BiTree<DataType>::LevelOrder()
{
queue<BiNode<DataType> *> q;
if (root)
q.push(root);
while (!q.empty())
{
BiNode<DataType> *cur = q.top();
q.pop();
cout << cur->data << ' ';
if (cur->lchild)
q.push(cur->lchild);
if (cur->rchild)
q.push(cur->rchild);
}
}
每天进步一点点,Come on!
(●’◡’●)
本人水平有限,如文章内容有错漏之处,敬请各位读者指出,谢谢!