二叉树的创建和遍历

一、基础

在创建二叉树之前,我们先来了解一下二叉树和几个特殊的二叉树

首先说一下二叉树:二叉树就体现在“二”上,也就是,每一个结点最多只有两个子树,即二叉树中不存在度大于2的结点;除此之外,二叉树的两个子树有左右之分,也就是左右子树的顺序不能颠倒。那么我们就可以得出一个结论,二叉树是一个有序树。

如下图,这就是一个二叉树,我们接下来创建二叉树就用这个例子,那么创建之后的各种遍历,当然也都是用这个。

这里写图片描述

在二叉树的分类中有几个特殊的二叉树,其中所有分支结点都存在左子树和右子树,并且,所有的叶子结点都在同一个层,这样的二叉树就是满二叉树。

这里写图片描述

还有一个比较特殊的,应用也比较多的二叉树是完全二叉树,是这样叙述:如果一棵具有N个结点的二叉树的结构与满二叉树的前N个结点的结构相同,那么这就是完全二叉树了,解释一下,这里的额前N个结点是说的是,将二叉树上的结点,按照从上到下,,从左到右的顺序编号,后面的层序也是这样。

这里写图片描述

之后我们再来说一下二叉树的创建,如果要想创建一个二叉树,那么你会从哪入手呢?有人想从根结点入手,也有人想从左子树入手,那么到底该怎么进行呢?

在这里我们总结几种方法:

先序:根结点——>左子树——>右子树
中序:左子树——>根结点——>右子树
后序:左子树——>右子树——>根结点
层序:从根结点开始,按照从上到下,从左到右的顺序

同样的,二叉树的遍历也是这几种方法,那么接下来我们就来分析一下二叉树的创建和遍历吧。

二、创建

二叉树的创建我们用先序的例子来介绍。按照先序的顺序,首先应该创建根结点,然后再创建左子树,然后是右子树。其中左子树也是二叉树,那么完全可以自己调用自己,也就是递归。思路很简单,那么接下来就是实现了。

我们想要创建创建二叉树的结点,首先分析一下,结点有什么,保存在这个结点的值是一个吧,还有就是指向两个子树的指针也是吧。我们想创建这个结点,那么可以将构造函数给出来吧。之后将这些封装到结构体中就行了。

template<class T>
struct BinaryTreeNode
{
    BinaryTreeNode(const T& value)
    : _value(value)
    , _pLeft(NULL)
    , _pRight(NULL)
    {}

    T _value;
    BinaryTreeNode<T>* _pLeft;   // 左孩子
    BinaryTreeNode<T>* _pRight;  // 右孩子
};

之后就是创建这个二叉树的实现了,前面大致提了一下这个方法,但是具体怎么实现呢?

我们用先序创建的顺序是“124356”,但是我们就这样将创建的时候会出现什么现象呢?这个会一直创建左子树,最后会创建一个左单支,显然不是我们想要的,那么问题出在哪里呢?

当我们创建124的时候显然是没有问题的,但是3是1的右子树,但是我们创建的时候把3给成4的左子树了,而4的左子树本来是没有的,所以我们得想个办法处理一下左右子树为空的时候。

其实很简单,我们可以将这些避开就行了,可以定义一个invalid,只要出现是invalid的时候,那么就跳过直接进行下一步。

如图:

这里写图片描述

那么我们的数组应该给成:“124###35##6”,然后就是实现了:

//创建二叉树
    void _CreateBinaryTree(Node* &pRoot, const T array[], size_t size, size_t& index, const T& invalid)
    {
        if (index < size&&array[index] != invalid)
        {
            pRoot = new Node(invalid);//注意new的时候要和结构体中写的函数参数对应
            pRoot->_value = array[index];

            _CreateBinaryTree(pRoot->_pLeft, array, size, ++index, invalid);//注意:++index
            _CreateBinaryTree(pRoot->_pRight, array, size, ++index, invalid);
        }
    }

遍历其实也是这几种方法

先序:根结点——>左子树——>右子树
中序:左子树——>根结点——>右子树
后序:左子树——>右子树——>根结点
层序:从根结点开始,按照从上到下,从左到右的顺序

首来看先序:

这里写图片描述

