二叉树的基本特性和二叉树的几种基本操作的机制_深入理解二叉树01 二叉树基础

数据结构是所有的程序员就业过程中无法回避的知识,最近在回顾数据结构的内容,因此会不定期的推出一些数据结构的文章,分享自己的笔记。

树是数据结构中的重点,由于二叉树又是树中的重中之重。二叉树的应用也非常的多,比如二叉排序树、平衡二叉树、红黑树、B树、堆等。本篇文章是关于二叉树的第一篇文章,主要介绍二叉树的几种遍历方式以及代码实现。

一、二叉树的基本概念

二叉树的定义:一棵二叉树节点的有限集合,该集合或者为空,或者由一个根节点加上两棵左子树和右子树组成,其左子树和右子树又分别是一棵二叉树。

二叉树的特点:

  • 每个节点最多只有两棵子树,即二叉树不存在度数大于2的节点
  • 二叉树的子树有左、右之分,其子树的次序不能颠倒
  • 二叉树的典型遍历方式有四种:先序遍历、中序遍历、后序遍历、层序遍历

几种特殊的二叉树:

  • 满二叉树:在一棵二叉树中,所有分支节点都存在左子树和右子树,并且所有的叶节点都在同一层上。如图1。
  • 完全二叉树:一棵具有N个节点的完全二叉树的结构与满二叉树的前N个节点的结构相同。如图2。
a2465110586d5f11c215a42b4445ae9d.png

图1:高度为3的满二叉树

fd2dd20abd1833f7c033df384b626478.png

图2:高度为3的完全二叉树

二、二叉树的遍历

在开始介绍遍历之前,首先规定一下二叉树的存储和遍历的接口,定义如下:

typedef struct BinaryTreeNode{    int data;    BinaryTreeNode* leftChild;    BinaryTreeNode* rightChild;}BinaryTreeNode, BinaryTree;class BinaryTreeOperate{public:    static void createBinaryTree(BinaryTree** root);    /* 递归方式操作 */    static void preOrderTraverse(const BinaryTree* root);    static void inOrderTraverse(const BinaryTree* root);    static void postOrderTraverse(const BinaryTree* root);    /* 非递归方式操作 */    static void preOrderTraverseNonRecurve(const BinaryTree* root);    static void inOrderTraverseNonRecurve(const BinaryTree* root);    static void postOrderTraverseNonRecurve(const BinaryTree* root);    /* 宽度优先遍历 */    static void widthFirstTraverse(const BinaryTree* root);};

测试二叉树结构如图3所示:

a50041c6248223b12f2fe7a55dda44e0.png

图3:测试二叉树

1、二叉树的先序遍历

二叉树的先序遍历:先访问根节点,再访问左子树,最后访问右子树。

上述的二叉树的先序遍历结果为:10 6 4 8 14 12 16

递归方式实现:

void BinaryTreeOperate::preOrderTraverse(const BinaryTree* root){    if(root != nullptr)    {        cout << root->data << "";        BinaryTreeOperate::preOrderTraverse(root->leftChild);        BinaryTreeOperate::preOrderTraverse(root->rightChild);    }}

非递归方式实现:

思考一下如何实现:首先访问根节点,然后访问它的左子树;当左子树为空时,我们要退回到上一个访问的节点(根节点),访问它的右子树。如何实现退回到上一个访问的节点的操作?有数据结构基础的人很容易就会想到栈了。

过程:

  • 访问根节点,然后将根节点入栈
  • 遍历根节点的左子树
  • 当左子树为空,获取栈顶元素并出栈,然后访问栈顶元素的右子树
