C++数据结构之:树Tree

摘要:

   it人员无论是使用哪种高级语言开发东东,想要更高效有层次的开发程序的话都躲不开三件套:数据结构,算法和设计模式。数据结构是相互之间存在一种或多种特定关系的数据元素的集合,即带“结构”的数据元素的集合,“结构”就是指数据元素之间存在的关系,分为逻辑结构和存储结构。

   此系列专注讲解数据结构数组、链表、队列、栈、树、哈希表、图,通过介绍概念以及提及一些可能适用的场景,并以C++代码简易实现,多方面认识数据结构,最后为避免重复造轮子会浅提对应的STL容器。本文介绍的是树Tree。

(开发环境:VScode,C++17)

关键词C++数据结构Tree

声明:本文作者原创,转载请附上文章出处与本文链接。

(文章目录:)

文章目录

      • 摘要:
      • 正文:
        • 介绍:
          • 特性:
          • 应用:
        • 代码实现:
        • 对应STL:
      • 推荐阅读

正文:

介绍:

   数据结构中的“树”是一种非常重要的非线性数据结构,由n(n≥0)个有限节点组成一个具有层次关系的集合。之所以被称为“树”,是因为它看起来像一棵倒挂的树,即根朝上,叶朝下。树的结构特点是:

  • 每个节点有零个或多个子节点;
  • 没有父节点的节点称为根节点;
  • 每一个非根节点有且只有一个父节点;
  • 除了根节点外,每个子节点可以分为多个不相交的子树。

其中红黑树和二叉树最为常用。

在这里插入图片描述

树的术语:

  • 节点:树中的每个元素。
  • 根节点:没有父节点的节点。
  • 子节点:一个节点含有的子树的根节点。
  • 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。
  • 兄弟节点:具有相同父节点的节点。
  • 树的度:一棵树中,最大的节点的度。
  • 树的深度或高度:树中节点的最大层次。
  • 叶子节点或终端节点:度为0的节点。
  • 非叶子节点或分支节点:度不为0的节点。
  • 森林:由n棵互不相交的树的集合称为森林。
特性:
  • 层次结构:树具有清晰的层次结构,每个节点都位于一个特定的层次上。根节点位于第一层,其子节点位于第二层,依此类推。这种层次结构使得树能够表示具有层次关系的数据。

  • 无环性:在树中,从一个节点出发,沿着父节点-子节点的关系向下(或向上)遍历,不可能回到原点,除非整个遍历过程已经完成。换句话说,树中不存在环路或循环。

  • 根节点唯一:树中只有一个根节点,它是树的起点。所有的其他节点都直接或间接地连接到根节点。

  • 遍历性:树支持多种遍历方式,如先序遍历(根-左-右)、中序遍历(左-根-右)和后序遍历(左-右-根)。这些遍历方式对于实现各种算法和数据结构操作非常重要。

应用:
  • 利用树型结构可以求解集合的幂集问题,如集合{1,2,…,n}的幂集问题。
  • 在计算机科学中,树被广泛应用于各种算法和数据结构中,如二叉搜索树、AVL树、红黑树等,用于实现高效的查找、插入和删除操作。
  • 树被广泛应用于各种场景,如文件夹目录、公司组织关系、编译器设计、数据库索引、网络路由协议等。
代码实现:
#ctree.h
#ifndef CTREE_H
#define CTREE_H
#include<iostream>
using namespace std;

// 树节点
template<class T>
class TreeNode
{
public:
  int index;                    // 坐标
  T data;                       // 数据
  TreeNode<T> *pLChild;         // 左节点
  TreeNode<T> *pRChild;         // 右节点
  TreeNode<T> *pParent;         // 父节点

public:
    TreeNode():index(0),data('0'),pLChild(NULL),pRChild(NULL),pParent(NULL) {}
    TreeNode(int iNodeIndex, T val):index(iNodeIndex),data(val),pLChild(NULL),pRChild(NULL),pParent(NULL) {}
    ~TreeNode(){}

    TreeNode<T>* SearchNode(int iNodeIndex);    // 通过序号坐标搜寻对应的节点
    void DeleteNode();                          // 删除此树节点
    void PreOrderTraversal();                   // 先序遍历 根左右
    void InOrderTraversal();                    // 中序遍历 左根右
    void PostOrderTraversal();                  // 后序遍历 左右根

private:
    // 禁止拷贝、赋值
    TreeNode(const TreeNode<T>& node);
    TreeNode& operator=(const TreeNode<T>& node);
};

