二叉树——初解

news2024/12/24 22:15:59

二叉树

    • 树的概念
    • 树的性质
  • 二叉树
    • 二叉树的概念
    • 二叉树的性质
    • 二叉树的实现方式
      • 数组构建
      • 左孩子右兄弟法构建
      • 指针构建

树的概念

在计算机科学中,树(Tree)是一种重要的非线性数据结构,它由若干节点(Node)组成,并且这些节点之间以(Edge)连接起来。树的节点可以有零个或多个子节点,但每个节点最多只能有一个父节点,其中一个节点被称为根节点(Root Node),没有父节点。除了根节点外,每个节点都有且仅有一个父节点,使得整个结构呈现出类似于自然界中树的形状,因此得名。

在这里插入图片描述

树的性质

树的基本概念包括

  • 节点(Node):树中的基本元素,每个节点包含一个数据元素和指向其子节点的指针。

  • 根节点(Root Node):树的顶端节点,它是树的起始点,没有父节点。

  • 父节点(Parent Node):除了根节点外,每个节点都有一个父节点。

  • 子节点(Child Node):一个节点的直接下级节点称为其子节点。

  • 叶子节点(Leaf Node):没有子节点的节点称为叶子节点或终端节点。

  • 路径(Path):从树中一个节点到另一个节点的序列,沿着边连接而形成。

  • 层级(Level):根节点的层级为0,其直接子节点的层级为1,依次类推。

  • 深度(Depth):节点所在的层数称为节点的深度,一般根节点的深度为0,依次递增。

  • 高度(Height):树中节点的最大深度称为树的高度。在一棵高度为h的树中,根节点的高度为h,叶子节点的高度为0。

  • 子树(Subtree):树中的任意节点及其所有子孙节点构成的树称为原树的子树。

  • 森林(Forest):由m(m>=0)棵互不相交的树组成的集合称为森林。

在这里插入图片描述

二叉树

二叉树的概念

二叉树是一种常见的树形数据结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树的结构使得它在计算机科学领域中被广泛应用,例如在搜索算法、排序算法、编译器设计和网络路由中。

二叉树的性质

以下是二叉树的一些重要概念和特性

  • 节点(Node):二叉树中的每个元素称为节点。每个节点包含一个数据元素和指向其左子节点和右子节点的指针。

  • 根节点(Root Node):二叉树的顶端节点称为根节点,它是树的起始点。

  • 叶子节点(Leaf Node):没有子节点的节点称为叶子节点,也叫终端节点。

  • 父节点(Parent Node)、子节点(Child Node):一个节点的直接上级节点称为其父节点,而直接下级节点称为其子节点。

  • 深度(Depth):节点所在的层数称为节点的深度,根节点的深度为0,依次递增。

  • 高度(Height):树中节点的最大深度称为树的高度。在一棵高度为h的二叉树中,叶子节点的高度为h,而根节点的高度为0。

  • 满二叉树(Full Binary Tree):除了叶子节点外,每个节点都有两个子节点的二叉树称为满二叉树。

  • 完全二叉树(Complete Binary Tree):除了最后一层,其他层的节点都是满的,并且最后一层的节点都集中在左边,这样的二叉树称为完全二叉树。

  • 二叉搜索树(Binary Search Tree,BST):一种特殊的二叉树,其中每个节点的值大于其左子树中的所有节点的值,小于其右子树中的所有节点的值。这个特性使得BST能够快速地进行查找、插入和删除操作。

  • 遍历(Traversal):遍历是指按照一定的顺序访问树中的每个节点。常见的二叉树遍历方式包括前序遍历(Pre-order)、中序遍历(In-order)、后序遍历(Post-order)和层序遍历(Level-order)。

一颗满二叉树,完全二叉树
在这里插入图片描述

二叉树的实现方式

数组构建

1,构建二叉树的一种简单方法是通过数组表示树的结构。数组中的每个元素代表二叉树中的一个节点,并且通过数组索引之间的关系来表示节点之间的父子关系

