二叉树的基本性质及使用实现

article/2025/8/26 20:47:47



1、前言




在现实生活中,大部分事物之间的关系都是非常复杂的,单从事物联系的数量来说,有的是一对一的关系,有的是一对多的关系,有的是多对多的关系。这就诞生了除了线性结构以外,还包含了树结构和图结构。树结构通常来免回一对多的关系,图结构则总是用在多对多的关系。前者比如族谱,后者比如交通图。因此,对于它们的了解,可以加强我们对现实生活的事物之间的抽象理解,这样才可以开发处符合现实生活的数据结构。本文主要讲解树结构 。




2、树结构的概念





接下来通过二叉树来了解树结构,因为二叉树是一个典型的例子,通过它就可以推广到整个树结构。

二叉树是有限个数据元素的集合,这个集合或者为空,或者只有一个根节点,又或者含有左子树和右子树的二叉树。当集合为空时,则称为这个二叉树为空二叉树。二叉树的每一个元素又称为节点。




3、树结构相关专业术语




节点的度:节点的分支数叫做这个节点的度。

叶节点:节点的分支数为0的节点称为叶节点。既不含有子元素。

分支节点:节点的分支数不为0的节点称为分支节点。即含有子元素。

左孩子,右孩子,双亲,兄弟:如果一个节点拥有子元素,那么它的左边的子元素称为左孩子,右边的子元素称为右孩子。对于孩子节点来说,这个节点就是双亲节点。而左孩子与右孩子互相又称为兄弟节点。


路径,路径长度:如果一棵树中的含有一串节点,节点n 1,n2......nk,其中ni是ni-1的父元素,则称这段节点为n1到nk的路径。这段路径的长度为k-1。


祖先,子孙:如果存在一条从ni到nk的路径,则称ni是nk的在子孙。

节点的层数:规定根节点的层数为1,其余节点为其父节点的层数+1。

满二叉树:如果一棵树的每一层的节点都达到了最大值(2的(n-1)次方),则称这棵树为满二叉树。满二叉树的特点除了叶子结点外,所有的分支节点都含有左子树和右子树。

完全二叉树:假设根节点的标号是1,其余节点均按次序从上到下,从左到右编号,每个编号均和满二叉树相同,则称这棵二叉树为完全二叉树。完全二叉树不一定是满二叉树,满二叉树一定是完全二叉树。因为完全二叉树的最后一层的节点可以不达到该层的最大节点数。



4、二叉树的主要性质以及证明



性质1  一棵非空二叉树第i层上最多有2的(i-1)次方个节点(i>=1)

思路:这种证明题,通常都是使用数学归纳法证明的。

证明:





性质2:一棵深度额为k的二叉树,最多具有2的k次方-1个节点。

证明思路:证明每一层的节点数相加不超过2的k次方-1即可。

证明:


性质3:对于一颗非空的二叉树,若叶子结点数为n0,度数为2的节点数为n2,则有n0=n2+1

证明思路:要证明度数为0的节点和度数为2的节点的关系,我们需要为他们之间建立关系式。首先可以从节点总数出发,即n=n0+n1+n2,接着我们需要借助其他证明把n1和n0以及n2的关系求出来,既可以得到n0和n2的关系。

证明:




性质4:由于涉及到对数,不好写直接上图。





5、二叉树的基本运算


1、initial,建立一棵空的二叉树。建议的做法是用一个头节点指向根节点。

2、create,创建二叉树的根节点。

3、insertL,给根节点插入左孩子节点,如果根节点本身含有左孩子节点,则将原来的左孩子节点作为当前结点的左孩子节点,当前结点则作为根节点的左孩子节点。

4、insertR,给根结点插入右孩子节点,如果根节点本身含有右孩子节点,则将原来的右孩子节点作为当前结点的右孩子节点,当前结点则作为根节点的右孩子节点。

5、deleteL,在二叉树中删除某个节点的左子树。

6、deleteR,在二叉树中删除某个节点的右子树。

7、search,在二叉树中查找某个值得节点元素。

