【C# 数据结构】树 开篇

学习路径

 

然后去leetcode刷简单的题目。

然后去leetcode刷简单的题目。

复习所有 在刷leetcode难部分

 

 

 

树的定义

树可以用递归的形式来定义:树T是由n(n>=0)个结点组成的有限集合,他或者是颗空树,或者包含一个根结点和零或若干棵互不相干的子树。

可以使用广义表(纯表)的形式树结构,如下树结构用广义表表示:A(B(E,F),C(G),D(H,I,J))

 

 表示树结构的广义表没有共享和递归成分,是一种纯表。广义表中的原子对应于树的叶节点,树的非叶子结点则用子表结构表示。

空树:节点树为0的树,即n=0时,称为空树。

非空树:一颗非空树T具有以下特点

            (1)有且仅有一个根节点

              (2)没有后继的节点称为“叶子节点”,有后继的节点称为“分子节点”.

              (3)当树的结点数n>1时,根结点之外的其他结点可以分为m(m>=1)个互不相交的集合T1,T2,T3.。。。Tm,其中每个集合Ti(1<=i<=m)具有与树T相同的树结构,称为子树。每颗子树的根结点有且仅有一个直接的前驱结点。

 

 

                            这是图

 

 

树的分类:

树结构可以分为有序和无序树两种类型,有序树种最常用的是二叉树。

  

 

 

树结构的应用场景:

操作系统的文件系统,根目录是文件树的根节点,子目录是树中的分子节点,文件是树结构的叶子节点。

除了根节点外,任何节点有且仅有一个前驱,有多个前驱的节点的叫图。叶子节点没用后继节点。

 

 

森林

 

若干颗互不相交的树的集合称为森林。给森林加上一个根节点就变成一颗树。将树的根结点删除就变成由子树组成的森林。

 

 

 

 

 

 

 

 

 C# 节点的深度是从0开始计算的。

 树类型概述:

二叉树,完全二叉树,满二叉树,二叉排序树,平衡二叉树,红黑树,B树,B+树,B*树、2-3-4树、2-3树

 平衡二叉树开始涉及到旋转。

操作

二叉树常用操作旋转(rotate),该操作为常数时间复杂度。 二叉树旋转前后,中序遍历的结果不变。因此树的任何部分旋转,对整棵树的元素顺序没有影响。

在哈夫曼树中用到。

 

 

 

总结

为什么有了数组和链表还要引入二叉树?

针对数组和链表的优缺点,无法说链表一定优于数组,或者是数组一定优于链表,因为某些长期的需要,所以就推出一个相对折中的二叉树。

为什么有了二叉树还要引入平衡二叉树?

有了二叉树还不算完,二叉树有一种极端的情况,就是所有的子结点偏向一端,二叉树退化成链表,这就相当于我选择了这种的二叉树,你现在罢工不干了,找了个链表来糊弄我...

所以为了解决二叉查找树退化为链表的情况,引入了平衡二叉树,即:

平衡二叉树是为了解决二叉树退化成一棵链表而诞生的。

既然有了平衡二叉树,这下总没有问题了吧?

为什么有了平衡二叉树还要引入红黑树?

但是是实际使用过程中,因为平衡二叉树追求绝对严格的平衡关系,显然这个规则在于频繁的插入、删除等操作的情景性能肯定会出现问题...

所以为了解决这个问题,进而又引入了红黑树。

平衡二叉树追求绝对严格的平衡,平衡条件必须满足左右子树高度差不超过1,红黑树是放弃追求完全平衡,它的旋转次数少,插入最多两次旋转,删除最多三次旋转,所以对于搜索、插入、删除操作较多的情况下,红黑树的效率是优于平衡二叉树的。

红黑树是终结吗?

时代总是进步的,大胆猜测不会是,就跟当初从数组、链表到二叉树一样。

至此,通过这篇希望大家对整个树结构的出现有一个基础的概念,目前面试中最为常问的就是红黑树了,当然这得益于 HashMap,但红黑树还有挺多其他的知识点可以考察,例如红黑树有哪些应用场景?红黑树与哈希表在不同应该场景的选择?红黑树有哪些性质?红黑树各种操作(插入删除查询)的时间复杂度是多少?等等等等...

 

 

树的双亲表示法、孩子表示法和孩子兄弟表示法

在使用结构描述实际问题时,大多数不是二叉树,更多的是普通的树结构,在存储之间具有普通树结构的数据时,经常使用的方法有3种:

  1. 双亲表示法
  2. 孩子表示法
  3. 孩子兄弟表示法

双亲表示法

取一块连续的内存空间,在存储每个结点的同时,各自都附加一个记录其父结点位置的变量。

在树结构中,除了树根外,每个结点都只有一个父结点(又叫“双亲结点”)。