那么我们先来将先序的遍历结构写出来:124356,思路很简单,先找到根结点,访问,然后再遍历左子树,最后是右子树,如果用递归的话,这样差不多就完了,不过,只是差不多,递归出口我们还没有给出呢。

那么递归出口应该怎么给呢?我们再来看图,访问到4的位置,左子树是不是访问完了,再访问,那么左子树就为空了,不能访问,因此,我们可以将pCur判断一下,只要其不为空,那么就可以继续递归,如果它为空,那么就返回。

那么接下来就是将这些思路翻译成代码,然后测试一下,看结果是不是“124356”

// 先序:访问根节点--->访问根节点的左子树--->访问根节点的右子树
    void _PreOrder(Node* pRoot)   //时间复杂度:
    {
        Node* pCur = pRoot;
        if (pCur == NULL)
            return;

        cout << pCur->_value << " ";

        _PreOrder(pCur->_pLeft);

        _PreOrder(pCur->_pRight);
    }

那么不用递归呢?

再来回顾一下先序的遍历顺序:根结点—–左子树—–右子树

进入左子树中还是这样,直到最后一个结点是空,那么这个过程是不是可以看成找这个二叉树的最左边的结点,并且在找的过程中,将路径上面的结点全部访问了,之后然后在访问右子树,但是这时候右子树怎么找呢?

我们可以在用栈将要遍历的结点保存起来,这时候就是体现模板的强大了,我们需要的是结构体类型的,那么定义栈的时候只需将Node*加入实例化列表中就行了。

然后再来看我们的思路:

1、首先我们得判断树是不是空的,如果是,那么我们就不需要处理了,return;就行了

2、然后将根结点入栈

3、取出栈顶的元素,访问了,让其出栈

4、再将这个结点的左子树和右子树压栈,不过要是不存在就不用了,所以还是加一个判断语句为好。

3、4其实是循环进行的,我们判断执行下一步的语句就是不断取出栈顶元素,现在思路写的差不多了,再来通过这个例子捋一遍:

首先将1压栈,然后取出1,访问,再将1的左右子树压栈,即2、3压栈,这时候循环,再来去栈顶元素,是3。。。。怎么是3呢?按理说我们因应该遍历2才对的吧,这时候就不得不提栈的性质了,后进先出,所以将左子树和右子树和右子树的压栈顺序调整一下,先将右子树压栈,再将左子树压栈

    //先序-----(非递归):用栈实现
    void _PreOrder_Nor(Node *pRoot)
    {
        if (pRoot == NULL)
            return;
        stack<Node*> s;
        s.push(pRoot);

        while (!s.empty())
        {
            Node *pCur = s.top();
            cout << pCur->_value << " ";

            s.pop();//时刻记住,访问完后要出栈

            if (pCur->_pRight)//注意,入栈顺序是先右后左
                s.push(pCur->_pRight);
            if (pCur->_pLeft)
                s.push(pCur->_pLeft);
        }
    }

中序的遍历和先序的差不多,只是遍历的顺序不同而已,中序是先访问左子树,再访问根结点,那么我们将先序遍历的方法中递归左子树和访问根结点两条语句交换一下就行了。

这里写图片描述

    // 中序:访问根节点的左子树--->访问根节点--->访问根节点的右子树
    void _InOrder(Node* pRoot)
    {
        Node *pCur = pRoot;
        if (pCur == NULL)
            return;

        _InOrder(pCur->_pLeft);

        cout << pCur->_value << " ";

        _InOrder(pCur->_pRight);
    }

同样的,中序的非递归实现方法也是通过栈实现的,那么这个可不可以像递归的形式一样,将访问根结点和左子树交换一下呢?

其实,这样做是没有毛病的,中序的非递归的思路也很简单:

1、判断树是否为空,为空,那么直接return;

2、将根结点入栈

3、找最左边的结点,并保存路径上的所有结点(注:只是保存,不能访问)

4、找到之后,取出栈顶元素,并访问,让其出栈

5、pCur = pCur->_pRight