假设我们有一个数组 arr 表示二叉树的节点,其中根节点位于索引 0 处。对于任意索引 i,其左子节点位于 2*i + 1 处,右子节点位于 2*i + 2 处。如果某个子节点不存在,则其对应的位置为 nullptr 或特殊值。

下面是一个简单的示例代码,演示如何通过数组构建二叉树:

#include <iostream>
#include <vector>

struct TreeNode {
    int data;
    TreeNode* left;
    TreeNode* right;
    
    TreeNode(int val) : data(val), left(nullptr), right(nullptr) {}
};

class BinaryTree {
private:
    std::vector<TreeNode*> nodes;
    
public:
    BinaryTree() {}
    
    void buildTree(std::vector<int>& arr) {
        // 创建节点并将其放入节点数组中
        for (int val : arr) {
            nodes.push_back(new TreeNode(val));
        }
        
        // 构建树的结构
        for (int i = 0; i < arr.size(); ++i) {
            if (2 * i + 1 < arr.size()) {
                nodes[i]->left = nodes[2 * i + 1];
            }
            if (2 * i + 2 < arr.size()) {
                nodes[i]->right = nodes[2 * i + 2];
            }
        }
    }
    
    // 释放节点内存
    void clear() {
        for (TreeNode* node : nodes) {
            delete node;
        }
        nodes.clear();
    }
    
    // 前序遍历
    void preorder(TreeNode* node) {
        if (node == nullptr) return;
        std::cout << node->data << " ";
        preorder(node->left);
        preorder(node->right);
    }
};

int main() {
    std::vector<int> arr = {1, 2, 3, 4, 5, 6, 7};
    BinaryTree tree;
    tree.buildTree(arr);
    tree.preorder(arr[0]); // 以根节点开始前序遍历
    tree.clear(); // 清除内存
    return 0;
}

在这个示例中,我们首先创建了表示二叉树的数组 arr,然后通过 buildTree 函数构建了二叉树的结构,并且通过前序遍历验证了构建的结果。最后,在程序结束时调用 clear 函数释放了动态分配的内存。

左孩子右兄弟法构建

左孩子右兄弟法(Left Child Right Sibling representation)是一种二叉树的表示方法,它将二叉树表示为多叉树的形式,每个节点有两个指针,一个指向其左子节点,另一个指向其右兄弟节点。

类似如此:
在这里插入图片描述

下面是一个简单的示例代码,演示如何通过左孩子右兄弟法构建二叉树:

#include <iostream>

struct TreeNode {
    int data;
    TreeNode* leftChild;
    TreeNode* rightSibling;
    
    TreeNode(int val) : data(val), leftChild(nullptr), rightSibling(nullptr) {}
};

class BinaryTree {
private:
    TreeNode* root;
    
public:
    BinaryTree() : root(nullptr) {}
    
    // 插入节点
    void insert(int parentVal, int val) {
        if (root == nullptr) {
            root = new TreeNode(parentVal);
            root->leftChild = new TreeNode(val);
        } else {
            TreeNode* parent = findNode(root, parentVal);
            if (parent != nullptr) {
                if (parent->leftChild == nullptr) {
                    parent->leftChild = new TreeNode(val);
                } else {
                    TreeNode* sibling = parent->leftChild;
                    while (sibling->rightSibling != nullptr) {
                        sibling = sibling->rightSibling;
                    }
                    sibling->rightSibling = new TreeNode(val);
                }
            } else {
                std::cout << "Parent node not found." << std::endl;
            }
        }
    }
    
    // 查找节点
    TreeNode* findNode(TreeNode* node, int val) {
        if (node == nullptr) return nullptr;
        if (node->data == val) return node;
        
        // 递归在左子树中查找
        TreeNode* foundNode = findNode(node->leftChild, val);
        if (foundNode != nullptr) return foundNode;
        
        // 递归在右兄弟节点中查找
        return findNode(node->rightSibling, val);
    }
    