template <class T>
inline TreeNode<T>* TreeNode<T>::SearchNode(int iNodeIndex)
{
    if (index == iNodeIndex){
        return this;
    }
    TreeNode<T> *temp = NULL;
    if (pLChild){
        if (pLChild->index == iNodeIndex){
            return pLChild;
        }
        else{
            temp = pLChild->SearchNode(iNodeIndex);
            if (temp){
                return temp;
            }
        }
    }
    if (pRChild){
        if (pRChild->index == iNodeIndex){
            return pRChild;
        }
        else{
            temp = pRChild->SearchNode(iNodeIndex);
            if (temp){
                return temp;
            }
        }
    }
    return NULL;
}

template <class T>
inline void TreeNode<T>::DeleteNode()
{
    if (pLChild != NULL){
        pLChild->DeleteNode();
    }
    if (pRChild != NULL){
        pRChild->DeleteNode();
    }
    if (pParent != NULL){
        if (this == pParent->pLChild)
            pParent->pLChild = NULL;
        if (this == pParent->pRChild)
            pParent->pRChild = NULL;
    }
    delete this;
}

template <class T>
inline void TreeNode<T>::PreOrderTraversal()
{
    cout << data << " ";
    if (pLChild){
        pLChild->PreOrderTraversal();
    }
    if (pRChild){
        pRChild->PreOrderTraversal();
    }
}

template <class T>
inline void TreeNode<T>::InOrderTraversal()
{
    if (pLChild){
        pLChild->InOrderTraversal();
    }
    cout << data << " ";
    if (pRChild){
        pRChild->InOrderTraversal();
    }
}

template <class T>
void TreeNode<T>::PostOrderTraversal()
{
    if (pLChild){
        pLChild->PostOrderTraversal();
    }
    if (pRChild){
        pRChild->PostOrderTraversal();
    }
    cout << data << " ";
}


// 基于链表的二叉树基本实现和遍历
template <class T>
class CTree
{
public:
    CTree(TreeNode<T> *pNode = NULL);
    ~CTree(){ m_pRoot->DeleteNode(); }
    TreeNode<T>* SearchNode(int index){ return m_pRoot->SearchNode(index); }

    bool AddNode(int index, int direction, TreeNode<T> *pNode);
    bool DeleteNode(int index, TreeNode<T> *pNode);
    // 先序遍历-递归
    void Recursive_PreOrderTraversal(){ m_pRoot->PreOrderTraversal(); }
    // 中序遍历-递归
    void Recursive_InOrderTraversal(){ m_pRoot->InOrderTraversal(); }
    // 后序遍历-递归
    void Recursive_PostOrderTraversal(){ m_pRoot->PostOrderTraversal(); }
    // 先序遍历-非递归
    void PreOrderTraversal();
    //中序遍历-非递归
    void InOrderTraversal();
    //后序遍历-非递归
    void PostOrderTraversal();

private:
    TreeNode<T> *m_pRoot; //根节点
};

template <class T>
inline CTree<T>::CTree(TreeNode<T> *pNode)
{
    // 创建树默认先创建根节点
    m_pRoot = new TreeNode<T>();
    if (!m_pRoot){
      throw "根节点申请内存失败";
      return;
    }
    if (pNode){
        m_pRoot->index = 0;
        m_pRoot->data = pNode->data;
    }
    else{
        m_pRoot->index = 0;
        m_pRoot->data = '0';
    }
}

template <class T>
inline bool CTree<T>::AddNode(int index, int direction, TreeNode<T> *pNode)
{
    if (!pNode){
        cout << "插入失败!新增的节点值为空。" << endl;
        return false;
    }
    TreeNode<T> *temp = SearchNode(index);
    if (!temp){
        cout << pNode->data << "插入失败!找不到传入下标对应的父节点。" << endl;
        return false;
    }

    TreeNode<T> *node = new TreeNode<T>();
    if (!node){
        cout << pNode->data << "插入失败!新的节点申请内存失败。" << endl;
        return false;
    }

    node->index = pNode->index;
    node->data = pNode->data;
    node->pParent = temp;

    if (1 == direction){
        temp->pLChild = node;
    }
    else if (2 == direction){
        temp->pRChild = node;
    }
    else{
        cout << pNode->data << "插入失败!direction参数错误:1为左节点,2为右节点" << endl;
    }
    return true;
}