8、traverse,遍历二叉树的所有节点。常用的遍历方式有三种,前序遍历,中序遍历,后序遍历。后面会展开讲解。


6、二叉树的存储方式


基本运算是数据结构的逻辑实现,具体的实现必须确认了存储方式才能实现,所以现在必须先了解二叉树可用的存储方式。


6.1、顺序存储结构


顺序存储结构在实现上就是利用数组来存储一组相邻的元素。在二叉树中,分为满二叉树,完全二叉树以及一般的二叉树。事实上,如果用数组来存储二叉树的元素,这里按照从上到下,从左到右的次序存储二叉树中的元素。考虑到相邻元素之间必须在二叉树里面也是相邻的,也只有满二叉树,完全二叉树可以毫无缝隙的对接树的相邻元素和数组的相邻元素恰好是相邻关系。而一般的二叉树,如果也希望用数组存储,并且数组的相邻元素在二叉树上也是相邻元素,只能对树结构进行完全化后再进行存储。

完全化:即对于树中的元素,按照从上到下,从左到右的次序存储,一旦遇到空的元素,就用NULL补齐,直到最后一个元素被存储。这样,如果相邻的元素是NULL,说明该元素并没有在树结构上相邻的元素。因为看上去用NULL补齐的树,就像一棵完全二叉树,所以称为完全化。

综上所述,如果二叉树的结构是满二叉树或者完全二叉树,那么可以考虑使用数组存储。但如果是一般的二叉树则不建议,因为完全化后的二叉树,空元素也需要占据空间,但实际上,这些元素并没有实际含义,所以会导致大量的空间浪费。

无论什么结构的二叉树,都建议使用链式存储结构。


6.2、链式存储结构


链式存储结构,即用链表的方式存储二叉树的结构,在此又分为二叉链表存储和三叉链表存储,它们的结构如下:

二叉链表:




三叉链表:





二叉链表存储,包含三个信息,左孩子节点指针,右孩子节点指针,以及数据域。如果没有孩子节点,则将指针域置为NULL。使用二叉链表结构,一般会创建一个头节点,并将其中的左子树指向根节点,右子树为NULL,这样方便了后续的操作。

三叉链表存储,除了含有二叉链表的结构外,还含有一个双亲节点指针域,指向它的双亲节点。这是典型的空间换时间的方法,如果涉及的操作需要频繁访问双亲节点,就建议使用三叉链表的存储结构。

对于一般的二叉树而言,二叉链表存储是最简单有效的方式,也是推荐使用的。


7、二叉链表的基本实现


在给出代码之前,先讲述二叉树的遍历方法。

先序遍历是先访问父元素在访问左孩子最后是右孩子。

中序遍历是先访问左孩子再访问父节点最后是右孩子。

后序遍历是先访问左孩子在访问右孩子最后访问父节点。

看了很多解释遍历的解说,都是特别抽象难懂的,笔者的建议是,结合代码以及提到的遍历的方法,一步一步调试,才会比较容易的去理解。



代码如下:

Node.h

#include<string>class Node
{
public:Node();~Node();std::string data;Node *lChild;Node *rChild;private:};Node::Node()
{
}Node::~Node()
{
}

BinaryTree.h

