树的基本概念
树是一种非线性的数据结构,因它的结构与倒着的树类似而得名。
树由N个节点构成,其中最上面有一个根节点,其它的节点可以认为都是根节点的孩子。
既然是倒过来的树,那么上图的A节点就相当于根,往下伸展最下面的就是叶子。
树有N个节点,又是由多个结构相同的子树组成的,像上图:
每个子树的根节点只能有一个前驱,可以有多个后驱(也可以没有)。
并且子树之间不能有交集,否则就不是树形结构,而是另一种数据结构:图。
像红线这里,产生了交集,这就不是树,而是图了。
下面列举了树结构里面一些常见的概念:
关于这里节点的层次、树的高度说明一下:
有的书上定义层次时,将根节点A(最上面的根节点)层次定义为1,但也有的定义为0,这是两种不同的定义方式,我个人认为起始层次定义为1更好一点(原因如下)
森林是由多棵树构成的,像并查集就是森林。
树的表示
要写一个树的结构就不像顺序表和链表那么简单了,因为它不是线性的,不能顺着一条线访问。
而且节点个数不确定,这样在构造结构的时候就会比较麻烦。
像这样不知道有多少个节点不好下手,这时我们想到可以用指针数组来封装节点。
用一个childArr封装节点,并用childSize记录节点个数。但是这相当于是静态的树,因为它把树的度给定死了,有的节点的度是50,但有的可能只有2,就会有很多浪费,无法满足我们的需求。这时候就需要动态的。
再用一个顺序表来存节点,需要用到二级指针,但这种结构虽然直观了一点,但是在C语言中很复杂,如果是在C++下面上个容器会清楚很多,但也不是最优的,有高手就创造了一种很好的结构(左孩子右兄弟表示法)。
让根节点去找它的第一个子节点,让第一个子节点去找它的兄弟节点。相当于一个爹先养一个大儿,大儿再养二儿.......这样就可以找到全部节点了。
有人可能会问,不是不能有交集吗?注意:这里是实现方式,实现方式可以任意,但是结构是不能产生交集的。我这么实现,但是结构出来是没有交集的。
下面是一段打印整个树的伪代码:
typedef struct TreeNode
{
struct TreeNode* child;
struct TreeNode* brother;
int data;
}TreeNode;
void TreeNodePrint(TreeNode* parent)
{
if (parent == NULL)
return;
TreeNode* cur = parent->child;//找到第一个孩子
while (parent)
{
//printf(); 打印数据
TreeNodePrint(parent);//递归调用,打印整个树
cur = cur->brother;
}
}
数还有一种表示法,叫双亲表示法。这种方式方便任何节点找祖先,像之前说的并查集就是这种表示法。
树的实际应用
像我们操作系统文件目录什么的就是树来实现的。
当我们点list这个文件夹时,就类似执行上面那种伪代码(只是类似),然后就显示下一层(子节点)。