template <class T>
inline bool CTree<T>::DeleteNode(int index, TreeNode<T> *pNode)
{
    TreeNode<T> *temp = SearchNode(index);
    if (!temp){
        cout << "删除失败!找不到传入下标对应的节点。" << endl;
        return false;
    }
    if (pNode){
        pNode->index = temp->index;
        pNode->data = temp->data;
    }
    temp->DeleteNode();
    return true;
}

template <class T>
inline void CTree<T>::PreOrderTraversal()
{
    int stackTop = -1;
    TreeNode<T>* nodeStack[10];
    TreeNode<T>* pMove = m_pRoot;
    while (stackTop != -1 || pMove){
        while (pMove){
            cout << pMove->data << " ";
            nodeStack[++stackTop] = pMove;
            pMove = pMove->pLChild;
        }
        if (stackTop != -1){
            pMove = nodeStack[stackTop];
            stackTop--;
            pMove = pMove->pRChild;
        }
    }
}

template <class T>
inline void CTree<T>::InOrderTraversal()
{
    int stackTop = -1;
    TreeNode<T>* nodeStack[10];
    TreeNode<T>* pMove = m_pRoot;
    while (stackTop != -1 || pMove){
        while (pMove){
            nodeStack[++stackTop] = pMove;
            pMove = pMove->pLChild;
        }
        if (stackTop != -1){
            pMove = nodeStack[stackTop--];
            cout << pMove->data << " ";
            pMove = pMove->pRChild;
        }
    }
}

template <class T>
inline void CTree<T>::PostOrderTraversal()
{
    int stackTop = -1;
    TreeNode<T>* nodeStack[10];
    TreeNode<T>* pMove = m_pRoot;
    TreeNode<T>* pLastVisit = NULL;
    while (pMove){
        nodeStack[++stackTop] = pMove;
        pMove = pMove->pLChild;
    }
    while (stackTop != -1)
    {
        pMove = nodeStack[stackTop--];
        if (pMove->pRChild == NULL || pMove->pRChild == pLastVisit){
            cout << pMove->data << " ";
            pLastVisit = pMove;
        }
        else{
            nodeStack[++stackTop] = pMove;
            pMove = pMove->pRChild;
            while (pMove){
                nodeStack[++stackTop] = pMove;
                pMove = pMove->pLChild;
            }
        }
    }
}

#endif // !CTREE_H
#ctree.cpp
#include "ctree.h"
using namespace std;

int main(int argc, char**argv)
{
    TreeNode<char> nodeA(0, 'A');
    TreeNode<char> nodeB(1, 'B');
    TreeNode<char> nodeC(2, 'C');
    TreeNode<char> nodeD(3, 'D');
    TreeNode<char> nodeE(4, 'E');
    TreeNode<char> nodeF(6, 'F');
    TreeNode<char> nodeG(9, 'G');

    CTree<char> *tree = new CTree<char>(&nodeA);
    tree->AddNode(0, 1, &nodeB);
    tree->AddNode(0, 2, &nodeC);
    tree->AddNode(1, 1, &nodeD);
    tree->AddNode(1, 2, &nodeE);
    tree->AddNode(2, 2, &nodeF);
    tree->AddNode(4, 1, &nodeG);

    cout << "recursive traversal:" << endl;
    tree->Recursive_PreOrderTraversal();
    cout << endl;
    tree->Recursive_InOrderTraversal();
    cout << endl;
    tree->Recursive_PostOrderTraversal();
    cout << endl;

    cout << "non recursive traversal:" << endl;
    tree->PreOrderTraversal();
    cout << endl;
    tree->InOrderTraversal();
    cout << endl;
    tree->PostOrderTraversal();
    cout << endl;

    delete tree;

    return 0;
}

在这里插入图片描述

对应STL:

   set,multiset,map,multimap这四种容器的共同点是:底层使用了平衡搜索树(即红黑树),容器中的元素是一个有序的序列。

   网络上有对 map VS unordered_map 效率对比的测试,通常 map 增删元素的效率更高,unordered_map 访问元素的效率更高,另外,unordered_map 内存占用更高,因为底层的哈希表需要预分配足量的空间。其它 xxxunordered_xxx 区别也一样。

