【每日算法】二叉树的遍历

二叉树特点

  1. 每个节点最多有两棵子树;
  2. 二叉树是有序的,即区分左右子树的次序。

完全二叉树

  1. 叶子节点只能出现在最下两层,且最下层的叶子节点都集中在二叉树左侧连续的位置。

  2. 如果有度为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,有以下两种情况:

  1. bt不为NULL,则bt及标志(置为1)入栈,遍历其左子树;
  2. 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!

(●’◡’●)

本人水平有限,如文章内容有错漏之处,敬请各位读者指出,谢谢!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值