二叉树遍历

介绍常规的三种递归遍历方法(先中后)以及非递归的先序、中序、后序、层序遍历

Prerequisite

对二叉树这种数据结构有一定了解
遍历二叉树的目的是为了访问到每个结点且按照某一特定的顺序

二叉树

先序遍历

1.遍历顺序

一定要记住 子树的->->

2.递归遍历

递归的特点就是代码可读性高,容易理解

/*先序遍历,以rt为根的树*/ //根->左->右
template <class T>
void BinaryTree<T>::PreOrder(const Node* rt, void(*Visit)(const T&)) const {
    if (rt != nullptr) {
        (*Visit)(rt->data);
        PreOrder(rt->left_child, Visit);
        PreOrder(rt->right_child, Visit);
    }
} 

3.非递归遍历

先分析先序遍历效果:如上图先序遍历的顺序为ABCDEFGHK
根总是先被访问,之后被访问的就是他的左孩子,左孩子又称为新的根,于是继续访问他的左孩子,直到这个根没有左孩子,之后访问他的右儿子,如果他没有右儿子,如图中的D ,追溯到他的父节点去访问右儿子,一直追溯,直到有了右儿子,如EA的右儿子, E又作为新的根节点先被访问 ,继续一开始的遍历。
可能要被我绕晕了,简言之,得到根节点之后先对其访问,然后要访问全部的左儿子,最后再是右儿子。所以,一颗子树的右儿子要放在最后访问,就好比压在最下面,等上面的根和左儿子访问过了在出来,于是,这里我们用到了
上代码!

/*非递归先序遍历*/
template <class T>
void BinaryTree<T>::NoRecurringPreOrder(const Node* rt, void(*Visit)(const T&)) const {
    if (rt == nullptr) {
        return;
    }
    stack<Node*> s;
    Node* temp = nullptr;
    s.push(rt);
    while (s.empty() == false) {
        temp = s.top();
        s.pop();
        (*Visit)(temp->data);
        if (temp->right_child != nullptr) {     //右节点后访问,先压入栈
            s.push(temp->right_child);
        }
        if (temp->left_child != nullptr) {
            s.push(temp->left_child);
        }
    }
}

中序遍历

1.遍历顺序

子树的->->

2.递归遍历

代码:

/*中序遍历*/   //左->根->右
template <class T>
void BinaryTree<T>::InOrder(const Node* rt, void(*Visit)(const T&)) const {
    if (rt != nullptr) {
        InOrder(rt->left_child, Visit);
        (*Visit)(rt->data);
        InOrder(rt->right_child, Visit);
    }
}

3.非递归遍历

遍历效果:上图遍历顺序为BDCAEHGKF

遍历思路:一路向左!直到这个左孩子作为根的时候没有它自己的左孩子,那就先访问他,因为此时按照遍历顺序->->,他被当成根了(没有左孩子),访问他完之后就取访问他的右儿子,继续一路向左,重复过程...

伪代码:当前根不为空的时候=>压入=>他成为他的左儿子=>循环,如果当前是空=>取栈顶(当前根)访问他,并且pop()=>他成为他的右儿子=>循环,栈空或者最初根为nullptr的时候退出。

上代码:

/*非递归中序遍历*/
template <class T>
void BinaryTree<T>::NoRecurringInOrder(Node* rt, void(*Visit)(const T&)) const {
    Node* temp = rt;
    stack<Node*> s;
    while (temp || s.empty() == false) {
        if (temp) {
            s.push(temp);
            temp = temp->left_child;
        }
        else {                      //没有左孩子了,就取当前栈顶(是上一个根的左孩子)访问,并pop
            temp = s.top();             
            s.pop();
            (*Visit)(temp->data);
            temp = temp->right_child;
        }
    }
}

后序遍历

1.遍历顺序

子树的->->

2.递归遍历

代码:

/*后序遍历*/ //左->右->根
template <class T>
void BinaryTree<T>::PostOrder(const Node* rt, void(*Visit)(const T&)) const {
    if (rt) {
        PostOrder(rt->left_child, Visit);
        PostOrder(rt->right_child, Visit);
        (*Visit)(rt->data);
    }
}