void BinaryTreeOperate::preOrderTraverseNonRecurve(const BinaryTree* root){    stack nodes_stack;    //root用了const修饰,直接将const赋值为non-const是不可以的    //需要先使用const_cast去除root的const限定    BinaryTreeNode* p = const_cast(root);    while(p != nullptr || !nodes_stack.empty())    {        if(p != nullptr)        {            cout << p->data << "";            nodes_stack.push(p);            p = p->leftChild;        }        else         {            p = nodes_stack.top();            nodes_stack.pop();            p = p->rightChild;        }    }}

2、二叉树的中序遍历

二叉树的中序遍历:先遍历左子树,再访问根节点,最后访问右子树。

上述的二叉树的中序遍历结果为:4 6 8 10 12 14 16

递归方式实现:

void BinaryTreeOperate::inOrderTraverse(const BinaryTree* root){    if(root != nullptr)    {        BinaryTreeOperate::inOrderTraverse(root->leftChild);        cout << root->data << "";        BinaryTreeOperate::inOrderTraverse(root->rightChild);    }}

非递归方式实现:

中序遍历的非递归实现和先序遍历类似:

  • 先将根节点入栈
  • 遍历根节点的左子树
  • 当左子树为空时,获取栈定元素并出栈,访问栈顶元素,然后遍历栈顶元素的右子树
void BinaryTreeOperate::inOrderTraverseNonRecurve(const BinaryTree* root){    stack nodes_stack;    BinaryTreeNode* p = const_cast(root);    while(p != nullptr || !nodes_stack.empty())    {        if(p != nullptr)        {            nodes_stack.push(p);            p = p->leftChild;        }        else         {            p = nodes_stack.top();            nodes_stack.pop();            cout << p->data << "";            p = p->rightChild;        }    }}

3、二叉树的后序遍历

二叉树的后序遍历:先遍历左子树,再遍历右子树,最后访问根节点。

上述二叉树的后序遍历结果为:4 8 6 12 16 14 10

递归方式实现:

void BinaryTreeOperate::postOrderTraverse(const BinaryTree* root){    if(root != nullptr)    {        BinaryTreeOperate::postOrderTraverse(root->leftChild);        BinaryTreeOperate::postOrderTraverse(root->rightChild);        cout << root->data << "";    }}

非递归方式实现:

后序遍历的非递归实现稍微复杂一点,先看一下下面的分析:

对于下图所示的这一棵二叉树:

a50041c6248223b12f2fe7a55dda44e0.png

后序遍历的结果为:4 8 6 12 16 14 10

后序遍历的逆序结果为:10 14 16 12 6 8 4

想一下后序遍历的逆序结果是如何得到的:

  • 先遍历根节点
  • 然后遍历右子树
  • 最后遍历左子树

思路出来了:

  1. 模仿先序遍历的过程,但是把左右节点的访问顺序颠倒,同时把访问的结果加到栈中(因为后序遍历是它的一个逆序的过程)
  2. 遍历栈中的所有节点,即为后序遍历的结果
void BinaryTreeOperate::postOrderTraverseNonRecurve(const BinaryTree* root){    stack nodes_stack_1, nodes_stack_2;    BinaryTreeNode* p = const_cast(root);    while(p != nullptr || !nodes_stack_1.empty())    {        if(p != nullptr)        {            nodes_stack_2.push(p);            nodes_stack_1.push(p);            p = p->rightChild;        }        else         {            p = nodes_stack_1.top();            nodes_stack_1.pop();            p = p->leftChild;        }    }    while(!nodes_stack_2.empty())    {        p = nodes_stack_2.top();        nodes_stack_2.pop();        cout << p->data << "";    }}

4、二叉树的层序遍历

二叉树的层序遍历:层序遍历也可以被称为宽度优先遍历,先访问树的第一层节点,再访问树的第二层节点······。再同一层节点中,以从左到右的顺序依次访问。

上述二叉树的层序遍历结果为:10 6 14 4 8 12 16

代码实现(借助队列):

void BinaryTreeOperate::widthFirstTraverse(const BinaryTree* root){    queue nodes_queue;    BinaryTreeNode* p = const_cast(root);    nodes_queue.push(p);    while(!nodes_queue.empty())    {        p = nodes_queue.front();        nodes_queue.pop();        if(p != nullptr)        {            cout << p->data << "";            if(p->leftChild != nullptr)                nodes_queue.push(p->leftChild);            if(p->rightChild != nullptr)                nodes_queue.push(p->rightChild);        }    }}

关于二叉树的遍历操作:递归实现简单,但是效率就比较低,因为编译器帮你做了很多的现场保护和现场恢复的工作;非递归实现都借用了栈,其实也是在模仿递归。两种方式都很重要。

完整代码(包含二叉树创建、遍历的各种实现、main函数测试)如下:

#include #include #include #include using namespace std;// 二叉树的每个节点的值,0表示该节点为空vector tree_data = {10, 6, 4, 0, 0, 8, 0, 0, 14, 12, 0, 0, 16, 0, 0};typedef struct BinaryTreeNode{    int data;    BinaryTreeNode* leftChild;    BinaryTreeNode* rightChild;}BinaryTreeNode, BinaryTree;class BinaryTreeOperate{public:    static void createBinaryTree(BinaryTree** root);    /* 递归方式操作 */    static void preOrderTraverse(const BinaryTree* root);    static void inOrderTraverse(const BinaryTree* root);    static void postOrderTraverse(const BinaryTree* root);    /* 非递归方式操作 */    static void preOrderTraverseNonRecurve(const BinaryTree* root);    static void inOrderTraverseNonRecurve(const BinaryTree* root);    static void postOrderTraverseNonRecurve(const BinaryTree* root);    /* 宽度优先遍历 */    static void widthFirstTraverse(const BinaryTree* root);};void BinaryTreeOperate::createBinaryTree(BinaryTree **root){    static int id = 0;    int tmp_data = tree_data[id++];    if (tmp_data > 0)    {        BinaryTreeNode *newNode = new BinaryTreeNode();        newNode->data = tmp_data;        newNode->leftChild = nullptr;        newNode->rightChild = nullptr;        *root = newNode;        BinaryTreeOperate::createBinaryTree(&((*root)->leftChild));        BinaryTreeOperate::createBinaryTree(&((*root)->rightChild));    }}void BinaryTreeOperate::preOrderTraverse(const BinaryTree* root){    if(root != nullptr)    {        cout << root->data << "";        BinaryTreeOperate::preOrderTraverse(root->leftChild);        BinaryTreeOperate::preOrderTraverse(root->rightChild);    }}void BinaryTreeOperate::preOrderTraverseNonRecurve(const BinaryTree* root){    stack nodes_stack;    //root用了const修饰,直接将const赋值为non-const是不可以的    //需要先使用const_cast去除root的const限定    BinaryTreeNode* p = const_cast(root);    while(p != nullptr || !nodes_stack.empty())    {        if(p != nullptr)        {            cout << p->data << "";            nodes_stack.push(p);            p = p->leftChild;        }        else         {            p = nodes_stack.top();            nodes_stack.pop();            p = p->rightChild;        }    }}void BinaryTreeOperate::inOrderTraverse(const BinaryTree* root){    if(root != nullptr)    {        BinaryTreeOperate::inOrderTraverse(root->leftChild);        cout << root->data << "";        BinaryTreeOperate::inOrderTraverse(root->rightChild);    }}void BinaryTreeOperate::inOrderTraverseNonRecurve(const BinaryTree* root){    stack nodes_stack;    BinaryTreeNode* p = const_cast(root);    while(p != nullptr || !nodes_stack.empty())    {        if(p != nullptr)        {            nodes_stack.push(p);            p = p->leftChild;        }        else         {            p = nodes_stack.top();            nodes_stack.pop();            cout << p->data << "";            p = p->rightChild;        }    }}void BinaryTreeOperate::postOrderTraverse(const BinaryTree* root){    if(root != nullptr)    {        BinaryTreeOperate::postOrderTraverse(root->leftChild);        BinaryTreeOperate::postOrderTraverse(root->rightChild);        cout << root->data << "";    }}void BinaryTreeOperate::postOrderTraverseNonRecurve(const BinaryTree* root){    stack nodes_stack_1, nodes_stack_2;    BinaryTreeNode* p = const_cast(root);    while(p != nullptr || !nodes_stack_1.empty())    {        if(p != nullptr)        {            nodes_stack_2.push(p);            nodes_stack_1.push(p);            p = p->rightChild;        }        else         {            p = nodes_stack_1.top();            nodes_stack_1.pop();            p = p->leftChild;        }    }    while(!nodes_stack_2.empty())    {        p = nodes_stack_2.top();        nodes_stack_2.pop();        cout << p->data << "";    }}void BinaryTreeOperate::widthFirstTraverse(const BinaryTree* root){    queue nodes_queue;    BinaryTreeNode* p = const_cast(root);    nodes_queue.push(p);    while(!nodes_queue.empty())    {        p = nodes_queue.front();        nodes_queue.pop();        if(p != nullptr)        {            cout << p->data << "";            if(p->leftChild != nullptr)                nodes_queue.push(p->leftChild);            if(p->rightChild != nullptr)                nodes_queue.push(p->rightChild);        }    }}int main(){    BinaryTree* root;    BinaryTreeOperate::createBinaryTree(&root);    cout << "先序遍历结果: " << endl;    BinaryTreeOperate::preOrderTraverse(root);    cout << "";    BinaryTreeOperate::preOrderTraverseNonRecurve(root);    cout << "中序遍历结果: " << endl;    BinaryTreeOperate::inOrderTraverse(root);    cout << "";    BinaryTreeOperate::inOrderTraverseNonRecurve(root);    cout << "后序遍历结果: " << endl;    BinaryTreeOperate::postOrderTraverse(root);    cout << "";    BinaryTreeOperate::postOrderTraverseNonRecurve(root);    cout << "宽度优先遍历结果:" << endl;    BinaryTreeOperate::widthFirstTraverse(root);    return 0;}

运行结果如图4所示:

d50be29eca5c430c7ed6b0508330d925.png

图4:完整代码的运行结果

今天的内容就到这儿了。如果对我的推、文有兴趣,欢迎转、载分、享。也可以推荐给朋友关、注哦。只推干货,宁缺毋滥。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值