数据结构中二叉树实现及部分操作

2023-11-14

谈二叉树之前,我们先来看看树的定义

树:由N(N>=0)个结点构成的集合。
对N>1的树:
1、有一个特殊的结点,称为根结点,根节点没有前驱结点
2、除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。
因此,树是递归定义的。
如下图所示:
这里写图片描述

再看一些和树有关的概念:

  • 节点:节点包含数据和指向其它节点的指针。
  • 根节点:树第一个结点称为根节点。
  • 节点的度:节点所拥有的子树的个数。
  • 树的度:树中所有节点的度的最大值称为该树的度。
  • 叶子节点:没有子节点的节点(度为0),也称为终端节点。
  • 父子节点:一个节点father指向另一个节点child,则child为孩子节点,father为父亲节点 。
  • 兄弟节点:具有相同父节点的节点互为兄弟节点。
  • 祖先节点:从根节点开始到该节点所经的所有节点都可以称为该节点的祖先。
  • 子孙节点:以某节点为根的子树中任一节点都称为该节点的子孙。
  • 树的高度:树中距离根节点最远节点的路径长度。

树中比较特殊的就是二叉树了,来看看二叉树的定义
二叉树:
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵分别称为左子树和右子树的二叉树组成。

二叉树的特点:
  • 每个结点最多有两棵子树,即二叉树不存在度大于2的结点(分支数最大不超过2)
  • 二叉树的子树有左右之分,其子树的次序不能颠倒

看张二叉树的示意图:
这里写图片描述
二叉树就是由上面这五种情况嵌套或组合而成

在介绍两种比较特殊的二叉树

1.完全二叉树

如果一棵具有N个结点的二叉树的结构与满二叉树的前N个结点的结构相同,称为完全二叉树。

这里写图片描述

2.满二叉树

在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子节点都在同一层上。

这里写图片描述

从概念和图我们可以看出:满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。

接下来就是二叉树的具体实现了,直接上代码:

BinaryTree.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once

#include <iostream>
using namespace std;
#include <stdlib.h>
#include <queue>
#include <stack>

//二叉树节点
template <class T>
struct BinaryTreeNode
{
    BinaryTreeNode(const T& data)
    :_pLeft(NULL)
    , _pRight(NULL)
    , _data(data)
    {}

    BinaryTreeNode<T>* _pLeft;//左子树
    BinaryTreeNode<T>* _pRight;//右子树
    T _data;//数据
};

