双亲表示法:
注:其中r=0表示的是根,n=11表示是11个结点。然后parent的数字指向是0-10的下标。
这样的存储结构,我们可以根据某结点的parent指针找到它的双亲结点,所用的时间复杂度是O(1),索引到parent的值为-1时,表示找到了树结点的跟。
因此可是,如果我们要知道某结点的孩子是什么?那么不好意思,请遍历整个树结构。
我们可以稍微改变下结构:
那么我们现在又比较关心它们兄弟之间的关系怎么办?
注:-1表示不存在兄弟结点,同时还有注意同一个双亲才可以称为兄弟结点。
综上可知,存储结构的设计是一个非常灵活的过程,只要你愿意,你可以设计出任何你想要的奇葩。一个存储结构设计是否合理,取决于基于该存储结构的运算是否适合、是否方便,时间复杂度好不好等等。
孩子表示法:
由于树中每个结点可能有多颗子树,可以考虑用多重链表来实现。
方案一:根据树的度,声明足够空间存放子树指针的结点。
缺点是,就是造成了浪费。就是空间上的浪费。
方案二:
虽然克服了浪费的概念,都是每个结点的度的值不同,初始化和维护起来难度巨大。就是时间上的浪费。
方案三:
这样相对于方案二来说降低了耦合度,对于A下面的结点来说,只需要1、2、3指示了就可以,不用再管它们下面是什么了。这样就避免了“动一发而动全身”。
那只找到孩子找不到双亲貌似还不够完善,那么我们合并上一讲,双亲孩子表示法:
(就是多了一行元素,指向parent)
二叉树:
二叉树是n个结点的有限集合,该集合或者为空集(空二叉树),或者由一个根结点和两课互不相交、分别称为根结点的左子树和右子树的二叉树组成。
左子树和右子树是有顺序的,次序不能颠倒。
满二叉树的特点有:
1.叶子只能出现在最下一层。
2.非叶子结点的度一定是2.
3.在同样深度的二叉树中,满二叉树的结点个数一定最多,同时叶子也是最多。
完全二叉树的特点有:
1.叶子结点只能出现在最下两层。
2.最下层的叶子一定集中在左部连续位置。
3.倒数第二场,若有叶子结点,一定都在右部连续位置。
4.如果结点度为1,则该结点只有左孩子。
二叉树的性质:
1.在二叉树的第i层上至多由2的(i-1)次方个结点。
2.深度为k的二叉树至多由(2的k次方-1)个结点。
3.对**任何一课二叉树**T,如果其终端结点树为n0,度为2的结点树为n2,则n0=n2+1
4.具有n个结点的完全二叉树的深度为(log2(n))取下限+1.
5.如果一棵树有n个结点的完全二叉树,其深度为【(log2(n))取下限+1】的结点按层序编号,对任一结点i(1<=i<=n)有以下性质:
a.如果i=1,则节点i是二叉树的跟,无双亲;如果i>1,则其双亲是结点(i/2)的下限。
b.如果2i>n,则结点i无做左孩子(结点i为叶子结点);否则其左孩子是结点2i。
c.如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1.
二叉树的存储结构:
二叉树的顺序存储结构:
这下看出完全二叉树的优越性了,由于他的严格定义,在数组直接能表现出逻辑结构。
当然对于一般二叉树,尽管层序编号不能反映逻辑关系,但是也可以按照完全二叉树编号方式修改一下,把不存在的结点用“^”代替即可。
但是存在一下极端情况,比如斜树,例如下图:
二叉链式:
二叉树每个结点最多有两个孩子,所以为它设计一个数据和两个指针域是比较自然的想法,我们称这样的链表叫做二叉链表。
二叉树的遍历:
二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
遍历方式:前序遍历 中序遍历 后序遍历 层序遍历
前序遍历:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。(这个根结点是每个子树的根结点) 根—左子树—右子树
所以遍历的顺序为:ABDHIEJCFKG
中序遍历:若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,然后中序遍历右子树。(这个根结点是每个子树的根结点,并不是最上面的那一个) 左子树—根—右子树
所以遍历的顺序为:HDIBEJAFKCG
后序遍历:若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后访问根结点。左子树—右子树—根
所以遍历的顺序是:HIDJEBKFGCA
层序遍历:
若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
所以顺序是:ABCDEFGHIJK
上面这些遍历方法就是把树中的结点变成某种意义的线性序列。
线索二叉树:
因为普通二叉树,1浪费空间,2浪费时间,所以才会有线索二叉树。
首先,我们看针对中序遍历的结果。
上图经过中序遍历后结果是:HDIBEAFCG
我们发现上面的HIEFG结点,都是刚才^造成浪费的结点,利用中序遍历刚好它们均处于字符中间,可以很好地利用^来存放前驱和后继的指针。这样当遍历到某个点时,我们就可以很快的找到它的前驱和后继,不用再逐个遍历了。
基于上面思路我们对定义的结构进行“扩容”:
这样就可以充分利用了,把不用的拿来指向前驱和后继。