基于红黑树的集合

  • set:

    set 是一个关联型容器,它的底层结构是红黑树,set 是直接保存 value 的,或者说,set 中的 value 就是 key。set 中的元素必须是唯一的,不允许出现重复的元素,且元素不可更改,但可以自由插入或者删除。

  • multiset:

    multiset 和 set 底层都是红黑树,multiset 相比于 set 支持保存多个相同的元素;

基于红黑树的映射

  • map:

    map 是一个关联型容器,其元素类型是由 key 和 value 组成的 std::pair,实际上 map 中元素的数据类型正是 typedef pair<const Key, T> value_type;

  • multimap:

    multimap 和 map 底层都是红黑树,multimap 相比于 map 支持保存多个key相同的元素。

推荐阅读

C/C++专栏:https://blog.csdn.net/weixin_45068267/category_12268204.html
(内含其它数据结构及对应STL容器使用)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/678323.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Xcode中给UIView在xib中添加可视化的属性

给UIView在xib中添加可视化的属性 效果如下图: 可以直接设置view 的 borderColor 、borderWidth、cornerRadius,也可以单独指定view的某个角是圆角。减少了代码中的属性。 完整代码: UIView+Border.h #import <UIKit/UIKit.h>@interface UIView (Border)/// 可以…

软件设计师(中级)概要笔记:基于软件设计师教程(第5版)

文章目录 作者前言1、计算机系统知识1.1、计算机系统基础知识1.1.1 计算机系统硬件基本组成1.1.2 中央处理单元1.1.3、数据表示原码、反码、补码和移码&#xff08;符号数&#xff09;符号数的应用定点数和浮点数 1.1.4、校验码奇偶校验循环冗余校验码海明码 1.2、计算机体系…

基于梯度提升树回归模型的房地产价格估计

目录 1. 作者介绍2. 梯度提升树回归算法介绍2.1 算法原理2.2 算法讲解与分析 3. 实验过程3.1 数据集介绍3.2 代码介绍3.3 完整代码实现3.4 测试结果 参考文献 1. 作者介绍 雷强&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2023级研究生 研究方向&#xff…

个人笔记-随意记录

常见问题&#xff1f; 1.linux重启服务 端口被占用如何解决&#xff1f; 查看某个端口被占用的进程 netstat -tulnp | grep :23454 强制杀死进程 kill -9 1776 重启服务即可

JDK 22 新特性

JDK各个版本特性查看地址&#xff1a;https://openjdk.org/projects/jdk/17/&#xff08;修改后面数字即可&#xff0c;目前最新的是23&#xff09; JDK 22 于 2024 年 3 月 19 日全面发布。 一&#xff0c;开发计划 2023/12/07Rampdown Phase One (fork from main line) 第…

10款你一定不知道的实用工具!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/ 1. S激活工具——KMS激活工具 HEU_KMS_Activator&#xff0c;一款KMS激活工具&#xff0c;适用于Windows、Office及VL版本&#xff0c;无需联网…

MySql学习(一)——MySQL概述之MySQL的启动

文章目录 一、MySQl概述1.1 启动MySQL1.2 客户端连接1.3 关系型数据库1.4 总结 一、MySQl概述 数据库&#xff1a;存储数据的仓库&#xff0c;数据是有组织的进行存储&#xff0c;简称为&#xff08;DB&#xff09;数据库管理系统&#xff1a;操纵和管理数据库的大型软件&…

模拟散列表-java

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 一、模拟散列表 二、算法思路 1.散列表 2.拉链法 3.开放寻址法 三、代码如下 1.拉链法代码如下&#xff1a; 2.开放寻址法代码如下&#xff1a; 3.读入数据 3.代码运行结…

scipy.io.loadmat加载.mat文件,出现KeyError: ‘xxx‘

源代码&#xff1a; input_image loadmat(rC:\Users\admin\Downloads\Indian_Pines\SVM/aa.mat)[aa] #影像图 错误显示&#xff1a; 解决方法&#xff1a; 因为loadmat函数读取出来的高光谱数据是dict格式的所以需要定位才能进行后续操作&#xff0c;定位通常是通过列名&a…

GraphQL(4):GraphQL clients访问接口

