树 树一个有n(n >= 0)个节点组成一个有限集合 1 有且仅有一个被称为根节点(root)的节点 2 当n > 1,除了根节点之外,剩余的m个节点互不相交,分别都是一颗颗的树 这些树我们称为子树,每一个节点实际上都是一棵树(子树)的根 树的每一个节点都包含两个部分 1 数据元素 2 节点和节点之间的关系,不像原先的链表,关系都是一个方向上面 1个节点可能有n个节点和它有关系(因此它就不是一个线性表) 每个节点的关系数量我们称为这个节点的度 度:出度(我可以到别人),树里面我们暂时只考虑出度 入度(别人到我) 在树里面如果一个节点和别人没有关系了(出度为0),这种节点我们称之为终端节点(叶子节点) 树的层次(深度):从根开始,根为第一层,根的儿子为第二层,儿子的儿子为第三层......不能乱了辈分 二叉树 -- 一种树的形态,每个节点的度(出度)最多只有2,如果你的树满足这一点 这棵树就被称为二叉树,二叉树我们就可以分大小两边 根据这个大小我们可以分为左右关系,左边的被称为左儿子 右边的被称为右儿子 二叉树有5种形态 1 空树 2 只有一个根节点 3 只有一个左儿子 4 只有一个右儿子 5 有左右儿子 二叉树的基本性质 1 在二叉树的第i(i从1开始)层上面最多有 2 ^ (i - 1) 2 深度为k的二叉树最多有 2 ^ k - 1 个节点 3 对于任何一个二叉树,有如下关系 n0为度为0的节点个数 n1为度为1的节点个数 n2为度为2的节点个数 n为总节点数 n = n2 + n1 + n0 n = 分支数(出度的个数) + 1 分支数 = 2 * n2 + 1 * n1 n = 2 * n2 + 1 * n1 + 1 so: 2 * n2 + 1 * n1 + 1 = n2 + n1 + n0 -> n2 + 1 = n0 ->这个性质对于任何一个多叉树都是适用的 假设你是一个m叉树 n = n0 + n1 + n2 + n3 + .... + nm n = 分支数(出度的个数) + 1 分支数 = m * nm + (m - 1) * n(m - 1) + .... + 3 * n3 + 2 * n2 + 1 * n1 n0 + n1 + n2 + n3 + .... + nm = m * nm + (m - 1) * n(m - 1) + .... + 3 * n3 + 2 * n2 + 1 * n1 + 1 n0 = 1 + n2 + 2n3 + 3n4 + .... + (m - 1)nm ----> 可以直接套公式的 二叉树的两种特殊的形态 满二叉树:深度为k的二叉树有 2 ^ k - 1 个节点,这颗二叉树就是一颗满二叉树 不增加深度的情况,不能往这棵树里面添加节点 完全二叉树: 1 除去最后一层是一个满二叉树 2 最下面的那一层所有的节点都是尽左排,一个节点的左边是不能添加新的节点 满二叉树是一个特殊的完全二叉树 4 对于一个有n个节点的完全二叉树从上到下,从左往右,从1开始进行编号 根为1 根的左儿子为2 根的右儿子为3 ...... 假设有一个节点的编号为 i 则,这个节点的父节点的编号为 i / 2 如果它有左儿子,那么它左儿子的编号为 2 * i 如果它有右儿子,那么它右儿子的编号为 2 * i + 1 5 具有n个节点的完全二叉树的深度为 log2(n) 向下取整 + 1 假设深度为k 2 ^ (k - 1) <= n < 2 ^ k 计算机如何来使用二叉树 首先我们应该要保存二叉树 1 数据 2 左右儿子(关系) 1 顺序结构来保存二叉树 由于完全二叉树是可以表示左右儿子关系和父亲关系的 因此完全二叉树才能用数组存储 就是上面的第4个性质 如果这棵树不是完全二叉树呢?数组保存不了,因此需要将不是完全二叉树的二叉树补全为完全二叉树 才能使用数组保存,这么一来浪费就有点多了 因此顺序结构存储二叉树就不是那么方便 2 链式结构保存 1 有数据域 2 有指针域 -> 至少要有两个指针 lchild rchild struct TreeNode { Tree_Datatype data;//树的数据 struct TreeNode * lchild;//指向它的左儿子 struct TreeNode * rchild;//指向它的右儿子 }; 二叉树的遍历: 遍历:根据某一种规则对集合里面所有元素的访问(必须访问1次,并且只能访问一次) 二叉树的遍历有三种(左边在右边的前面) 1 先序(根)遍历 -> 根 左 右 首先:先访问根节点 再次:以相同的规则继续访问根节点的左儿子 最后:以相同的规则继续访问根节点的右儿子 2 中序(根)遍历 -> 左 根 右 首先以相同的规则访问它的左孩子 访问根节点 最后以相同的规则继续访问根节点的右儿子 3 后序(根)遍历 -> 左 右 根 首先以相同的规则访问它的左孩子 再以相同的规则继续访问根节点的右儿子 访问根节点 练习: 一棵二叉树的先序为:EBADCFHGIKJ 中序为:ABCDEFGHIJK 请画出这棵树 先中序、中后序能确定这棵树 但是先后序不一定能确定这棵树 建立一棵二叉树:建立树之前我们需要先确定左边是什么关系,右边是什么关系 这种确定关系的二叉树我们叫二叉排序树 一般是根绝大小来进行确定的 一般左边为小右边为大 示例代码为左小右大 删除树里面一个节点 : 删除一个节点之后我们不能影响这个树的排序性(每个节点的左边都是小的,右边都是大的) 1 最特殊的,这个树是空树 2 这棵树就只有这么一个根节点,你还把它删了(这会导致树的根节点发生变化) 3 删除的这个节点只有一个左孩子 4 删除的这个节点只有一个右孩子 5 你要删除的这个节点是一个叶子 6 你要删除的节点有两个孩子 我们在建立这棵树的时候,这棵树有可能变得奇怪 比如说这棵树变得像一个链表一样 如你的加入顺序为ABCDEFG 还有像一个蚯蚓一样 还有很多种奇怪的情况,遇到这种情况树的查找效率就变得很低了 树的优势就没有了,因此我们需要处理,让这棵树看起来更像一棵树 这种操作我们叫平衡处理 AVL树 -> 平衡二叉树 height-balanced tree / balanced binary tree 规定AVL树里面所有节点的左孩子和右孩子的高度差的绝对值不超过1 这颗树我们就叫AVL树 AVL树在创建的时候我们就需要做平衡处理,加入一个节点就可能造成某一个节点不平衡 因此我们需要对每个节点都要做平衡处理 -- 由于没加如一个节点都要做平衡处理,因此它的添加效率较低 但是由于是平衡的,因此它的查找效率很高 如果想要它的插入效率高一点,查找效率也高 --- 红黑树(有限的平衡) AVL树我们需要做平衡处理 1 当我们往一个节点的左边的左边插入了一个节点,这个时候引起了这个节点的不平衡 单向右旋 2 当我们往一个节点的右边的右边插入了一个节点,这个时候引起了这个节点的不平衡 单向左旋 3 当我们往一个节点的左边的右边插入了一个节点,这个时候引起了这个节点的不平衡 先左旋再右旋 4 当我们往一个节点的有边的左边插入了一个节点,这个时候引起了这个节点的不平衡 先右旋在左旋 左左为右 右右为左 左右为左右 右左为右左 线索:树是非线性的,通过线索我们可以将树的遍历变成线性的 先序线索 中序线索 后序线索 将树的非线性转换成一个链表的线性 在树节点里面加上一个成员 struct ListTreeNode * _prenext; 先序线索指针 指向先序遍历的下一个 中序后序亦然 最小生成树 : 将所有的节点都连接起来,节点与节点的连接线上面有一个权值() 为了解决成本最低的问题 每次都在带加入节点里面找到权值最小的,将它加入到我们的树里面去 加入的时候不一定会和原树连接在一起,别急,你还要加的 当所有的节点全部被覆盖,你的最小生成树就弄完了 哈夫曼树:每个节点都是带值的,每次在待加入的节点里面找最小的两个 将这两个最小的节点合成一个新的节点 根据大小关系将树给建立出来 多个树在一起就是森林 平时看到的树都是多叉树,但是处理的时候我们需要转换为二叉树 将多叉树转为二叉树: 1 先将所有的兄弟依次连接起来(大哥连二哥,二哥连三哥.....) 2 断开除了大哥和父亲的关系以外的父子关系 3 拉开兄弟之间的连线,节点变成它前面的那个哥哥的右孩子(大哥的右孩子是二哥,二哥的右孩子是三哥..) 长子变成它的左孩子 森林转为二叉树 -> 根据多叉树转二叉树的规则可以知道, 转出来的二叉树是没有右孩子的,其它树都往前面一棵树上的右孩子去接 二叉树转树 1 将父节点与子节点的右孩子,右孩子的右孩子,右孩子的右孩子的右孩子...连接起来 2 断掉所有的右孩子关系 二叉树转森林 根节点的右孩子是二颗树 根节点的右孩子的右孩子是第三颗树 .....
12-21
1万+

08-05
211