第四步我们访问的实际上是根结点,那么根据中序遍历的顺序,我们该访问右子树了,但是,右子树的根结点可能不存在,所以,我们在最外层的循环中应该再加一个条件,当前结点不为空的时候才能循环,那么如果右子树为空,那么,就进不去循环,但是现在栈不为空,又可以进入循环,在取栈顶元素进行访问,因此,最外围新加的条件和原来判断栈是否为空的条件应该是“||”的关系吧。

    //中序(非递归)----用栈
    void _InOrder_Nor(Node *pRoot)
    {
        if (pRoot == NULL)
            return;

        stack<Node*> s;
        Node *pCur = pRoot;

        while (pCur || !s.empty())
        {
            //找最左边的结点,并保存到栈中
            while (pCur)
            {
                s.push(pCur);
                pCur = pCur->_pLeft;
            }

            //找到最左边的结点之后,取栈顶元素(根),并访问,后出栈
            pCur = s.top();
            cout << pCur->_value << " ";
            s.pop();

            pCur = pCur->_pRight;//可以分情况讨论
        }
    }

后序也是这样,将中序的访问根结点和递归右子树交换一下即可

这里写图片描述

    // 后续遍历:遍历根的左子树-->遍历根的右子树-->遍历根节点
    void _PostOrder(Node* pRoot)
    {
        Node *pCur = pRoot;
        if (pCur == NULL)
            return;

        _PostOrder(pCur->_pLeft);

        _PostOrder(pCur->_pRight);

        cout << pCur->_value << " ";
    }

后序的循环实现也是通过栈,步骤和先序中序差不多,不过,具体的实现总是会发现问题的。

我们先来说一下这个思路:

1、判断树是否为空,为空,return;

2、保存根结点到栈中

3、找最左边的结点,并且将路径上的结点都保存。

4、取出栈顶元素,

5、如果右子树为空或是已经访问过,那么就可以访问了

由于根结点的访问条件中有一个是右子树已经访问过,那么如何判断已经访问过呢?其实很简单,只需在没访问一个结点的时候将其用一个指针(prev)指向即可,那么我们在判断右子树是否访问过的时候,只需将指向右子树的指针和prev比较一下就行了,如果相等,那么我们就可以访问根结点了。

    //后序(非递归)----用栈
    void _PostOrder_Nor(Node * pRoot)
    {
        if (pRoot == NULL)
            return;

        stack<Node*> s;
        Node* pCur = pRoot;
        Node*prev = NULL;//用来指向最新一次访问的结点

        while (pCur||!s.empty())
        {
            while (pCur)
            {
                s.push(pCur);
                pCur = pCur->_pLeft;
            }

            Node* pTop = s.top();

            //访问根结点---->右为空 或者 右已经访问过了
            if (pTop->_pRight == NULL || pTop->_pRight == prev)
            {
                cout << pTop->_value << " ";
                prev = pTop;//将最新访问过的结点保存
                s.pop();
            }
            else
                pCur = pTop->_pRight;
        }
    }

层序的访问顺序是从上到下,从左到右的,按照我们的例子来说是1,2,3,4,5,6

其中1是一层,2,3是一层,并且是1的左右孩子;同样,4,是2的左孩子,5,6是3的左右孩子。

那么我们就可以想一下,是不是可以将1保存,再将1的左右子树保存,之后再访问1;然后再保存2的左右子树(如果存在的话),访问2;再访问3的左右子树,访问3;再循环下去的话,是该2的左子树了……

如此,一边保存,一边取出访问,会联想起什么呢?

没错,就是队列,应用队列,我们再来捋一遍思路:

1、判断树是不是空树:是,return;

2、将根结点放入队列

3、取出队头元素,将其左右孩子存入队列(判断孩子是否存在)

4、访问当前结点,之后让其出队列

3、4这两个步骤其实是循环进行的,其中循环的执行流就是不断地取对头元素,直到队列为空停止。

//层序-----用队列实现
    void _LevelOrder(Node *pRoot)
    {
        if (pRoot == NULL)
            return;
        queue<Node *> q;
        q.push(pRoot);//先将根结点放入队列

        while (!q.empty())
        {
            Node* pCur = q.front();//取队头元素,将其存在的左右孩子放入队列
            if (pCur->_pLeft)
                q.push(pCur->_pLeft);
            if (pCur->_pRight)
                q.push(pCur->_pRight);

            cout << pCur->_value << " ";//访问当前结点
            q.pop();//将访问过的结点出队列
        }
    }