//二叉树
template<class T>
class BinaryTree
{
    typedef BinaryTreeNode<T>* pNode;
    typedef BinaryTreeNode<T> Node;
public:
    //无参构造函数
    BinaryTree()
        : _pRoot(NULL)
    {}
    //带参数的构造函数
    BinaryTree(T* arr, size_t size, const T& invalid)
    {
        size_t index = 0;
        _CreateBinaryTree(_pRoot, arr, size, index, invalid);
    }
    //拷贝构造函数
    BinaryTree(const BinaryTree<T>& bt)
    {
        _pRoot = _CopyBinaryTree(bt._pRoot);
    }
    //赋值运算符重载(两种方式均可)
    //BinaryTree& operator=(const BinaryTree<T>& bt)
    //{
    //  if (this != &bt)
    //  {
    //      _DestoryBinaryTree(_pRoot);
    //      _pRoot = _CopyBinaryTree(bt._pRoot);
    //  }
    //  return *this;
    //}
    BinaryTree& operator=(const BinaryTree<T>& bt)
    {
        swap(_pRoot, bt._pRoot);
        return *this;
    }
    //析构函数
    ~BinaryTree()
    {
        _DestoryBinaryTree(_pRoot);
    }
    //递归前序遍历
    void PreOrder()
    {
        _PreOrder(_pRoot);
        cout << endl;
    }
    //递归中序遍历
    void InOrder()
    {
        _InOrder(_pRoot);
        cout << endl;
    }
    //递归后序遍历
    void PostOrder()
    {
        _PostOrder(_pRoot);
        cout << endl;
    }
    //层序遍历(用队列)
    void LevelOrder()
    {
        queue<pNode> q;
        if (_pRoot)
            q.push(_pRoot);
        while (!q.empty())
        {
            pNode cur = q.front();
            q.pop();
            cout << cur->_data << " ";
            if (cur->_pLeft)
            {
                q.push(cur->_pLeft);
            }
            if (cur->_pRight)
            {
                q.push(cur->_pRight);
            }
        }
        cout << endl;
    }
    //非递归前序遍历
    void PreOrderNoR()
    {
        _PreOrderNoR2(_pRoot);
    }
    //非递归中序遍历
    void InOrderNoR()
    {
        _InOrderNoR(_pRoot);
    }
    //非递归后序遍历
    void PostOrderNoR()
    {
        _PostOrderNoR(_pRoot);
    }
    //二叉树节点个数
    size_t Size()
    {
        return _Size(_pRoot);
    }
    //二叉树叶子节点个数
    size_t LeafSize()
    {
        return _LeafSize(_pRoot);
    }
    //二叉树第K层节点个数
    size_t KSize(size_t k)
    {
        return _KSize(_pRoot, k);
    }
    //二叉树高度
    size_t Height()
    {
        return _Height(_pRoot);
    }
    //寻找二叉树中的节点
    pNode Find(const T& data)
    {
        return _Find(_pRoot, data);
    }
    //寻找某节点的双亲节点
    pNode FindParent(pNode node)
    {
        return _FindParent(_pRoot, node);
    }
    //寻找某节点的左孩子节点
    pNode FindLeftChild(pNode node)
    {
        return _FindLeftChild(_pRoot, node);
    }
    //寻找某节点的右孩子节点
    pNode FindRightChild(pNode node)
    {
        return _FindRightChild(_pRoot, node);
    }

//封装函数
private:
    //创建二叉树(前序遍历)
    void _CreateBinaryTree(pNode& pRoot, T* arr, size_t size, size_t& index, const T& invalid)
    {
        if (index < size&&invalid != arr[index])
        {
            pNode pNewNode = new Node(arr[index]);
            _CreateBinaryTree(pNewNode->_pLeft, arr, size, ++index, invalid);
            _CreateBinaryTree(pNewNode->_pRight, arr, size, ++index, invalid);
            pRoot = pNewNode;
        }
    }
    //拷贝二叉树
    pNode _CopyBinaryTree(pNode pRoot)
    {
        if (pRoot == NULL);
        {
            return NULL;
        }
        pNode tmp = new Node(pRoot->_data);
        tmp->_pLeft = _CopyBinaryTree(pRoot->_pLeft);
        tmp->_pRight = _CopyBinaryTree(pRoot->_pRight);
        return tmp;
    }
    //递归前序遍历
    void _PreOrder(pNode pRoot)
    {
        if (pRoot)
        {
            cout << pRoot->_data << " ";
            _PreOrder(pRoot->_pLeft);
            _PreOrder(pRoot->_pRight);
        }
    }
    //递归中序遍历
    void _InOrder(pNode pRoot)
    {
        if (pRoot)
        {
            _InOrder(pRoot->_pLeft);
            cout << pRoot->_data << " ";
            _InOrder(pRoot->_pRight);
        }
    }
    //递归后序遍历
    void _PostOrder(pNode pRoot)
    {
        if (pRoot)
        {
            _PostOrder(pRoot->_pLeft);
            _PostOrder(pRoot->_pRight);
            cout << pRoot->_data << " ";
        }
    }
    //非递归前序遍历
    //方法一:访问根之后直接访问左子树访问完
    void _PreOrderNoR(pNode pRoot)
    {
        stack<pNode> s;
        pNode cur = pRoot;
        while (cur || !s.empty())
        {   
            while (cur)
            {
                cout << cur->_data << " ";
                s.push(cur);
                cur = cur->_pLeft;
            }//此时左子树遍历完
            //取栈顶元素访问右子树(此时栈顶元素已经访问)
            pNode top = s.top();
            s.pop();
            //子问题
            cur = top->_pRight;
        }
        cout << endl;
    }
    //方法二:
    void _PreOrderNoR2(pNode pRoot)
    {
        if (pRoot == NULL)
            return;
        stack<pNode> s;
        s.push(pRoot);//根节点入栈
        while (!s.empty())
        {
            pNode top = s.top();
            cout << top->_data << " ";
            s.pop();
            if (top->_pRight)//先压右子树
                s.push(top->_pRight);
            if (top->_pLeft)//再压左子树
                s.push(top->_pLeft);
        }
        cout << endl;
    }
    //非递归中序遍历
    void _InOrderNoR(pNode pRoot)
    {
        stack<pNode> s;
        pNode cur = pRoot;
        while (cur || !s.empty())
        {
            while (cur)
            {
                s.push(cur);
                cur = cur->_pLeft;
            }//直到最左节点
            //取栈顶元素访问该元素并访问右子树
            pNode top = s.top();
            cout << top->_data << " ";
            s.pop();
            //子问题
            cur = top->_pRight;
        }
        cout << endl;
    }
    //非递归后序遍历
    void _PostOrderNoR(pNode pRoot)
    {
        stack<pNode> s;
        pNode cur = pRoot;
        pNode prev = NULL;//标记最近访问过的节点
        while (cur || !s.empty())
        {
            while (cur)
            {
                s.push(cur);
                cur = cur->_pLeft;
            }//直到最左节点

            pNode top = s.top();//取栈顶元素
            if (top->_pRight == NULL || top->_pRight == prev)//判断该节点的右子树是否为空
            {                                                //再判断是不是第一次在栈顶取到该元素 
                cout << top->_data << " ";
                prev = top;
                s.pop();
            }
            else//不为空,访问右子树
            {
                cur = top->_pRight;
            }
        }
        cout << endl;
    }
    //后序遍历递归销毁二叉树
    void _DestoryBinaryTree(pNode pRoot)
    {
        if (pRoot)
            return;
        _DestoryBinaryTree(pRoot->_pLeft);
        _DestoryBinaryTree(pRoot->_pRight);
        delete pRoot;
        pRoot = NULL;
    }
    //求节点个数
    size_t _Size(pNode pRoot)
    {
        if (!pRoot)
        {
            return 0;
        }
        return _Size(pRoot->_pLeft) + _Size(pRoot->_pRight) + 1;
    }
    //求叶子节点个数
    size_t _LeafSize(pNode pRoot)
    {
        if (!pRoot)
            return 0;
        if (pRoot->_pLeft == NULL && pRoot->_pRight == NULL)
            return 1;
        return _LeafSize(pRoot->_pLeft) + _LeafSize(pRoot->_pRight);
    }
    //求第K层节点的个数
    size_t _KSize(pNode pRoot, size_t k)
    {
        if (pRoot == NULL)
            return 0;
        if (k == 1)
            return 1;
        return _KSize(pRoot->_pLeft, k - 1) + _KSize(pRoot->_pRight, k - 1);
    }
    //求二叉树高度
    size_t _Height(pNode pRoot)
    {
        if (!pRoot)
            return 0;
        if (pRoot->_pLeft == NULL&&pRoot->_pRight == NULL)
            return 1;
        size_t LeftHeight = _Height(pRoot->_pLeft);
        size_t RightHeight = _Height(pRoot->_pRight);
        return LeftHeight > RightHeight ? LeftHeight + 1 : RightHeight + 1;
    }
    //找二叉树中的节点
    pNode _Find(pNode pRoot, const T& data)
    {
        if (pRoot == NULL)
            return NULL;
        if (pRoot->_data == data)
            return pRoot;
        pNode ret = NULL;
        ret = _Find(pRoot->_pLeft, data);
        if (ret)
            return ret;
        return _Find(pRoot->_pRight, data);
    }
    //找二叉树中节点的双亲节点
    pNode _FindParent(pNode pRoot, pNode node)
    {
        //树为空树   寻找的节点为空节点   寻找的节点是根节点(无双亲节点)
        if (pRoot == NULL || node == NULL || pRoot == node)
        {
            return NULL;
        }
        if (node == pRoot->_pLeft || node == pRoot->_pRight)
            return pRoot;
        if (pRoot->_pLeft)
            return _FindParent(pRoot->_pLeft, node);
        return _FindParent(pRoot->_pRight, node);
    }
    //寻找节点的左孩子
    pNode _FindLeftChild(pNode pRoot, pNode node)
    {
        if (node == NULL || pRoot == NULL)
        {
            return NULL;
        }
        pNode cur = _Find(pRoot, node->_data);
        return cur->_pLeft;
    }
    //寻找节点的右孩子
    pNode _FindRightChild(pNode pRoot, pNode node)
    {
        if (node == NULL || pRoot == NULL)
        {
            return NULL;
        }
        pNode cur = _Find(pRoot, node->_data);
        return cur->_pRight;
    }

private:
    pNode _pRoot;
};

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include "BinaryTree.h"


