在数据结构中,二叉树这一部分是十分的重要,也是面试中经常考察的部分。不仅仅是要处在了解阶段,还要对于二叉树的性质,实现原理,以及二叉树中运用过程中使用递归思想的进行了解。
想要学习二叉树我们先要对树进行一个了解。
树是一一种非线性的数据结构,它是由n (n>=0) 个有限结点组成一个具有层次关系的集合。 把它叫做树是因为它看起来像一棵倒挂的树, 也就是说它是根朝上,而叶朝下的。
有一个特殊的结点,称为根结点,根节点没有前驱结点。
除根节点外,其余结点被分成M(M>0}个互不相交的集合T1、T2、… Tm,其中每一个集合Tl(1<=i<=m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱, 可以有0个或多个后继.因此,树是递归定义的。
注意:子树是不相交的
除了根节点外,每个结点有且仅有一个父节点;
一颗N个节点的树有N-1条边。
树是一个一个的节点组成,表示节点的关系,一般情况下使用指针,但使用数组也可以表示。
如果是使用数组,需要通过连续的空间,创建一个结构体类型的元素,通过下标访问左右孩子。
不过这种表示方式在删除节点的时候需要遍历;
在插入的时候如果空间不够,需要扩容,把原空间的数据拷贝到
新空间,操作不够简便。
所以,我们需要通过指针的方式去存储,一般树的表示方法有很多种表示方法,汝双亲表示法,孩子表示法,孩子兄弟表示法等等。这里我们就简单介绍一下孩子兄弟表示法
typedef int TDataType;
struct Node
{
struct Node* firstchild;//第一个孩子节点
struct Node* nextbrother;//指向其下一个兄弟节点
TDataType data;//节点中的数据区域
};
孩子指向它的下一个孩子,兄弟指向同层次兄弟的数据区域。
1.节点的度:一个节点含有的子树的个数成为该节点的度;如上图:
A的度为6.
2.叶节点或终端节点:度为0的节点称为叶节点:如上图: B、C、H、1…等节点为叶节点
3.非终端节点或分支节点:度不为0的节点;如上图: D、E、F、G…等节点为分支节点
4.双亲节点或父节点:若个节点含有子节点, 则这个节点称为其子节点的父节点;如上图: A是B的父节点
5.孩子节点或子节点:一 个节 点含有的子 树的根节点称为该节点的子节点;如上图: B是A的孩子节点
6.兄弟节点:具有相同父节点的节点互称为兄弟节点;如上图: B、C是兄弟节点
7.树的度:一棵树中,最大的节点的度称为树的度;如上图:树的度为6
8.节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
9.树的高度或深度:树中节点的最大层次;如上图:树的高度为4
10.堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图: H、1互为兄弟节点
11.节点的祖先:从根到该节点所经分支上的所有节点;如上图: A是所有节点的祖先
11.子孙:以某节点为根的子树中任-节点都称为该节点的子孙。如上图:所有节点都是A的子孙
12.森林:由m (m>0)棵互不相交的树的集合称为森林;
2.1概念:
一颗二叉树是结点的一个有限集合, 该集合成者为空,或者是由一个根节点加上两颗树分别称为左子树和右子树的二叉树组成。
2.2二叉树的特点:
1.每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
2.二叉树的子树有左右之分,其子树的次序不能颠倒。
2.3特殊二叉树
1.满二叉树:一个二叉树, 如果每一个层的结点数都达到最大值,则这个二又树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2k)-1,则它就是满叉树。
2.完全二叉树:完全二叉树是效率很高的数据结构,完全叉树是由满二叉树而引出来的。 对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满叉树中编号从1至n的结点一 对应时称之为完全二叉树。要注意的是满二叉树是一种特殊的完全二叉树。
1.若规定根节点的层数为1,则棣非空=叉树的第层上影多有2^(i-1)个结点。
2.若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h-1。
3.对任何一棵二叉树,如果度为0其叶结点个数为n0,度为2的分支结点个数为n2.则有n0=n2+1。
4.若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=Log2(n+1)。
5.对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编,于序号为的结点有:
1.若i>0, 1位置节点的双亲序号: (-1)/2; i=0, 为根节点编号;无双亲节点
2.若2i+1=n否则无左孩子
3.若2i+2=n否则无右孩子
普通的二叉树是适合用来存储数组的,因为可能造成大量的浪费,而完全二叉树更适合用顺序结构来存储。现实中我们通常把堆使用顺序结构的数组来存储。
有关于堆的详细操作我已经在上一篇文章中具体介绍:
有关于堆的详细介绍
二叉树的链式存储结构是指用链表来表示一颗二叉树,就是用链表来指示元素的逻辑关系。
通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。
链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链。
要对二叉树进行遍历,我们就得对遍历得概念有一个清楚得认识。
遍历就是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。
访问结点所做的操作依赖于具体的应用问题。遍历是二 叉树上最重要的运算之一
,是 二叉 树上进行其它运算之基础。
注意二叉树中的操作大多数需要递归来进行完成
4.11前序遍历:
是访问根节点得操作发生在遍历其左右子树之前——既遍历顺序为根,左子树,右子树。
运用递归得思想;
代码部分
void PreOrder(TNode* root)
{
if (root)
{
printf("%d", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
}
4.12中序遍历:
与前序遍历相比只是遍历得顺序发生了变化——左子树,根,右子树。
代码部分
void IneOrder(TNode* root)
{
if (root)
{
IneOrder(root->left);
printf("%d", root->data);
IneOrder(root->right);
}
}
4.13后序遍历:
遍历得顺序为——左子树,右子树,根
代码部分
void PostOrder(TNode* root)
{
if (root)
{
PostOrder(root->left);
PostOrder(root->right);
printf("%d", root->data);
}
}
4.14层序遍历:
设二叉树的根节点所在的层数为1,层序遍历就是从所在二叉树的根节点出
发,首先访问第一层的树根节点,然后从左到右访问第二层上的节点,以此类推,
自上 而下,自作而右逐层访问树的节点过程就是层序遍历。
层序遍历是通过队列来实现的,通过定义一个结构体类型的变量 q,进行初始化,将二叉树中的root给进去,按照根,左子树,右子树的顺序,依次插入到队列中,进行循环,然后删除根节点,也就是第一个插入队列的元素,当队列中为空时循环结束。这样就实现了层序遍历,最后将q销毁掉。
代码部分
void LeveOrder(TNode* root)
{
Queue q;
if (NULL==root)
{
return;
}
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
TNode* cur = QueueFront(&q);
printf("%d", cur->data);
if (cur->left)
{
QueuePush(&q, cur->left);
}
if (cur->right)
{
QueuePush(&q, cur->right);
}
QueuePop(&q);//删除根点也就是第一个插入队列中的
}
QueueDestroy(&q);
}
二叉树的创建与二叉树的遍历十分类似
创建二叉树时提供的序列中只包含了有效元素,如果遇到的节点没有左右孩子,
无法区分的序列,在序列中必须标记那些节点有左右孩子,那些没有。
代码部分
TNode* _CreateTree(TDataType arr[], int size, int*index, TDataType invaild)
{
TNode* root = NULL;
if (*index<size && invaild != arr[*index])
{
//创建根节点
root = buyTreeNode(arr[*index]);
//创建左子树
++(*index);
root->left = _CreateTree(arr, size,index,invaild);
//创建右子树
++(*index);
root->right = _CreateTree(arr, size,index,invaild);
}
return root;
}
TNode* CreateTree(TDataType arr[], int size, TDataType invaild)
{
int index = 0;
return _CreateTree(arr, size, &index,invaild);
}
二叉树的销毁也是和遍历的规则类似,运用递归,这里按照后序遍历的规则进行销毁
代码部分
void DestroyTree(TNode** root)
{
assert(root);
if (NULL == *root)
{
return;
}
DestroyTree(&(*root)->left);
DestroyTree(&(*root)->right);
free(*root);
*root = NULL;
}
代码部分
int TreeSize(TNode* root)
{
if (NULL == root)
return 0;
//根节点加左节点和右节点 用递归
return 1 + TreeSize(root->left) + TreeSize(root->right);
}
代码部分
int YZTreeSize(TNode* root)
{
if (NULL == root)
return 0;
if (NULL==root->left && NULL==root->right)
{
return 1;
}
return YZTreeSize(root->left) + YZTreeSize(root->right);
}
代码部分
int DKTreeSize(TNode* root, int k)
{
if (NULL == root || k <= 0)
return 0;
//第一层中只有根节点
if (1 == k)
return 1;
return DKTreeSize(root->left, k - 1) + DKTreeSize(root->right, k - 1);
}
求出左右节点的高度,较大的那个加一,则为树的高度。
代码部分
int TreeHeight(TNode* root)
{
if (0 == root)
return 0;
int leftHeight = TreeHeight(root->left);
int rightHeight = TreeHeight(root->right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight+1;
}
TNode* TreeFind(TNode* root, TDataType x)
{
TNode* ret=NULL;
if (NULL == root)
return NULL;
if (x == root->data)
return root;
if (ret = TreeFind(root->left, x))
return ret;
return TreeFind(root->right, x);
}
代码部分
int TreeComplete(TNode* root)
{
Queue q;
int flag=0;
//空树也是完全二叉shu
if (root == NULL)
return 1;
//检测
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
TNode* cur = QueueFront(&q);
if (flag)
{
//从第一个不包含节点之后,所有的节点不能有孩子
if (cur->left || cur->right)
{
QueueDestroy(&q);
return 0;
}
}
else
{
//按照层序的方式遍历找第一个不饱和的节点
if (cur->left&&cur->right)
{
QueuePush(&q, cur->left);
QueuePush(&q, cur->right);
}
else if (cur->left)//只有左孩子
{
QueuePush(&q, cur->left);
flag = 1;
}
else if (cur->right)
{
//不符合完全二叉树
QueueDestroy(&q);
return 0;
}
else
{
//左右孩子都为空,找到了
flag = 1;
}
}
QueuePop(&q);
}
QueueDestroy(&q);
return 1;
}
二叉树中的一些实现大多数运用了递归的思想,所以要搞明白递归的实
现原理。上图中有的代码没有给出原理 是因为在代码中已经标注。
将自己所学的一些知识进行总结,供自己的复习以及有需要的学习,如有不对的地方,敬请指教,同时也希望自己能一直坚持,一直在路上!!!!