下面演示在GraphQL clients访问GraphQL 接口 1 修改baseType.js 添加可供用户访问的静态资源路径 代码如下&#xff1a; const express require(express); const {buildSchema} require(graphql); const grapqlHTTP require(express-graphql).graphqlHTTP; // 定义schema…

深度学习500问——Chapter10:强化学习(1)

文章目录 10.1 强化学习的主要特点 10.1.1 定义 10.2 强化学习应用实例 10.3 强化学习和监督式学习、非监督式学习的区别 10.3.1 强化学习和监督式学习的区别 10.3.2 强化学习和非监督式学习的区别 10.1 强化学习的主要特点 其他许多机器学习算法中学习器都是学得怎样做&#…

0基础学习区块链技术——推演猜想

在《0基础学习区块链技术——入门》一文中&#xff0c;我们结合可视化工具&#xff0c;直观地感受了下区块的结构&#xff0c;以及链式的前后关系。 本文我们将抛弃之前的知识&#xff0c;从0开始思考和推演&#xff0c;区块链技术可能是如何构思出来的。 去中心 在一般的思维…

SpringSecurity6从入门到实战之SpringSecurity整合自动装配详解(源码级讲解,耐心看完)

SpringSecurity6从入门到实战之SpringSecurity整合自动装配详解 这里我先引出问题然后再来一步步进行剖析,SpringSecurity到底是如何实现引入依赖后所有请求都需要进行认证并且会弹出login登录表单页面. 接下来会对SpringBoot的自动装配进行详解,SpringSecurity也是通过自动装配…

【渗透测试】DC-1靶机实战(上)漏洞扫描获取反弹shell

目录 一、范围界定 二、信息收集 三、目标识别 1&#xff09;主机发现 2&#xff09;端口扫描 四. 服务枚举 1&#xff09;网站首页 2&#xff09;Web指纹识别 3&#xff09;nikto报告 4&#xff09;robots.txt 5&#xff09;UPGRADE.txt 五. 漏洞映射 1&#xff…

【项目管理常见问题大揭秘】每个管理者都要Get的「五维思维」~

走上管理岗☸要懂得五维思维 &#x1f4bc;自我管理——做自己的CEO 严于律己&#xff1a;严格要求自己&#xff0c;注重个人品牌建设 宽以待人&#xff1a;接纳不同观点&#xff0c;提升团队凝聚力 尊重事实&#xff1a;鼓励团队成员发挥优势&#xff0c;避免负面评价 坚守诚…

Mysql基础教程(15):别名

MySQL 别名 在本文中&#xff0c;我们讨论了 MySQL 中的列别名&#xff0c;表别名和派生表别名&#xff0c;以及使用别名来简化 SQL 和提高 SQL 的可读性。 如果在一个 SQL 中涉及到多个表&#xff0c;我们需要使用 table_name.column_name 这样的方式来引用每个表的字段&…

《科技和产业》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《科技和产业》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的第一批认定学术期刊 问&#xff1a;《科技和产业》是什么级别的&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a;中国科学技术协会 主办单位&…

猫毛过敏的克星!宠物空气净化器,铲屎官的终极武器~

现在很多人都喜欢养猫&#xff0c;但约有10%的人会对猫咪产生过敏反应。常见的症状包括打喷嚏、流鼻涕&#xff0c;严重时甚至会呼吸困难。 过敏源依附在宠物的毛发和皮屑上&#xff0c;通过空气传播&#xff0c;遍布家中的各个角落&#xff0c;如地面、衣物和家具。这不仅增加…

Jenkins+Rancher2.7部署构建

在Jenkins中使用rancher插件时需要去查找工作负载地址 在Rancher2.7没有查看Api按钮了需要自己去查找 1.进入https://192.168.x.xx:6443/v3/projects/ 2.输入在rancher中要查找的的项目名称并点击deployment连接进入下一个页面 3.找到自己的deployment随便点一个进去 4.浏览…

【数据结构】树与二叉树——二叉树的概念

二叉树的概念 导读一、二叉树的定义及其主要特性1.1 二叉树的定义1.2 二叉树的主要特性 二、特殊的二叉树2.1 满二叉树2.2 完全二叉树2.3 二叉排序树2.4 平衡二叉树 三、二叉树的性质3.1 性质一3.2 性质二3.3 性质三3.4 性质四3.5 性质五 结语 导读 大家好&#xff0c;很高兴又…