最全【数据结构】二叉树详解(上篇)(1),2024年最新快速上手

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

{
struct Node* _firstChild1; // 第一个孩子结点
struct Node* _pNextBrother; // 指向其下一个兄弟结点
DataType _data; // 结点中的数据域
};


![在这里插入图片描述](https://img-blog.csdnimg.cn/c46792154c1b4ec391ebca6405b8e360.png#pic_center)


## 二、二叉树


### 1.二叉树的定义及主要性质


#### 1.1二叉树的定义


  二叉树是一种特殊的树,其特点就是每个结点最多有两棵子树(即二叉树中不存在度大于2的结点),二叉树是 n (n>=0)个结点的有限集合:  
   ①:或者为空二叉树,即 n = 0;  
   ②:或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成,左子树和右子树有分别是一棵二叉树。  
   二叉树是有序树,若将其左右子树颠倒,则成为另一棵不同的二叉树。即树中结点只有一棵子树,也要区分它是左子树还是右子树。对于任意的二叉树都是由以下几种情况复合而成的:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/5205e638388f428fa298e9fe9e938703.png#pic_center)  
   二叉树与度为 2 的有序树的区别:  
   ①:度为 2 的树至少有 3 个结点,而二叉树可以为空;  
   ②:度为 2 的有序树的孩子的左右次序是相对于另一个孩子而言的,若某个结点只有一个孩子,则这个孩子就无需区分其左右次序,而二叉树无论孩子数是否为 2 ,均需要确定其左右次序,即二叉树的结点次序不是相对于另一个结点而言的,而是确定的。


#### 1.2几个特殊的二叉树


  1)满二叉树:一个二叉树如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 (2^k) - 1 ,则它就是满二叉树。


  2)完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树(也就是说,前 k-1 层是满的,第 k 层从左到右的叶子结点是连续的)。 要注意的是满二叉树是一种特殊的完全二叉树。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/bd98b1a5d91a45c993187e1da7a28738.png#pic_center)


#### 1.3二叉树的性质


1. 若规定根节点的层数为1,则一棵非空二叉树的第 k 层上最多有 2^(k-1)个结点;
2. 若规定根节点的层数为1,则深度为 h 的二叉树的最大结点数是 (2^h)-1;
3. 对任何一棵二叉树, 如果度为 0 其叶结点个数为 n0 , 度为 2 的分支结点个数为 n2,则有 n0= n2+1;



> 
> 证明:设度为0,1和2的结点个数分别为n0,n1和n2,结点总数n=n0+n1+n2。再看二叉树中的分支数,除根结点外,其余结点都有一个分支进入,设B为分支总数,则n=B+1。由于这些分支是由度为1或2的结点射出的,所以又有B=n1+2n2。于是得n0+n1+n2=n1+2n2+1,则n0=n2+1。
> 
> 
> 


4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= log2(n+1);
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:  
   ①:若 i > 0,i 位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点;  
   ②:若 2i+1 < n,左孩子序号:2i+1;若 2i+1>=n否则无左孩子;  
   ③:若 2i+2 < n,右孩子序号:2i+2;若 2i+2>=n否则无右孩子。


### 2.二叉树的存储结构


#### 2.1顺序存储结构


  二叉树的顺序存储是指用一组地址连续的存储单元依次自上而下、自左而右存储二叉树上的结点。根据二叉树的性质,完全二叉树和满二叉树采用顺序存储比较合适,树中结点的序号可以唯一的反映结点之间的逻辑关系,这样既能最大可能节省存储空间,又能利用数组元素的下标值确定结点在二叉树的位置,以及结点之间的关系。但对于一般的二叉树,为了让数组下标能反映出二叉树中结点之间的逻辑关系,只能添加一些并不存在的空结点,让其每个结点与完全二叉树上的结点相对照,再存储到一维数组的相应分量中。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/aec34d87774844e689932c21be549af9.png#pic_center)


#### 2.2链式存储结构


  因为顺序存储的空间利用率较低,因此二叉树一般采用链式存储结构,二叉树的链式存储结构是指用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。