    // 前序遍历
    void preorder(TreeNode* node) {
        if (node == nullptr) return;
        std::cout << node->data << " ";
        preorder(node->leftChild);
        preorder(node->rightSibling);
    }
};

int main() {
    BinaryTree tree;
    
    // 构建二叉树
    tree.insert(1, 2);
    tree.insert(1, 3);
    tree.insert(1, 4);
    tree.insert(2, 5);
    tree.insert(2, 6);
    tree.insert(3, 7);
    
    // 前序遍历
    tree.preorder(tree.getRoot());
    
    return 0;
}

在这个示例中,我们定义了 TreeNode 结构体来表示树节点,其中包含一个指向左子节点的指针 leftChild 和一个指向右兄弟节点的指针 rightSibling。然后,通过 BinaryTree 类的 insert 方法插入节点,并且通过前序遍历验证了构建的结果。

指针构建

1,使用链表建立二叉树通常有两种常见的方式:链表嵌套节点或者使用指针连接节点。以下是两种方法的示例:

  • 方法一:链表嵌套节点
#include <iostream>

struct TreeNode {
    int data;
    TreeNode* left;
    TreeNode* right;

    TreeNode(int val) : data(val), left(nullptr), right(nullptr) {}
};

// 链表节点
struct ListNode {
    int data;
    ListNode* next;

    ListNode(int val) : data(val), next(nullptr) {}
};

class BinaryTree {
private:
    TreeNode* root;

    // 使用递归构建二叉树
    TreeNode* buildTree(ListNode*& head) {
        if (head == nullptr) return nullptr;

        // 从链表中取出节点值
        int val = head->data;
        head = head->next;

        // 构建当前节点
        TreeNode* node = new TreeNode(val);

        // 递归构建左子树和右子树
        node->left = buildTree(head);
        node->right = buildTree(head);

        return node;
    }

public:
    BinaryTree() : root(nullptr) {}

    // 从链表构建二叉树
    void buildFromList(ListNode* head) {
        root = buildTree(head);
    }

    // 前序遍历
    void preorder(TreeNode* node) {
        if (node == nullptr) return;
        std::cout << node->data << " ";
        preorder(node->left);
        preorder(node->right);
    }

    // 获取根节点
    TreeNode* getRoot() {
        return root;
    }
};

int main() {
    // 构建链表表示的二叉树
    ListNode* head = new ListNode(1);
    head->next = new ListNode(2);
    head->next->next = new ListNode(3);
    head->next->next->next = new ListNode(4);
    head->next->next->next->next = new ListNode(5);

    BinaryTree tree;
    tree.buildFromList(head);

    // 前序遍历
    tree.preorder(tree.getRoot());

    return 0;
}
  • 方法二:使用指针连接节点
#include <iostream>

struct TreeNode {
    int data;
    TreeNode* left;
    TreeNode* right;

    TreeNode(int val) : data(val), left(nullptr), right(nullptr) {}
};

class BinaryTree {
private:
    TreeNode* root;

    // 使用递归构建二叉树
    TreeNode* buildTree(int* arr, int& index, int size) {
        if (index >= size || arr[index] == -1) {
            index++;
            return nullptr;
        }

        // 构建当前节点
        TreeNode* node = new TreeNode(arr[index]);
        index++;

        // 构建左子树和右子树
        node->left = buildTree(arr, index, size);
        node->right = buildTree(arr, index, size);

        return node;
    }

public:
    BinaryTree() : root(nullptr) {}

    // 从数组构建二叉树
    void buildFromArray(int* arr, int size) {
        int index = 0;
        root = buildTree(arr, index, size);
    }

    // 前序遍历
    void preorder(TreeNode* node) {
        if (node == nullptr) return;
        std::cout << node->data << " ";
        preorder(node->left);
        preorder(node->right);
    }

    // 获取根节点
    TreeNode* getRoot() {
        return root;
    }
};