3.非递归遍历

遍历效果:DCBHKGFEA

关于思路

后序遍历的非递归就比前两种的要复杂一点了,也比较难理解,因为根是最后被访问的,而在栈顶的元素当做根的话并不知道他的儿子们有没有被访问过,所以开一个栈不够,起码还要一个栈来保存一个标记,这个标记用来记录他的儿子们有没有被访问过。

关于方法

方法有很多种,可以开一个布尔栈放入对应根节点的标记,判断当前根已经被访问过儿子们了即可出栈,否则就压入他的两个儿子(按顺序是先左后右,又因为栈的关系,所以先压入右儿子)。

这里呢我就介绍一种方法:开一个栈,每次节点压入都压两次,出栈的时候判断栈顶元素是否和他相同,如果两个相同,就说明他的儿子没有被访问(压入栈),就先压入他的右孩子,再左孩子,注意每次压入都是压两个,空节点不入栈。

代码:

/*非递归后序遍历*/
void BinaryTree<T>::NoRecurringPostOrder(Node* rt, void(*Visit)(const T&)) const {
    if (rt == nullptr) {
        return;
    }
    stack<Node*> s;
    Node* temp = rt;
    s.push(temp);
    s.push(temp);
    while (s.empty() == false) {
        temp = s.top();
        s.pop();
        if (!s.empty() && temp == s.top()) {//栈空了就只剩最后一个,直接访问
            if (temp->right_child) {        //先压入右孩子
                s.push(temp->right_child);
                s.push(temp->right_child);
            }
            if (temp->left_child) { 
                s.push(temp->left_child);
                s.push(temp->left_child);
            }
        }
        else {
            (*Visit)(temp->data);
        }
    }
}

目前我看到的方法不管是开两个栈还是一个栈,需要的空间都是总节点数的两倍。

层序遍历

1.遍历顺序

按照层数从左往右依次遍历

上图遍历效果:ABECFDGHK

2.利用队列层序遍历

思路:访问的顺序像排队一样逐层遍历,每层的下面一层,按照从左到右的顺序在当前层的最后排好队,当前层也是按照从左往右的顺序被排好了,那在访问当前层的节点时将儿子们(下一层)排入尾,这样就可以实现了。可以看成是一种递推,把根看成最初的一个人在排队

上代码:

/*层序遍历,利用队列,也可以利用数组,然后用两个指针去控制入和出*/
template <class T>
void BinaryTree<T>::LevelOrder(Node* rt, void(*Visit)(const T&)) const {
    queue<Node*> q;
    Node* temp;
    if (rt != nullptr) {
        q.push(rt);
    }
    while (!q.empty()) {
        temp = q.front();
        q.pop();        //pop的位置?
        (*Visit)(temp->data);
        if (temp->left_child) {
            q.push(temp->left_child);
        }
        if (temp->right_child) {
            q.push(temp->right_child);
        }
    }
}

3.利用数组实现

还有一种利用数组来实现的方法,网上找到的,大致思路其实就是用数组实现了队列的功能:通过两个指针 in out(都是下标)控制,个人认为会浪费资源空间,不过思路很好。

void FloorPrint(pTreeNode Tree)  //层序遍历
{
    pTreeNode temp[100];   //创建pTreeNode指针类型的指针数组
    int in = 0;
    int out = 0;

    temp[in++] = Tree;  //先保存二叉树根节点

    while (in > out)
    {
        if (temp[out])
        {
            cout << temp[out]->data << " → ";
            temp[in++] = temp[out]->leftPtr;
            temp[in++] = temp[out]->rightPtr;
        }
        out++;
    }
}
作者:askunix_hjh
来源:CSDN
原文:https://blog.csdn.net/m0_37925202/article/details/80796010
版权声明:本文为博主原创文章,转载请附上博文链接!

小结

二叉树的遍历很重要(我们老师是这么说的。。可能只是为了应付考试吧)我还是比较推荐在理解的基础上掌握知识,要不断的在脑海中模拟遍历的过程,递归的和非递归的。

内容来源于网络如有侵权请私信删除
你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!