引言
在前面的一段时间里,我们学习了顺序表、链表、栈、队列的知识。其实这些顺序结构都是线性的,它们的逻辑结构都是一条线穿起来的。
在这篇文章中,将介绍另一种非线性的数据结构,树形结构:
树与二叉树
树的概念及结构
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
树由根结点与子节点组成,一个根结点可以有多个子节点;
中间的子节点又是子树的根结点;
最后的结点没有子节点,为叶子结点:
(需要注意的是,树的结点之间不能有交集,也就是对于一个结点,永远只有一条途径可以访问到)
除此之外,我们需要了解一些树的相关概念:
节点的度:一个节点含有的子树的个数称为该节点的度;(如上图:a的度为4)
叶节点或终端节点:度为0的节点称为叶节点;(如上图:f、g、h等结点为叶子结点)
非终端节点或分支节点:度不为0的节点;(如上图:b、c、d、e等节点为分支节点)
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;(如上图:a是b的父节点)
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;(如上图:b是d的孩子节点)
兄弟节点:具有相同父节点的节点互称为兄弟节点;(如上图:b、c是兄弟节点)
树的度:一棵树中,最大的节点的度称为树的度;(如上图:树的度为4)
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
节点的祖先:从根到该节点所经分支上的所有节点;(如上图:a是所有节点的祖先)
子孙:以某节点为根的子树中任一节点都称为该节点的子孙;(如上图:所有节点都是a的子孙)
森林:由m(m>0)棵互不相交的树的集合称为森林。
树的表示
相对于线性表,树的表示就会比较麻烦,我们并不能确定一个结点会有多少个子节点。所以如果我们有要根据结点个数来用指针指向下一个结点来表示的话,就会很麻烦。
有一种方式以神奇的方式来表示树,即孩子兄弟表示法:
即用结构体来存储每个结点的数据,每个结点中都有存储数据的成员val,以及指向该结点的第一个孩子结点的指针与指向该结点下一个兄弟的指针:
typedef int DataType;
struct Node
{
struct Node* _firstChild1; // 第一个孩子结点
struct Node* _pNextBrother; // 指向其下一个兄弟结点
DataType _data; // 结点中的数据域
};
用这个方法来表示上图中的树:
二叉树
二叉树是一种特殊的树形结构:
二叉树不存在度大于2的结点;
二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树:
如图的两个二叉树虽然数据相同,但并不是相同的二叉树:因为在第一个二叉树中C结点的右节点为F,而在第二个中F为C的左节点。
特殊的二叉树
满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树;
完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对
应时称之为完全二叉树。
要注意的是满二叉树是一种特殊的完全二叉树;
二叉树的性质
- 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点;
- 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h-1;
- 对任何一棵二叉树,如果度为0其叶结点个数为n,度为2的分支结点个数为m,则有n=m+1;
- 若规定根节点的层数为1,具有n个结点的满二叉树的深度为log(n+1);(ps:log(n+1)是log以2为底,n+1的对数)
- 对于具有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否则无右孩子。
(最后一个性质可以帮助我们顺序访问二叉树)
二叉树的表示
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构:
顺序结构
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空
间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺
序存储在物理上是一个数组,在逻辑上是一颗二叉树:
这种结构结合上面的第5个性质,有着容易访问二叉树中的任意元素的优点。即可以通过公式轻松的算出某个结点的父子结点的下标,来进行访问。
但是也存在一定的问题:
即顺序结构在存储非完全二叉树时,会有或多或少的内存浪费:
链式结构
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是
链表中每个结点由三个成员组成,数据和左右指针,左右指针分别用来给出该结点左孩子和右孩子所
在的链结点的存储地址:
我们可以创建如下的结构体来表示结点:
typedef int BTDataType;
struct BinaryTreeNode
{
struct BinTreeNode* _pLeft; // 指向当前节点左孩子
struct BinTreeNode* _pRight; // 指向当前节点右孩子
BTDataType _data; // 当前节点值
}
这种存储结构的缺陷也很明显,即不能任意的访问某一个结点。
总结
到此,关于树的基础知识就介绍完了
接下来会详细的介绍用二叉树的顺序结构实现堆以及其他的相关知识,欢迎持续关注哦
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出
如果本文对你有帮助,希望一键三连哦
希望与大家共同进步哦