![在这里插入图片描述](https://img-blog.csdnimg.cn/09fe2324d64242e0b56f86960da34ad0.png#pic_center)



typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
struct BinTreeNode* lchild; // 指向当前节点左孩子
struct BinTreeNode* rchild; // 指向当前节点右孩子
BTDataType data; // 当前节点值域
};

// 三叉链
struct BinaryTreeNode
{
struct BinTreeNode* Parent; // 指向当前节点的双亲
struct BinTreeNode* _lchild; // 指向当前节点左孩子
struct BinTreeNode* _rchild; // 指向当前节点右孩子
BTDataType data; // 当前节点值域
};


## 三、二叉树的顺序存储实现


### 1.堆的概念及结构


  普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储。  
   对于一个连续数组,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足所有的父亲结点都大于(小于)它的孩子结点,则称为大堆(或小堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。



> 
> 堆的性质:  
>  堆中某个节点的值总是不大于或不小于其父节点的值;  
>  堆总是一棵完全二叉树。
> 
> 
> 


### 2. 堆的实现


#### 2.1 堆向下调整算法


  假若现在给你一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。**向下调整算法有一个前提:左右子树必须是一个堆,才能调整**。



int array[] = {27,15,19,18,28,34,65,49,25,37};


![在这里插入图片描述](https://img-blog.csdnimg.cn/4594adb5bbfb48d6b2eae65c63758a39.png)



void Swap(HPDataType* left, HPDataType* right)
{
HPDataType temp = *left;
*left = *right;
*right = temp;
}
void AdjustDown(Heap* hp,int n,int parent)
{
int child = parent * 2 + 1; //调整结点的左孩子
while (child < n) //当child大于结点个数时调整完毕
{
//判断是否有右孩子并且右孩子大于左孩子
if (child + 1 < n&&hp->array[child] < hp->array[child + 1])
{
child += 1;
}
if (hp->array[child]>hp->array[parent])
{
Swap(&hp->array[child], &hp->array[parent]); //若孩子大于父亲则交换
//继续向下调整继续判断
parent = child;
child = parent * 2 + 1;
}
else
{
return;
}
}
}


#### 2.2 堆的创建


  对于一个非堆的完全二叉树,我们要将其转换为一个堆就要从倒数第一个非叶子节点开始执行堆向下调整算法,一直调整直到根结点 ,这样这棵树就成了一个堆。



void HeapCreate(Heap* hp, HPDataType* a, int n)
{
assert(hp);
hp->array = (HPDataType*)malloc(sizeof(HPDataType)*n);
if (hp->array == NULL)
{
return;
}
//将一个数组全部拷贝到要调整的二叉树中
memcpy(hp->array, a, sizeof(HPDataType)*n);
hp->size = hp->capacity = n;
//从倒数第一个非叶子节点开始调整
for (int root = (n - 2) / 2; root >= 0; root–)
{
AdjustDown(hp, n, root);
}
}


#### 2.3 堆的插入


  先将待插入元素放置在数组的最后,也就是二叉树的最后一个叶子结点,然后再执行向上调整算法。而**向上调整算法**就是将刚插入的结点作为孩子,再找到它的父亲结点与之比较,若孩子结点大于父亲结点就交换,一直到根结点或者孩子结点小于父亲结点才结束。



void AdjustUP(Heap* hp, int child)
{
assert(hp);
int parent = (child - 1) / 2;
while (child)
{
if (hp->array[child]>hp->array[parent])
{
Swap(&hp->array[child], &hp->array[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
return;
}
}
}

void CheckCapacity(Heap* hp)
{
assert(hp);
if (hp->capacity == hp->size)
{
int newCapacity = hp->capacity + 3;
HPDataType* temp = (HPDataType*)realloc(hp->array,sizeof(HPDataType)* newCapacity);
if (NULL == temp)
{
assert(0);
return;
}
hp->array = temp;
hp->capacity = newCapacity;
}

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

mg-y3oBgStW-1715800484261)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值