曾经,我非常羡慕那些人见人爱的人,我也想要变成那样,可是后来我才明白人见人爱也是需要天赋的,后来我开始默默努力,我想,就算我不能让每个人都喜欢我,至少因为我做的努力能得到别人的尊重。

数据结构之树

第一部分:定义

  树(Tree)是n(n>=0)个结点的有限集。 n = 0 时成为空树。在任意一颗非空树中: 

    (1). 有且仅有一个特定的成为根(root)的节点。

    (2). 当 n > 1 时,其余结点可以分为m个互不相交的有限集 T1、 T2、 T3 .....Tm,其中每一个集本身又是一棵树,并且称为根的子树

  

 

第二部分:基本概念

  1. 结点分类

   在一棵树中,结点分为 根结点内部结点叶结点(又称终端结点)。

  结点拥有的子树的数目称为结点的度(Degree)。 B的度为1, D的度为3

  在一棵树中, 最大的结点的度为树的度。  由于D的度最多,所以树的度就是3.

  

  

   2.节点间关系

     其中B是A的孩子A是B的双亲, C是B的兄弟。 之所以称之为双亲是因为对于结点来说父母同体,只有这么称呼是比较合适的了。

  

    3. 其他相关概念

     结点的层次: 从根开始算起, 根为第一层,然后第二层,第三层....

      树的深度或高度: 结点的最大层次就是树的深度或高度。

      有序树:如果将树中结点的各子树看成是从左到右有次序的,不能互换的,那么该树就是有序树,否则就是无序树

 

第三部分: 树的存储结构

   我们知道, 树中某个结点的孩子有多个,所以无论是使用顺序存储结构还是使用链式存储结构,都不能很好的表示树。但是我们可以利用顺序存储结构和链式犓结构的特点来实现之。

   一. 双亲表示法

   我们假设以一组连续的空间存储树的结构,同时在每个节点中,附加一个指示器指示其双亲结点在数组中的位置,也就是说,每个结点不仅仅知道自己是谁意外,还知道它的双亲在数组中的哪个位置。  

   即一个结点包含两个部分: 1.  data(数据域) --- 用于存储结点的数据信息。   2. parent (指针域) --- 用于存储该结点的双亲在数组中的下标

    

   注意: 这样的好处是我们可以根据结点的parent指针很容易的找出它的双亲结点,所以复杂度是O(1),  但是如果我们要知道结点的孩子是谁,就得遍历整个结构才行。为了解决这个问题,我们可以增加一个结点最左边孩子的域,可以称之为长子域。  孩子可以找到了, 但是找兄弟呢 ? 我们还可以增加一个兄弟域,这样,可以发现:

存储结构的设计是一个非常灵活的过程,一个存储结构设计的是否合理,取决于该存储结构的运算是否合适、是否方便, 时间复杂度好不好等等。  

  

   二. 孩子表示法

    如果一棵树中的每一个结点都有很多的孩子,  那么这时孩子为重,  我们就可以考虑每个结点有多个指针域, 每个指针域指向一颗子树的根节点, 这种方法就是多重链表表示法。 不难想象,多重链表表示法的最终结构和一颗树的结构是无异的。但是根据一个结点需要多少个域的不同,我们可以将多重链表表示法分为下面两种:

   1. 每个结点的指针域的个数是数的度

     优点 这样做的好处在于树的结构是稳定的、整齐的、维护起来较为方便。 缺点 坏处在于有可能很多指针域都是空着的,造成了资源的浪费。

   2. 每个结点的指针域的个数就是其子节点的个数(即它的度)

      优点  这样做的好处在于不会造成资源的浪费。  缺点 坏处在于这样在后期维护起来会比较麻烦,且结构会比较混乱。

 

   ok!  那有没有一种方法可以回避上面的缺点呢? 当然有,这就是孩子表示法了, 孩子表示法,即以n个结点的单链表作为存储结构, 则n个结点就有n个孩子链表,每个结点又会引出孩子的节点(如果没有孩子,就不会引出,如果有,就一直引下去)。

    这样的表示法缺点在于,如果知道某个结点的双亲是谁呢?  并不难,只要我们把双亲表示法和孩子表示法综合一下不就行了吗?  这种思路当然可行

 

   三、 孩子兄弟表示法

  

 

 