下面是具体的实现代码,包括拷贝构造函数和赋值运算符重载:

不过如果你的具体代码不想让别人知道的话,可以将代码在封装一层,将具体的实现代码写在private

#include<iostream>
using namespace std;

#include<queue>
#include<stack>

// 孩子表示法
template<class T>
struct BinaryTreeNode
{
    BinaryTreeNode(const T& value)
    : _value(value)
    , _pLeft(NULL)
    , _pRight(NULL)
    {}

    T _value;
    BinaryTreeNode<T>* _pLeft;   // 左孩子
    BinaryTreeNode<T>* _pRight;  // 右孩子
};


template<class T>
class BinaryTree
{
    typedef BinaryTreeNode<T> Node;//换个名字Node
public:
    //构造函数①
    BinaryTree()
        :_pRoot(NULL)
    {}

    //构造函数②
    BinaryTree(const T array[], size_t size, const T& invalid)
    {
        size_t index = 0;
        _CreateBinaryTree(_pRoot, array, size, index, invalid);
    }

    //拷贝构造函数
    BinaryTree(const BinaryTree<T>& bt)
    {
        _pRoot = _CopyBirnaryTree(bt._pRoot);
    }

    //赋值运算符重载
    BinaryTree<T>& operator=(const BinaryTree<T>& bt)
    {
        if (this != &bt)
        {
            _CopyBirnaryTree(bt._pRoot);
        }
        return this;
    }

    //析构函数
    ~BinaryTree()
    {
        _DestroyBinaryTree(_pRoot);
    }


    //先序遍历
    void PreOrder()
    {
        cout << "PreOrder:" << endl;
        _PreOrder(_pRoot);
        cout << endl;
    }

    先序遍历(非递归)
    void PreOrder_Nor()
    {
        cout << "PreOrder_Nor:" << endl;
        _PreOrder_Nor(_pRoot);
        cout << endl;
    }

    //中序遍历
    void InOrder()
    {
        cout << "InOrder:" << endl;
        _InOrder(_pRoot);
        cout << endl;
    }

    // 中序遍历(非递归)。哪一种快?
    void InOrder_Nor()
    {
        cout << "InOrder_Nor:" << endl;
        _InOrder_Nor(_pRoot);
        cout << endl;
    }

    //后序遍历
    void PostOrder()
    {
        cout << "PostOrder:" << endl;
        _PostOrder(_pRoot);
        cout << endl;
    }

    //后序遍历(非递归)
    void PostOrder_Nor()
    {
        cout << "PostOrder:" << endl;
        _PostOrder_Nor(_pRoot);
        cout << endl;
    }

    // 层序遍历
    void LevelOrder()
    {
        cout << "LevelOrder:" << endl;
        _LevelOrder(_pRoot);
    }

private:
    //创建二叉树
    void _CreateBinaryTree(Node* &pRoot, const T array[], size_t size, size_t& index, const T& invalid)
    {
        if (index < size&&array[index] != invalid)
        {
            pRoot = new Node(invalid);//注意new的时候要和结构体中写的函数参数对应
            pRoot->_value = array[index];

            _CreateBinaryTree(pRoot->_pLeft, array, size, ++index, invalid);//注意:++index
            _CreateBinaryTree(pRoot->_pRight, array, size, ++index, invalid);
        }
    }

    // pRoot-->被拷贝树的根节点
    Node* _CopyBirnaryTree(Node* pRoot)
    {
        if (pRoot == NULL)
            return NULL;
        Node *NewRoot = new Node(pRoot->_value);
        NewRoot->_pLeft = _CopyBirnaryTree(pRoot->_pLeft);
        NewRoot->_pRight = _CopyBirnaryTree(pRoot->_pRight);
        return NewRoot;
    }

    //销毁二叉树
    void _DestroyBinaryTree(Node*& pRoot)
    {
        Node* temp = pRoot;
        if (temp == NULL)
            return;
        _DestroyBinaryTree(temp->_pLeft);
        _DestroyBinaryTree(temp->_pRight);
        delete temp;
        temp = NULL;
    }