int main() {
    // 构建数组表示的二叉树
    int arr[] = {1, 2, 4, -1, -1, -1, 3, -1, -1};
    int size = sizeof(arr) / sizeof(arr[0]);

    BinaryTree tree;
    tree.buildFromArray(arr, size);

    // 前序遍历
    tree.preorder(tree.getRoot());

    return 0;
} 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iPhone15销量不佳,新产品没希望,苹果开始寻找库克接班人

美国媒体开始谈论谁将成为苹果的新CEO&#xff0c;这意味着苹果董事会开始为库克寻找接班人了&#xff0c;导致如此结果&#xff0c;可能在于iPhone15的表现实在太差了&#xff0c;而库克力推的vision Pro等新产品又没有为苹果打开局面所致。 如今的苹果倒是与1980年代乔布斯离…

壹资源知识付费系统源码-小程序端+pc端

最新整理优化&#xff0c;含微信小程序和pc网页。内置几款主题&#xff0c;并且可以自己更改主题样式&#xff0c;各区块颜色&#xff0c;文字按钮等。 适用于知识付费类资源类行业。如&#xff1a;项目类&#xff0c;小吃技术类&#xff0c;图书类&#xff0c;考研资料类&…

Nodejs 第七十一章(libuv)

libuv 在Node.js中&#xff0c;libuv是作为其事件循环和异步I/O的核心组件而存在的。Node.js是构建在libuv之上的&#xff0c;它利用libuv来处理底层的异步操作&#xff0c;如文件I/O、网络通信和定时器等。 libuv在Node.js中扮演了以下几个重要角色&#xff1a; 事件循环&a…

同为科技详解智能PDU所应用的通信协议与接口

现如今&#xff0c;信息服务、AI人工智能的飞速发展与增长&#xff0c;全球正经历信息数据的爆炸。不仅数据量以惊人的速度增长&#xff0c;而且全球社会各行业对数据的依赖的程度也在日益增加。这些趋势使数据中心在全球都享有关键基础架构的地位。假设某个数据中心发生严重的…

写一个类ChatGPT应用,前后端数据交互有哪几种

❝ 对世界的态度&#xff0c;本质都是对自己的态度 ❞ 大家好&#xff0c;我是「柒八九」。一个「专注于前端开发技术/Rust及AI应用知识分享」的Coder 前言 最近&#xff0c;公司有一个AI项目&#xff0c;要做一个文档问答的AI产品。前端部分呢&#xff0c;还是「友好借鉴」Cha…

经典神经网络(8)GAN、CGAN、DCGAN、LSGAN及其在MNIST数据集上的应用

经典神经网络(8)GAN、CGAN、DCGAN、LSGAN及其在MNIST数据集上的应用 1 GAN的简述及其在MNIST数据集上的应用 GAN模型主导了生成式建模的前一个时代&#xff0c;但由于训练过程中的不稳定性&#xff0c;对GAN进行扩展需要仔细调整网络结构和训练考虑&#xff0c;因此GANs虽然在…

【js逆向】易车网JS逆向案例实战手把手教学(附完整代码)

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

一表捋清网络安全等级保护测评要求

三级网络安全等级保护测评指标&#xff1a; 对于中小企事业单位来说&#xff0c;网络安全建设是一个复杂且投入较高的过程&#xff0c;因此他们更倾向于寻找一种“省心省力”的等保建设方案&#xff0c;以及一种能够持续有效且具有较高性价比的网络安全建设投入方式。 此时&…

合合信息:TextIn文档解析技术与高精度文本向量化模型再加速

文章目录 前言现有大模型文档解析问题表格无法解析无法按照阅读顺序解析文档编码错误 诉求文档解析技术技术难点技术架构关键技术回根溯源 文本向量化模型结语 前言 随着人工智能技术的持续演进&#xff0c;大语言模型在我们日常生活中正逐渐占据举足轻重的地位。大模型语言通…

