欢迎来到本期频道!
本期让我们一起来探索一下神奇的二叉树吧!
二叉树的定义:
在了解二叉树之前,我们需要稍微了解一下什么是树
横向观看:树是一种非线性 的数据结构,是由N个有限个结点组成具有层次关系 的集合
纵向观看:树有一个没有前驱结点的根结点,其余结点被分成M个互不相交 的集合。 其中每一个集合又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
一些常用的树的相关概念:
结点的度:一个结点含有的子树的个数称为该结点的度 叶结点或终端结点:度为0的结点称为叶结点 树的度:一棵树中,最大的结点的度称为树的度 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推 树的高度或深度:树中结点的最大层次
所以什么是二叉树呢?
如果一棵树的每个节点都有两个子树,既每个节点的度最大为2 ,那么该树就是二叉树。 由于二叉树的根结点只有两个子树,所以我们把它叫做左子树和右子树, 这两个子树存在顺序,故二叉树是有序树。
例图: 一般用链式结构存储:
满二叉树 :一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。
完全二叉树 :完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树引出来的。 对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。
二叉树的性质:
❶.若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点
❷. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是2^h-1.
❸. 若规定根结点的层数为1,具有n个结点的满二叉树的深度,h= log(N+1)( log是以2 为底的)
❹. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点有: 双亲结点:(i-1)/2 左孩子:2i+1 右孩子:2i+2
❺. 对任何一棵二叉树, 如果度为0其叶结点总数为n₀ , 度为2的分支结点总数为n₂ , 则有 n₀=n₂+1 设树的边数为L,树的总结点树为N,对于任意一棵树都有 L=N-1. 设该二叉树的度为1的节点总数为n₁, 则该二叉树的边数L = 2*n₂+1*n₁+0*n₀ (度为多少对应几条边) 又因为节点总数为N = n₂+n₁+n₀ 化简一下就可以得出结论
二叉树的结构:
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。 二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
这里的向下调整算法 可以结合视频理解:
堆排序:
void swap ( int * x, int * y)
{
int ret = * x;
* x = * y;
* y = ret;
}
void AdjustDown ( int * arr, int parent, int n)
{
int child = parent * 2 + 1 ;
while ( child < n)
{
if ( child + 1 < n && arr[ child + 1 ] > arr[ child] )
{
child++ ;
}
if ( arr[ child] > arr[ parent] )
{
swap ( & arr[ child] , & arr[ parent] ) ;
parent = child;
child = parent * 2 + 1 ;
}
else
{
break ;
}
}
}
void HeapSort ( int * arr, int n)
{
int i = 0 ;
for ( i = ( n - 1 - 1 ) / 2 ; i >= 0 ; i-- )
{
AdjustDown ( arr, i, n) ;
}
for ( i = 0 ; i < n - 1 ; i++ )
{
swap ( & arr[ 0 ] , & arr[ n - 1 - i] ) ;
AdjustDown ( arr, 0 , n - 1 - i) ;
}
}
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
二叉树的方法:
typedef char DataType;
typedef struct BTNode
{
struct BTNode * left;
struct BTNode * right;
DataType val;
} BTNode;
我们以先序遍历 字符串创建二叉树,‘#’代表空树
以字符串 “abc##d##e” 为例: 思想:递归
结束条件:空树 子问题:创建根结点,让根结点的左指针连左子树,根结点的右指针连右子树。
从逻辑层面看:
BTNode* GetNode ( DataType x)
{
BTNode* newnode = ( BTNode* ) malloc ( sizeof ( BTNode) ) ;
if ( newnode == NULL )
{
perror ( "malloc fail:" ) ;
exit ( 1 ) ;
}
newnode-> left = newnode-> right = NULL ;
newnode-> val = x;
return newnode;
}
BTNode* BinaryTree_Create ( DataType* arr, int n, int * index)
{
if ( * index >= n)
return NULL ;
if ( arr[ * index] == '#' )
{
( * index) ++ ;
return NULL ;
}
BTNode* root = GetNode ( arr[ ( * index) ++ ] ) ;
root-> left = BinaryTree_Create ( arr, n, index) ;
root-> right = BinaryTree_Create ( arr, n, index) ;
return root;
}
从物理层面看:
这里可以明白,如果树的高度是h,那么BinaryTree_Create函数需要开辟h+1 快函数栈帧空间。
思想:递归
结束条件:空树高度0 子问题:左子树的高度与右子树的高度较高的+根的高度1
int BinaryTree_Height ( BTNode* root)
{
if ( root == NULL )
return 0 ;
int left = BinaryTree_Height ( root-> left) ;
int right = BinaryTree_Height ( root-> right) ;
return left > right ? left+ 1 : right+ 1 ;
}
结束条件:空树0 子问题:根的数量1+左子树的节点数量+右子树的节点数量
int BinaryTree_Size ( BTNode* root)
{
if ( root == NULL )
return 0 ;
return 1 + BinaryTree_Size ( root-> left) + BinaryTree_Size ( root-> right) ;
}
结束条件:空树0,叶子1 子问题:左子树的叶子数量+右子树的叶子数量
int BinaryTree_LeafSize ( BTNode* root)
{
if ( root == NULL )
return 0 ;
if ( root-> left== NULL
&& root-> right== NULL )
return 1 ;
return BinaryTree_LeafSize ( root-> left) + BinaryTree_LeafSize ( root-> right) ;
}
结束条件:k==1返回1,空树0 子问题:左子树的k-1层的节点个数+右子树k-1层的节点个数
int BinaryTree_KLevelSize ( BTNode* root, int k)
{
if ( root == NULL || k<= 0 )
return 0 ;
if ( k == 1 )
return 1 ;
return BinaryTree_KLevelSize ( root-> left, k- 1 ) + BinaryTree_KLevelSize ( root-> right, k- 1 ) ;
}
思想: 广度优先遍历
根节点先入队列,然后出一个,带下一层节点入队列, 当对头节点是空节点时,判断队列中是否有非空节点, 如果没有,证明了该节点所在层数往后 到 该节点下一层位置都没有非空节点, 也就说明该节点之后没有非空节点,所以是完全二叉树,反之否。
bool BTIsComplete ( BTNode* root)
{
Queue queue;
QInit ( & queue) ;
if ( root != NULL )
{
QPush ( & queue, root) ;
}
while ( ! QIsEmpty ( & queue) )
{
root = QGetFront ( & queue) ;
QPop ( & queue) ;
if ( root == NULL )
{
while ( ! QIsEmpty ( & queue) )
{
root = QGetFront ( & queue) ;
QPop ( & queue) ;
if ( root != NULL )
{
QDestroy ( & queue) ;
return false;
}
}
break ;
}
QPush ( & queue, root-> left) ;
QPush ( & queue, root-> right) ;
}
QDestroy ( & queue) ;
return true;
}