#include "Node.h"
#include <iostream>
#include<stack>/*
初始化二叉树的表头
*/
Node *initial()
{Node *header = new Node;header->lChild = nullptr;header->rChild = nullptr;header->data = " ";return header;
}/*
创建根节点
*/
void createRoot(std::string data, Node *header)
{Node *root = new Node();root->data = data;root->lChild = nullptr;root->rChild = nullptr;header->lChild = root;//将树的根节点保存在头节点的左节点。以后的操作只要正对根节点就行了
}/*
给根结点添加左孩子节点,如果根节点本身就存在左孩子节点就将当前结点作为根节点的左孩子,之前的左孩子节点作为新的节点的左孩子节点
*/void insertL(std::string data, Node *header)
{Node *root = header->lChild;Node *lChild = new Node();lChild->data = data;lChild->rChild = nullptr;//新添加的节点应该是不含孩子节点的,但如果根节点本身就有左孩子节点,则此节点有左孩子节点if (root->lChild != nullptr)lChild->lChild = root->lChild;elselChild->lChild = nullptr;root->lChild = lChild;
}/*
给根结点添加右孩子节点,如果根节点本身就存在右孩子节点就将当前结点作为根节点的右孩子,之前的右孩子节点作为新的节点的右孩子节点
*/void insertR(std::string data, Node *header)
{Node *root = header->lChild;Node *rChild = new Node();rChild->data = data;rChild->lChild = nullptr;//新添加的节点应该是不含孩子节点的,但如果根节点本身就有右孩子节点,则此节点有右孩子节点if (root->rChild != nullptr)rChild->rChild = root->rChild;elserChild->rChild = nullptr;root->rChild = rChild;
}/*
删除某个节点的左子树
*/
void deleteL(Node *parent, Node *header)
{if (parent == nullptr || parent->lChild == nullptr)return;else{Node *lChild = parent->lChild;parent->lChild = nullptr;lChild = nullptr;free(lChild);}
}/*
删除某个节点的右孩子树
*/
void deleteR(Node *parent, Node *header)
{if (parent == nullptr || parent->rChild == nullptr)return;else{Node *rChild = parent->rChild;parent->rChild = nullptr;rChild = nullptr;free(rChild);}
}/*
查找某个数据的节点元素
*/Node *search(std::string data, Node *header)
{Node *root = header->lChild;Node *item = header->lChild;std::stack<Node *> nodeStack;nodeStack.push(item);//先序遍历查找节点	while (item != nullptr || nodeStack.size() != 0){while (item != nullptr){if (item->data == data)return item;if (item != header->lChild)nodeStack.push(item);item = item->lChild;}if (nodeStack.size() == 0)return nullptr;item = nodeStack.top();nodeStack.pop();item = item->rChild;}return nullptr;}/*
先序遍历二叉树
*/
void preTraveler(Node *header)
{Node *item = header->lChild;//需要用一个栈来保存所有访问过的元素,这样才能访问当前元素的父元素std::stack<Node *> nodeStack;nodeStack.push(item);//只有当栈没有元素了,item也是null的时候,才说明二叉树访问完毕while (item != nullptr || nodeStack.size() != 0){while (item != nullptr){//将左子树入栈std::cout << item->data << " ";if (item != header->lChild)nodeStack.push(item);item = item->lChild;}//如果栈中没有元素,说明所有的元素都访问过了。if (nodeStack.size() == 0)return;//弹出栈顶元素item = nodeStack.top();nodeStack.pop();item = item->rChild;}}/*
中序遍历二叉树
和先序遍历不同的地方在于,访问节点数据的位置不同。
先序遍历是先访问节点在入栈,中序遍历则是先入栈在访问节点
*/
void midTraveler(Node *header)
{Node *item = header->lChild;std::stack<Node *> nodeStack;nodeStack.push(item);while (item != nullptr || nodeStack.size() != 0){while (item != nullptr){if (item != header->lChild)nodeStack.push(item);item = item->lChild;}if (nodeStack.size() == 0)return;item = nodeStack.top();std::cout << item->data << " ";nodeStack.pop();item = item->rChild;}}


Main.cpp

#include"BinaryTree.h"
#include<sstream>//fstreamint main()
{Node *header = initial();createRoot("root", header);//创建根节点//将偶数插入左子树,奇数插入右子树。//由于每次都插入到根节点的字数,导致数的结构是左子树只有左孩子,右子树只有右孩子。//其实insertL可以传递的header可以改成需要作为父元素的节点,这样就可避免除出现签名的情况std::stringstream inS;for (int i = 1; i <= 100; i++){inS <<std::unitbuf<< i<<std::nounitbuf;if (i % 2 == 0)insertL(inS.str(), header);elseinsertR(inS.str(), header);inS.str("");//必须情况内容,否则的话,会把前面的内容拼合起来}//先序遍历preTraveler(header);std::cout << std::endl;//中序遍历midTraveler(header);return 0;
}

笔者只是实现了先序遍历和中序遍历,后续遍历的流程比较复杂一点,目前暂时没有思路。有兴趣的读者可以自己去研究。




---------文章写自:HyHarden---------

--------博客地址:http://blog.csdn.net/qq_25722767-----------





http://chatgpt.dhexx.cn/article/kDjhr1W5.shtml

相关文章

二叉树的定义及性质

在讨论一般树的存储结构及其操作之前&#xff0c;我们首先研究一种简单而极其重要的树结构 —— 二叉树。 二叉树的定义 二叉树&#xff08;Binary Tree&#xff09; 是另外一种树型结构&#xff0c;它的特点是每个节点至多只有两棵子树&#xff08;即二叉树中不存在度大于2的…

二叉树的基本概念和性质

目录 一、树的概念和结构 1.1 树的概念 1.2 树的重要概念 1.3 树的表示​​​​​​​ 二、 二叉树概念及结构 2.1 二叉树的概念 2.2 特殊的二叉树 2.3 二叉树的性质 2.4 二叉树练习题 一、树的概念和结构 1.1 树的概念 树是一种非线性的数据结构&#xff0c;他是由n…

二叉树的基本概念和特点

首先&#xff0c;什么是二叉树&#xff1f; 二叉树&#xff08;Binary Tree&#xff09;是n &#xff08;n>0&#xff09;个节点的有限集合&#xff0c;该集合可以为空集&#xff08;称为空二叉树&#xff09;&#xff0c;或者由一个根节点和两个互不相交的&#xff0c;分别…

树和二叉树的基本性质及其推导

文章目录 1 树的基本性质性质 1 &#xff1a;树中的节点数等于所有节点度数加 1性质 2 &#xff1a;度为 m 的树中第 i 层至多有 m i − 1 m^{i-1} mi−1个节点&#xff08; i ≥ 1 i\geq 1 i≥1)性质 3&#xff1a; 高度为 h 的 m 叉树至多有 m h − 1 m − 1 \frac{m^{h}-1…

【数据结构】二叉树的概念 | 满二叉树和完全二叉树 | 二叉树的基本性质

前言&#xff1a; 在上一章中我们正式开启了对数据结构中树的讲解&#xff0c;介绍了树的基础。本章我们将学习二叉树的概念&#xff0c;介绍满二叉树和完全二叉树的定义&#xff0c;并对二叉树的基本性质进行一个简单的介绍。本章附带课后练习。 &#x1f517; 链接&#xf…

树与二叉树基本概念与性质

树的基本概念 基本概念 树的度—— 一棵树中最大的结点度数 双亲—— 孩子结点的上层结点叫该结点的双亲 兄弟—— 同一双亲的孩子之间互成为兄弟 祖先—— 结点的祖先是从根到该结点所经分支上的所有结点 子孙—— 以某结点为根的子树中的任一结点都成为该结点的子孙 结…

【数据结构】 树与二叉树的基本概念、结构特点及性质

前言&#xff1a;本章内容主要是数据结构中树与二叉树的基本概念、结构特点及性质的引入。 文章目录 树的概念树的特点&#xff1a;树的常用术语&#xff1a;树的表示&#xff1a;代码创建&#xff1a; 树在实际中的应用&#xff1a; 二叉树的概念特殊的二叉树满二叉树完全二叉…

二叉树的基本性质

一、二叉树的定义&#xff1a; 二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树”&#xff08;right subtree&#xff09;。二叉树常被用于实现二叉查找树和二叉堆。 如图所示为一颗二叉树&#xff1a; ​ 二、二叉树的基本性质&#xff1a; 1、…

B+树叶子结点到底存储了什么?

首先MYSQL默认InnoDB引擎&#xff0c;该引擎默认B树&#xff1b;先说结论&#xff1a;B树叶子结点存储的是主键KEY或者具体数据。分情况讨论&#xff1a; 主键KEY 比如说user_name是个索引&#xff0c;当执行该SQL&#xff1a;select * from user_info where user_name xiao…

二叉树叶子结点,非叶子节点以及深度的计算

二叉树叶子结点的计算 //统计叶子结点的数目 int LeafNum(BiTree T) {if (!T) {return 0;} else if (!T->lchild && !T->rchild) {return 1;} else {return LeafNum(T->lchild) LeafNum(T->rchild);} }二叉树非叶子节点的计算 //统计非叶子结点的数目 i…

叶子结点

叶子结点 叶子结点是离散数学当中的概念。一棵树当中没有子结点&#xff08;即度为0&#xff09;的结点&#xff0c;称为叶子结点&#xff0c;简称“叶子”。 叶子是指度为0的结点&#xff0c;又称为终端结点。 叶子结点 就是度为0的结点 就是没有子结点的结点 n0&#xff1a;…

Pytorch 叶子张量 leaf tensor (叶子节点) (detach)

在Pytorch中&#xff0c;默认情况下&#xff0c;非叶节点的梯度值在反向传播过程中使用完后就会被清除&#xff0c;不会被保留。只有叶节点的梯度值能够被保留下来。 对于任意一个张量来说&#xff0c;我们可以用 tensor.is_leaf 来判断它是否是叶子张量&#xff08;leaf tenso…

Pytorch学习笔记(一)——自动求导和叶子节点

一、什么是叶子节点 PyTorch中的张量tensor有一个属性是is_leaf&#xff0c;当is_leaf为True时&#xff0c;改tensor是叶子张量&#xff0c;也叫叶子节点。 二、叶子节点的作用 PyTorch有自动求导的功能&#xff0c; 当requires_gradTrue时&#xff0c;PyTorch会自动记录运算…

数据结构-树:根节点、子节点、叶子节点是什么?

前言&#xff1a;这个属于数据结构&#xff1a;树。 下面给个例子图解释&#xff08;根节点、子节点、叶子节点&#xff09;。 上图数字 1、3、7是叶子节点&#xff1b;&#xff08;因为他们下面没有分叉出子节点&#xff0c;所以称为&#xff1a;叶子节点&#xff09;【度为0】…

数据结构 - 树

树 &#xff08;1&#xff09;相关概念 兄弟节点&#xff1a;节点的父节点是同一个节点&#xff0c;所以它们之间互称为兄弟节点。 根节点&#xff1a;没有父节点的节点叫作根节点 叶子节点&#xff1a;没有子节点的节点叫作叶子节点或者叶节点。 节点的高度&#xff1a;节…

根节点、子节点、叶子节点是什么?

前言&#xff1a;这个属于数据结构&#xff1a;树。 下面给个例子图解释&#xff08;根节点、子节点、叶子节点&#xff09;。 上图数字 1、3、7是叶子节点&#xff1b;&#xff08;因为他们下面没有分叉出子节点&#xff0c;所以称为&#xff1a;叶子节点&#xff09;【度为0】…

弱网、2G、3G、4G测试

1.各项指标 教程指引&#xff1a;弱网测试教程 2.概念介绍 Bandwidth&#xff08;带宽&#xff09;、Utilistation&#xff08;利用百分比&#xff09;、Round-trip&#xff08;往返延迟&#xff09;、MTU&#xff08;最大传输单元&#xff09; 3G&#xff1a;300k-2Mbps左…

简单实用Chrome 日常开发功能详解,帮助你上班摸鱼

chrome是目前开发过程中一骑绝尘的浏览器&#xff0c;占有绝对领导地位。其强大的功能和生态圈&#xff0c;让很多开发者爱不释手。但很多的开发者使用chrome还是停留在F12打开控制台查看log、检查元素或者debug打断点阶段&#xff0c;其实chrome的强大的功能远远超过我们的想象…

小米路由器3G如何解决USB3.0 5G WiFi速度慢的问题

经常玩电脑&#xff0c;希望家里有个轻 nas&#xff0c;小米路由器是一个不错的选择&#xff0c;tbw买了一个小米路由器3G看重的是快速的速度&#xff08;usb3.0 5G Wifi&#xff09;&#xff0c;及小米的可拓展性&#xff0c;使用usb3.0的usb接口&#xff0c;且使用5gb网速&am…