代码表示:

  1. #define tree_size 100//宏定义树中结点的最大数量
  2. #define TElemType int//宏定义树结构中数据类型
  3. typedef struct PTNode{
  4. TElemType data;//树中结点的数据类型
  5. int parent;//结点的父结点在数组中的位置下标
  6. }PTNode;
  7. typedef struct {
  8. PTNode nodes[tree_size];//存放树中所有结点
  9. int r,n;//根的位置下标和结点数
  10. }PTree;


例如,使用双亲表示法存储 1(A)中的树结构时,数组存储结果为(B):

 

 


                    (A)                                                                               (B)

图 1 双亲表示法
 

当算法中需要在树结构中频繁地查找某结点的父结点时,使用双亲表示法最合适。当频繁地访问结点的孩子结点时,双亲表示法就很麻烦,采用孩子表示法就很简单。

孩子表示法

将树中的每个结点的孩子结点排列成一个线性表,用链表存储起来。对于含有 n 个结点的树来说,就会有 n 个单链表,将 n 个单链表的头指针存储在一个线性表中,这样的表示方法就是孩子表示法。

如果结点没有孩子(例如叶子结点),那么它的单链表为空表。

代码表示:

  1. #define TElemType int
  2. #define Tree_Size 100
  3. //孩子表示法
  4. typedef struct CTNode{
  5. int child;//链表中每个结点存储的不是数据本身,而是数据在数组中存储的位置下标
  6. struct CTNode * next;
  7. }*ChildPtr;
  8. typedef struct {
  9. TElemType data;//结点的数据类型
  10. ChildPtr firstchild;//孩子链表的头指针
  11. }CTBox;
  12. typedef struct{
  13. CTBox nodes[Tree_Size];//存储结点的数组
  14. int n,r;//结点数量和树根的位置
  15. }CTree;

例如,使用孩子表示法存储图 1 (A),存储效果如图 2:

 

 



图 2 孩子表示法


使用孩子表示法存储的树结构,正好和双亲表示法相反,适用于查找某结点的孩子结点,不适用于查找其父结点。可以将两种表示方法合二为一,存储效果如图 3:

 

 



图 3 孩子双亲表示法

孩子兄弟表示法

使用链式存储结构存储普通树。链表中每个结点由 3 部分组成:

 

 



图 4 结点构成
 

其中孩子指针域,表示指向当前结点的第一个孩子结点,兄弟结点表示指向当前结点的下一个兄弟结点。

代码表示:

  1. #define ElemType int
  2. typedef struct CSNode{
  3. ElemType data;
  4. struct CSNode * firstchild,*nextsibling;
  5. }CSNode,*CSTree;


通过孩子兄弟表示法,普通树转化为了二叉树,所以孩子兄弟表示法又被称为“二叉树表示法”或者“二叉链表表示法”。

例如,用孩子兄弟表示法表示图 1 (A)的普通树,存储结果为:

 

 



图 5 二叉链表表示法

补:森林和二叉树的相互转化

通过孩子兄弟表示法的学习,对于任意一棵树,都可以找到唯一的一棵二叉树与之对应。

普通树转化成的二叉树,其根结点都没有右孩子,即普通树对应的二叉树肯定没有右子树。

而森林是由多棵树组成,为了便于对森林的遍历等操作,需要将森林中的所有树都组合成一颗大的二叉树,转化步骤为:

  1. 首先将森林中树各自转化为二叉树;
  2. 森林中第一棵二叉树的树根作为转化后二叉树的树根;
  3. 其他树的树根作为第一棵树树根的兄弟结点,进行连接;

 

 



图 6 森林转化成二叉树


如图 6 所示,(A)中由三棵普通树组成的森林,首先三棵普通树采用孩子兄弟表示法各自转化成二叉树,如(B)所示;然后由(B)转(C)时,将森林中第一棵树的树根作为转化后的整棵二叉树的树根,其他数的树根作为第一棵树的树根的兄弟结点,如(C)所示。

转化成二叉树的森林,做的最多的操作就是查找树中的结点。在遍历转化后的二叉树时,遍历方式有先序遍历、中序遍历和后序遍历。

对森林使用先序和中序遍历的结果和对转化后的二叉树使用先序和中序遍历得到的序列是一样的,而使用后序遍历得到的结果不同。例如图 6(B)中森林采用中序遍历和(C)中二叉树采用中序遍历得到的结果是相同的,遍历序列都为:B C D A F E H J I G

总结

树的三种表示方法中,双亲表示法和孩子表示法在实际算法中的应用场景正好相反:双亲表示法应用于解决查找某结点的父结点,而孩子表示法应用于查找某结点的孩子结点。

孩子兄弟表示法可以将普通树转化成二叉树存储,在实际操作中,可以应用二叉树的性质来解决普通树或者森林的问题。

posted @ 2022-05-19 07:49  小林野夫  阅读(912)  评论(0)    收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/