第四部分:二叉树

  二叉树(Binary Tree)是n个结点的有限集合,该集合或者为空集,或者由一个根节点和两颗互不相交的、分别称为根节点的左子树和右子树的二叉树组成。  

  1. 二叉树的特点

  1.  每个结点最多有两个子树。
  2.  左子树和右子树的顺组是不能随意颠倒的。
  3.  即使某个结点只有一个子树,也要分清楚左子树还是右子树。
  4. 三个结点可以构成 5 种不同的二叉树

  

  2. 一些特殊的二叉树

  1. 斜树  如上图中的第二个图和最后一个图就是斜树 ---  每一层都只有一个结点,结点的个数和二叉树的深度相同。
  2. 满二叉树 在一颗二叉树中,如果所有分支结点都存在左子树和右子树,并且所有的叶子都在同一层上, 这样的二叉树就称为满二叉树
  3. 完全二叉树  对于一颗具有n个结点的二叉树按层序编号,如果编号为i (1 <= i <= n)的结点与同样深度的满二叉树中编号为i的结点在树中的位置完全相同,则这棵树就是完全二叉树。  编号方式: 从上到下,从左到右。
  4. 注意: 满二叉树一定是完全二叉树, 完全二叉树不一定是满二叉树。

  

  3. 二叉树的5条性质

  性质一:  在二叉树的第i层上最多有2i-1  个结点。(i>=1).   如二叉树的第三层上最多有2 的 2 次方个结点。

  性质二: 在深度(或高度)为k的二叉树上最多有 2k - 1 个结点。  如深度为3的二叉树的结点数最多为 2 - 1 = 7个结点。

  性质三: 对于任意一颗二叉树T, 如果其终端的节点数为n ,度为2的节点数为n2, 则有n0= n+ 1; 

  性质四: 具有n个结点的完全二叉树深度为[log2n] + 1 (其中[x]表示不大于x的最大整数)。

  性质五: 

  如果对一颗有n个结点的完全二叉树(其深度为[log2n] + 1)的结点按层序编号(从上到下,从左到右), 对任一结点i (1 <= i <= n)都有:

    1. 如果 i = 1, 那么结点i是二叉树的根。 如果i > 1, 那么双亲是结点[i/2]。

    2. 如果 2i>n, 则结点 i 无左孩子,否则其做孩子是2i。

    3. 如果2i + 1 > n, 则结点i无右孩子, 否则其右孩子是结点 2i + 1;

  

 

第五部分:遍历二叉树

二叉树的遍历 ( traversing binary tree )是指从根节点出发, 按照某种次序依次访问二叉树中的所有结点,使得每个结点都被访问一次且仅被访问一次。  

  这里的两个关键词是 访问 和 次序。

  显然,二叉树的访问次序不同于线性结构,对于线性结构而言,最多也就是从头到尾、循环、双向等简单的遍历方式。树的结点之间不存在唯一的前驱和后继,所以在访问了一个结点之后,下一个被访问的结点面临着不同的选择。因而,对于二叉树而言,遍历结点的方式就完全不同了。

  说明:一般我们的习惯是从左到右的,这里同样也做出这样的限制。那么遍历时,前中后是指根节点被访问的时间上的前、中、后。 

  如前序遍历时先访问根节点、 中序历时中间的时候访问根节点、后续遍历是最后访问根节点。 不论是哪种遍历方式都是先左子树、后右子树。

一: 前序遍历

  规则: 若二叉树为空, 则空操作返回,否则先访问根结点,然后前序遍历左子树,在前序遍历右子树。

   在二叉树中,先根后左再右。巧记:根左右。

 

二: 中序遍历

  规则: 若树为空, 则空操作返回, 否则先从根节点开始(注意并不是先访问根节点),中序遍历根节点的左子树,然后是访问根节点,最后中序遍历右子树。

     在二叉树中,先左后根再右。巧记:左根右。

  注意: 对称遍历就是中序遍历。

 

三: 后续遍历

  规则: 若树为空,则空操作返回,否则从左到右先叶子结点的方式遍历访问左子树,最后是访问根节点。

  在二叉树中,先左后右再根。巧记:左右根。

 

四:层序遍历

  规则: 若树为空,则空操作返回,否则从树的第一层开始,也就是根节点开始访问,从上而下逐层遍历,在同一层中,按照从左到右的顺序对结点逐个访问。

  这个是最为简单的,只要遵循从上到下、从左到右的规则即可。

 

规则: 1. 这里的空操作返回,就是指向上返回,且空操作返回是共同点。   2. 这里的左子树、右子树和根节点都是相对的,环境和事件稍有改变,这些名词所代表的实体就发生了彻底的变化。

   3. 每次访问到一个节点,那么就要以这个节点作为根节点看作一个二叉树。

 

posted @ 2017-03-10 22:31  Wayne-Zhu  阅读(301)  评论(0编辑  收藏  举报

一分耕耘,一分收获。