通过java将数据导出为PDF,包扣合并单元格操作

最近项目中需要将查询出来的表格数据以PDF形式导出&#xff0c;并且表格的形式包含横向行与纵向列的单元格合并操作&#xff0c;导出的最终效果如图所示&#xff1a; 首先引入操作依赖 <!--导出pdf所需包--><dependency><groupId>com.itextpdf</groupId&…

Linux 生态与工具

各位大佬好 &#xff0c;这里是阿川的博客 &#xff0c; 祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 目录 Linux生态简介:Linux工具lrzsz&#xff…

适用于 Windows 8/10/11 的 10 大 PC 迁移工具:电脑克隆迁移软件

当您发现自己拥有一台新的 PC 或笔记本电脑时&#xff0c;PC 迁移变得至关重要。将数据从旧计算机传输到新计算机的过程似乎令人生畏&#xff0c;尤其是如果您是第一次这样做。迁移过程中数据丢失的潜在风险加剧了焦虑。为确保文件和系统设置的无缝无忧传输&#xff0c;使用专为…

探索设计模式的魅力:机器学习赋能,引领“去中心化”模式新纪元

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 探索设计模式的魅力&#xff1a;机器学习赋能&#xff0c;引领“去中心化”模式新纪元 ✨欢迎加入…

3月份太阳镜行业线上市场销售数据分析

在消费者行为方面&#xff0c;太阳镜不仅仅是视力保护工具&#xff0c;更逐渐成为一种时尚单品。随着人们对健康和美容重视程度的提高&#xff0c;太阳镜作为体现个人风格的单品&#xff0c;其市场需求得到了进一步的推动。此外&#xff0c;全球旅行和旅游业的恢复&#xff0c;…

被暗示离职?教你优雅反击

在职场中&#xff0c;面对公司暗示离职的情况&#xff0c;应届毕业生可能会感到困惑与无助。但是&#xff0c;保持冷静和据理力争是保护自己权益的重要途径。以下是如何应对此类情况的一些建议。 当你感觉到被暗示离职时&#xff0c;首要的策略就是与上司进行有效沟通。安排一个…

halo博客--解决恶意刷评论的问题

原文网址&#xff1a;halo博客--解决恶意刷评论的问题_IT利刃出鞘的博客-CSDN博客 简介 本文介绍halo博客如何通过设置评论次数来解决恶意刷评论的问题。 评论功能要设置频率的限制&#xff0c;否则可能被人一直刷评论&#xff0c;然后数据库存的垃圾评论越来越多&#xff0…

数据结构——队列(链表实现)

一、队列的特点 先进先出 二、队列的代码 typedef int QDataType;// 链式结构&#xff1a;表示队列 typedef struct QListNode {struct QListNode* next;QDataType data; }QNode;// 队列的结构 typedef struct Queue {QNode* front; //指向队列的第一个结点QNode* rear;//指…

刷代码随想录有感(66):回溯算法——组合问题的优化(剪枝)

代码&#xff1a;将for循环中i的搜索范围进行缩小&#xff0c;免去多余的不可能符合条件的操作。 for(int i start; i < n-(k-tmp.size())1;i) 实质是剪枝&#xff0c;拿n4,k4作比较&#xff1a; 显然结果只可能是[1,2,3,4]&#xff0c;选取顺序只可能是1-2-3-4&#xff…

Day27 代码随想录打卡|栈与队列篇---删除字符串中的所有相邻重复项

题目&#xff08;leecode T1047&#xff09;&#xff1a; 给出由小写字母组成的字符串 S&#xff0c;重复项删除操作会选择两个相邻且相同的字母&#xff0c;并删除它们。 在 S 上反复执行重复项删除操作&#xff0c;直到无法继续删除。 在完成所有重复项删除操作后返回最终…

霍廷格电源 Tru plasma DC3030 通快DC3040 MF3030

霍廷格电源 Tru plasma DC3030 通快DC3040 MF3030