树的理解

2019-08-16  本文已影响0人  pujess

1.什么是树?

树解决的是一对多的问题,一般来说什么是一对多的问题呢,分类问题(分类问题某种意义上也概括了从属问题)其实就是一对多问题,经常说的思维导图什么的:

图1
分类有一个很重要的特性:子类互不相交
如果分完类,不同类之间还有重叠的部分,还叫什么分类呢?
比如最近的很火的垃圾分类,把许多垃圾分成不同的垃圾种类,但是在丢垃圾的时候必须只能选一个种类丢,总不可能把垃圾分成两半一个丢不可回收垃圾分类,一个丢有害垃圾分类吧?
诚然,在人们生活中分类总是不完全的,但是对于一个能够被理解和使用的分类必须是能够把事物和事物区分的开的,也就是说,类与类之间不能有相交。

2.树的定义

有关于树的例子就是上面的分类相关的问题。凡是树的每一分支,都代表着一种分类依据。比如图1中,特色功能和特色功能代表着对思维导图功能点的分类依据。

树的定义

要点:

3.结点

结点是组成一棵树的重要部分,它的重要意义是——结点是数据的载体。之所以是载体不是全部,因为它不仅代表了数据元素,也存放了数据元素之间的关系(双亲/孩子关系)。

结点的度(根结点、叶结点)

结点有多少分支,就有多少度。树的度就是最大结点度
“度”解决了啥问题?

  1. 度可以解决存储树时,存储空间预留多少的问题
  2. 度可以解决遍历树时,每个结点循环多少次取它的孩子们

结点层次

结点层次这个概念提出来,与“层数”也就是树的深度,功能和度其实差不多,方便确定存储空间的预留遍历时的复杂度

结点之间的关系

  1. 双亲(父)
  2. 兄弟

这三种关系和问题的具体分类情况有关,才能确定谁是父结点、子结点

4.树的有序性

将树中的结点从左往右看成是有次序,那么就是一棵有序树,相对的就是无序树。
为什么要引入树的有序性?
实际上树的有序性对于描述问题并没有很大的作用,在对事物分类的过程中,引入顺序是没有太多意义的。

但是!!!!对存储和寻找很有用,只有给了结点顺序,我才能定位到某一个数据元素。

5.树的存储结构

要把一棵树存进计算机(内存)里面,不简单。
之前我们说过在计算机里存储数据结构只有顺序存储和链式存储两种


Tips:但是要注意的是,无论是顺序存储还是链式存储还是顺序存储混合链式存储,都需要结合逻辑结构去设计相应的存储方式。这种存储方式要做到既能够表示问题的逻辑,也要符合计算机存储和寻址的方式


在树这里也是一样的,需要怎么去设计才能够既能满足计算机顺序链式存储的结构,又能表示树里数据元素之间的关系呢?

因此就有了这三种设计思路:

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

这些表示法不一定固定用哪一种计算机存储结构,有用数组解决,有用链表解决,更有混合的。但是他们有一个共同点:都是通过--数据元素+数据元素与双亲(孩子/孩子和兄弟)的关系来构成一个结点的!

这个关系怎么表达?是由下面两种方法来解决这个问题:

  1. 链表指针的形式来指向
  2. 记录别的结点在数组中下标来指向(当然使用这种方法有一个前提就是必须把所有结点存成数组)

另外,一个节点里面数据元素和关系也可有顺序和链式两种方式表达

存储结构小结:

要点一:树的存储结构解决的问题是,如何才能够既能满足计算机顺序链式存储的结构,又能表示树里数据元素之间的关系
要点二:设计思路有三种,分别描述了结点中存放的“关系”是怎么样一种数据与数据关系
要点三:怎么去描述结点中需要存放的“关系”呢?,是用链表指针还是用数组下标
要点四:一个结点需要存放两个东西:1.数据元素 2.关系(和其它结点的);因此这两个存放的东西之间的关系又可以用链表或者数组来描述
--数据结构(四)树---树的存储结构里面有详细的描述,可以好好感悟一下

二叉树

二叉树是树的一种,它描述分类问题一般分类依据只有一种,比如:大小、多少、强弱...
是一种二分类问题!因此二叉树的左右结点是有差别的!你想一种情况,当有两棵树,每棵树只有两个元素,一棵树是大的元素就放右边,另一棵是较小的元素放左边,那虽然两个树都只有一个子结点,但是这两棵树并不等价

提前说一下把原本的一棵树变为二叉树,就是把一棵树里面的结点分成两种类型——是结点的长子&结点的兄弟
这里暗示了一个——问题和问题之间是可以相互转换的,把树问题结点编上号它就变成了线性表

二叉树的作用:

  1. 解决原本就是二分类的问题
  2. 二分类问题方便存储进计算机里,也方便遍历

接下来所有的东西目的都是为了把二叉树这种二分类问题放进计算机里操作

1.研究如何存放

2.研究如何对二叉树操作(遍历、建立)

最复杂的问题就是如何遍历树了,如果所有结点存成顺序存储结构,那么遍历就不是一件难事,但是以链表的形式存二叉树的时候,就不那么简单了,如何对二叉树操作(遍历)呢?

    void InOrderTraverse(BiTree T) {
        if(T==NULL) {
            return;
        }
        InOrderTraverse(T->lchild);
        printf("%c",T->data);
        InOrderTraverse(T->rchild);
    }

1.判断左子树是否遍历完
2.是,则执行操作 (输出Data)
3.判断右子树是否遍历完

同样

    void InOrderTraverse(BiTree T) {
        if(T==NULL) {
            return;
        }
        printf("%c",T->data);
        InOrderTraverse(T->lchild);
        InOrderTraverse(T->rchild);
    }

1.进入函数,执行操作 (输出Data)
2.判断左子树是否遍历完
3.判断右子树是否遍历完

整一个递归其实就是一个判断的过程,操作步骤前的递归语句一定要执行完才能执行操作
因此对于树的递归遍历,无论是前序中序还是后序,要输出Data,就必须要判断前面的递归语句完成没有
如果输出Data,那么意味着输出语句前面递归语句全部执行,也就是说一个前置条件,就和跑程序一样

  1. 一开始指针指向F结点,那么就要找到以F为根的这棵树的第一个元素,如果线索化的是先序遍历就是找到F
  2. 然后因为F没有后继指针,那么我就判断实际后继结点是谁,因为这个是先序遍历,因此要往左子树去找,找到结点C。
  3. 然后将以C为根的这棵树重复上述过程。
  4. 找到结点A之后,就顺着A的后继指针找到结点D,再顺着后继指针找到B
  5. B后继找到E,然后E没有后继指针,就找以E为根的树的第一个遍历的元素,找它的左子树....直到全部遍历完成

可以参考博客——https://blog.csdn.net/dongfei2033/article/details/80709975

树、森林、二叉树之间的转换

因为二叉树问题便于理解和操作,所以把树和森林转换为二叉树。

转换过程其实就是对树和森林分类的过程,把一棵树的结点可以用是兄弟还是长子来分类,把森林的结点可以用是根还是子结点来分类,(其实树和森林都差不多)

树的先根遍历=先访问树的根结点再去遍历子树=二叉树的先序遍历
树的后根遍历=先访问每棵子树,再访问根结点=原树的后序遍历=二叉树的中序遍历

森林的前序遍历=先访问根结点,再访问每棵子树=二叉树前序遍历
森林的后序遍历=先访问每棵子树=二叉树的中序遍历

总结

树.png
上一篇 下一篇

猜你喜欢

热点阅读