void FunTest1()
{
    char arr1[] = { 'A', 'B', 'D', '#', 'G', '#', '#', '#', 'C', 'E', '#', '#', 'F' };
    BinaryTree<char> bt1(arr1, sizeof(arr1) / sizeof(arr1[0]), '#');
    bt1.PreOrder();
    bt1.InOrder();
    bt1.PostOrder();
    bt1.LevelOrder();
    cout << bt1.Size() << endl;
    cout << bt1.LeafSize() << endl;
    cout << bt1.KSize(3) << endl;
    cout << bt1.Height() << endl;
    cout << bt1.KSize(4) << endl;

    BinaryTreeNode<char>* ret1 = bt1.Find('B');
    BinaryTreeNode<char>* ret2 = bt1.FindParent(ret1);
    BinaryTreeNode<char>* ret3 = bt1.FindLeftChild(ret1);
    BinaryTreeNode<char>* ret4 = bt1.FindRightChild(ret1);
    bt1.PreOrderNoR();
    bt1.InOrderNoR();
    bt1.PostOrderNoR();
}

int main()
{
    FunTest1();
    system("pause");
    return 0;
}

结果在这里不做演示,读者可以自行测试。
(注:对二叉树中常见的面试题做出整理后,会附上链接)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构中二叉树实现及部分操作 的相关文章