    // 先序:访问根节点--->访问根节点的左子树--->访问根节点的右子树
    void _PreOrder(Node* pRoot)   //时间复杂度:
    {
        Node* pCur = pRoot;
        if (pCur == NULL)
            return;

        cout << pCur->_value << " ";

        _PreOrder(pCur->_pLeft);

        _PreOrder(pCur->_pRight);
    }

    //先序-----(非递归):用栈实现
    void _PreOrder_Nor(Node *pRoot)
    {
        if (pRoot == NULL)
            return;
        stack<Node*> s;
        s.push(pRoot);

        while (!s.empty())
        {
            Node *pCur = s.top();
            cout << pCur->_value << " ";

            s.pop();//时刻记住,访问完后要出栈

            if (pCur->_pRight)//注意,入栈顺序是先右后左
                s.push(pCur->_pRight);
            if (pCur->_pLeft)
                s.push(pCur->_pLeft);
        }
    }

    // 中序:访问根节点的左子树--->访问根节点--->访问根节点的右子树
    void _InOrder(Node* pRoot)
    {
        Node *pCur = pRoot;
        if (pCur == NULL)
            return;

        _InOrder(pCur->_pLeft);

        cout << pCur->_value << " ";

        _InOrder(pCur->_pRight);
    }

    //中序(非递归)----用栈
    void _InOrder_Nor(Node *pRoot)
    {
        if (pRoot == NULL)
            return;

        stack<Node*> s;
        Node *pCur = pRoot;

        while (pCur || !s.empty())
        {
            //找最左边的结点,并保存到栈中
            while (pCur)
            {
                s.push(pCur);
                pCur = pCur->_pLeft;
            }

            //找到最左边的结点之后,取栈顶元素(根),并访问,后出栈
            pCur = s.top();
            cout << pCur->_value << " ";
            s.pop();

            pCur = pCur->_pRight;//可以分情况讨论
        }
    }

    // 后续遍历:遍历根的左子树-->遍历根的右子树-->遍历根节点
    void _PostOrder(Node* pRoot)
    {
        Node *pCur = pRoot;
        if (pCur == NULL)
            return;

        _PostOrder(pCur->_pLeft);

        _PostOrder(pCur->_pRight);

        cout << pCur->_value << " ";
    }

    //后序(非递归)----用栈
    void _PostOrder_Nor(Node * pRoot)
    {
        if (pRoot == NULL)
            return;

        stack<Node*> s;
        Node* pCur = pRoot;
        Node*prev = NULL;//用来指向最新一次访问的结点

        while (pCur||!s.empty())
        {
            while (pCur)
            {
                s.push(pCur);
                pCur = pCur->_pLeft;
            }

            Node* pTop = s.top();

            //访问根结点---->右为空 或者 右已经访问过了
            if (pTop->_pRight == NULL || pTop->_pRight == prev)
            {
                cout << pTop->_value << " ";
                prev = pTop;//将最新访问过的结点保存
                s.pop();
            }
            else
                pCur = pTop->_pRight;
        }
    }

    //层序-----用队列实现
    void _LevelOrder(Node *pRoot)
    {
        if (pRoot == NULL)
            return;
        queue<Node *> q;
        q.push(pRoot);//先将根结点放入队列

        while (!q.empty())
        {
            Node* pCur = q.front();//取队头元素,将其存在的左右孩子放入队列
            if (pCur->_pLeft)
                q.push(pCur->_pLeft);
            if (pCur->_pRight)
                q.push(pCur->_pRight);

            cout << pCur->_value << " ";//访问当前结点
            q.pop();//将访问过的结点出队列
        }
    }

private:
    Node* _pRoot;   // 指向树的根节点
};


void FunTest()
{
    char* pStr = "124###35##6";

    BinaryTree<char> bt(pStr, strlen(pStr), '#');
    //bt.PreOrder();
    //bt.InOrder();
    //bt.PostOrder();
    //bt.LevelOrder();

//  BinaryTree<char> bt1(bt);
//  bt1.PreOrder();
//  bt1.InOrder();
//  bt1.PostOrder();
//
//  BinaryTree<char> bt2;
//  bt2 = bt1;
//  bt2.PostOrder();

    bt.PreOrder_Nor();
    bt.InOrder_Nor();
    bt.PostOrder_Nor();
}


int main()
{
    FunTest();
    return 0;
}

结果:

这里写图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值