随机推荐

  • Linux的用户空间与内核空间

    一 简介 Linux 操作系统和驱动程序运行在内核空间 应用程序运行在用户空间 两者不能简单地使用指针传递数据 因为Linux使用的虚拟内存机制 用户空间的数据可能被换出 当内核空间使用用户空间指针时 对应的数据可能不在内存中 用户空间的内
  • vue3项目引入高德地图详细方法教程

    项目需求需要引入地图 对于目前最新的Vue3 0 无论是百度 高德 腾讯地图目前还没有适配 只有Vue 2 x版本的 目前只有谷歌地图的Vue3 0适配 但是没有适配并不代表不能使用 下面就来教大家如何使用 1 在高德开发平台申请你的key
  • react定义函数,默认函数参数的方式

    参数是 对象 有传入参数用传入参数作为入参数 无传入参数用默认值 getTableData async pageData gt const params Object assign currPage 1 pageSize this stat
  • 网传字节跳动实习生删除GB以下所有机器学习模型,差点没上头条

    作者 陈大鑫 陈彩娴 来源 AI科技评论 昨晚脉脉上有网友爆料 字节跳动一位实习生删除了公司所有轻量级别的机器学习模型 什么是lite模型 该楼主表示 lite模型就是公司内几乎所有GB大小以下的机器学习模型 且全部被删除了 实习生直接删除
  • 公司固定资产怎么明细管理

    固定资产的管理是一个至关重要的环节 它不仅影响到企业的运营效率和经济效益 也直接影响到公司的长期发展 因此 对固定资产进行精细化管理 是每一个负责任的企业都应该做到的 本文将探讨如何通过创新的方式 实现公司固定资产的明细管理 我们需要明确什
  • 设置vscode终端的最大输出行

    使用vscode终端输出的时候 如果输出的行数很多 之前打印的东西就看不到了 因此需要设置一下终端输出的最大行数来保留之前的信息 terminal integrated bell scrollback
  • MMDet——EMA更新hook详解

    Hook 首先需要明白mmdet中hook机制 EMA就是建立在Hook机制上的 推荐一个Hook详解 深度理解目标检测 MMdetection HOOK机制 EMA 指数平均 exponential mean average 一般来说 在
  • 使用Google Guava Cache Util工具类实现本地缓存设置过期时间的Java应用

    使用Google Guava Cache Util工具类实现本地缓存设置过期时间的Java应用 随着互联网应用的发展 缓存成为提高系统性能和响应速度的关键技术之一 而在Java开发中 Google Guava提供了一个强大的缓存工具类 Ca
  • 关于数据库表字段的数据权限设计

    吐槽 刚在同事的帮忙下 把maven工程成功导入到eclipse 期间遇到的最大问题就是安装eclipse插件 花费了其中大部分的时间 现在做的研发产品 遇到的一个新的需求是 控制外部系统对于表中字段的访问权限 其实说白了 就是 对于CRU
  • sklearn机器学习包中的对原始数据的预处理及训练集、测试集的分割

    sklearn机器学习包中的对原始数据的预处理及训练集 测试集的分割 一 数据预处理 1 标准化 2 归一化 3 最小最大标准化 4 缺失值插补 二 训练集测试集的划分 一 数据预处理 sklearn preprocessing 包提供了几
  • 编码-整数

    计算机中存储的数值 正数为其原码 而负数存的是其补码 正数 原码 用最高位表示符号位 其余位表示数值 其中 正数的符号位为 0 负数的符号位为 1 正整数转成二进制 除二取余 直到商为零或一时为止 然后倒序排列 举个栗子 121 gt 0
  • 【蓝桥杯】什么算法才是版本答案?近三年(2019-2021)蓝桥杯省赛涉及算法出现频率分析

    2022年的蓝桥杯比赛已经基本报名结束 寒假来临 如何抓住重点 快速掌握各种算法知识 在4月份的蓝桥杯省赛中取得好成绩呢 本文收集了近三年的4场蓝桥杯省赛题目 2019年 2020年第二场 2020年第三场 2021年 并总结了题目涉及的算
  • python是一门机器语言_python是一门怎样的编程语言?

    大家应该都听说过python语言 也知道它是一门非常适合零基础学习的语言 但是对于没有接触过的人来说可能就疑惑python到底是一门什么样的编程语言 1 跨平台 跨平台不依赖操作系统和硬件环境 某个操作系统环境下开发的应用 放在其他的系统中
  • angular中的组件嵌套

    1 创建3个包 header module main module sliderbar module 2 在header module创建三个组件 header center heder left header right 3 z将三个组件
  • BP神经网络回归预测-MATLAB代码实现(代码完整直接可用,注释详细,可供新手学习)

    一 前言 代码获取 私信或附评论区 BP神经网络预测回归MATLAB代码 代码完整可用 复制后即可运行使用 操作简单 1 BP神经网络的知识想必不用再过多介绍 本篇文章从实际应用的角度 针对新手应用者 针对不需要过多了解BP 但是需使用MA
  • Java-主流框架—(4)SpringMVC

    1 SpringMVC概述 三层架构 表现层 负责数据展示 业务层 负责业务处理 数据层 负责数据操作 MVC Model View Controller 一种用于设计创建Web应用程序表现层的模式 Model 模型 数据模型 用于封装数据
  • javaScript基础面试题 --- JS作用域

    面试10家公司 得有8家会问到作用域的题 所以说JS的作用域一定要弄清楚 非常重要 1 除了函数之外 JS没有块级作用域 2 作用域链 内部可以访问外部的变量 但是外部不能访问内部变量 如果内部有 优先内部的 如果内部没有 就先查找外部的
  • 基于深度学习的恶意软件检测

    深度神经网络可以有效地挖掘原始数据中的潜在特征 而无需大量数据预处理和先验经验 神经网络在计算机视觉 语音识别和自然语言处理方面取得了一系列的成功 当然 成功的原因是多方面的 其中的一个因素就是神经网络具有从诸如像素或单个文本字符之类的原始
  • 【软件测试】自动化测试战零基础教程——Python自动化从入门到实战(九)

    整理不易 希望对各位学习软件测试能带来帮助 软件测试知识持续更新 第八章 自动化测试高级应用 第一节 自动发邮件功能 8 1 1 文件形式的邮件 8 1 2 HTML 形式的邮件 8 1 3 获取测试报告 8 1 4 整合自动发邮件功能 第
  • 数据结构中二叉树实现及部分操作

    谈二叉树之前 我们先来看看树的定义 树 由N N gt 0 个结点构成的集合 对N gt 1的树 1 有一个特殊的结点 称为根结点 根节点没有前驱结点 2 除根节点外 其余结点被分成M M gt 0 个互不相交的集合T1 T2 Tm 其中每