数据结构(2) 树形结构

245 阅读9分钟

基本概念

Q: 什么是度?

度就是节点拥有子节点的个数。

Q: 什么是叶子节点

度为O的节点

Q: 什么是二叉树

每个节点的的度最多为2.就是最多有两个子树。且有序

Q: 什么是真二叉树

节点的度要不为0 要不为2。(没有度为1的节点)

Q: 什么是满二叉树

节点的度要不为0 要不为2。所有叶子节点都在最后一层。

Q: 什么是完全二叉树

叶子节点只会出现在最后两层。且最后一层像左对齐

满二叉树一定是真二叉树
满二叉树一定是完全二叉树。

二叉树

如图。是一个二叉树。可以看得出来他是一个有序树。他的左子树比又子树小

在我们面试的过程中有些问题

  • 什么是二叉树。

  • 说一下二叉树前序遍历、中序遍历、后续遍历的区别。

    1. 前序遍历。先遍历根节点,然后遍历左子树,右子树。最常见的可以使用递归遍历
     func orderTree(Node *node) {
       if node == nil return;
        printf(node.element)
        ordertree(node.left)
        ordertree(node.right)
      }
    
    1. 中序遍历。 左子树 根节点 右子树 或者 右子树 根 左 (中序遍历出来的数据 从小到大排列的。或者从大到小排列的)
     if node == nil return;
     ordertree(node.left)
     printf(node.element)
     ordertree(node.right)
    
    1. 后序遍历。 根节点放在最后遍历。
  • 写一个反转二叉树的算法。

    反转二叉树的算法完全可以基于遍历来写。例如:

    func orderTree(Node *node) {
       if node == nil return;
       
        Node temp = node.left;
        node.left = node.right;
        node.right = temp	
        
        ordertree(node.left)
        ordertree(node.right)
    }
    

二叉搜索树

任意节点。他所有左子树。小于所有右子树。

如果在一个动态整数中,查找一个整数

使用有序动态数组。虽然可以用2分查找 O(logn)。但是插入很慢 O(n)。 这个时候可以用搜索二叉树

他可以提高搜索效率。 但是他存储的元素必须有可比性。如果存储对象,必须指定可比方式。

  • 他的添加,删除,查找复杂度为。O(h)和他的高度紧密相关。 也可以写成 O(logn)

AVL树

如果搜索二叉树的添加顺序是 从小往大添加的。 那他就会退化成链表类似的结构。 为了防止搜索二叉树退化成链表。 所以平衡二叉树登场。

  • 两边子树的高度差的越小。 越平衡。

  • 我们是没有办法控制数据添加和删除的顺序的。能做的是在添加和删除之后。使用尽量少的调整,适度的提高树的两边平衡。

  • 但是调整的次数也不能太多,太多的话反而增加时间复杂度。应该是适当的调整,达到适度的平衡即可

  • 一个达到适度平衡的二叉搜索树,叫做 平衡二叉搜索树 (AVL 红黑树 自平衡的二叉搜索树)

那么AVL树就是根据平衡因子来确保。二叉搜索树不会降低为链表,从而损失该有的性能。

那什么是平衡因子呢。 其实就是两个子树的高度差。 AVL平衡因子的 每个平衡因子 只能是 1,0 , -1 所以他的 搜索添加删除的复杂度 都是 O(logn)

  • 当给AVL添加元素的时候时候,如果平衡因子大于1或者小于-1的时候, 他会通过旋转来保持平衡。

总结:

  1. AVL的添加元素,可能所有祖先节点都失衡。 但是只要调整最低的失衡点,就全部恢复平衡

  2. AVL删除元素。 会导致被删除的父节点或者祖先节点失去平衡 。 父节点平衡之后,可能会导致更高层的节点失衡。所以要全部恢复平衡才行 logn次

  3. 平均时间复杂度。

    搜索 O(logn)

    添加 O(logn) 他的旋转是 O(log1)

    删除 O(logn) 但是他的 旋转也是 O(log1)

B树

B树不一定是二叉树。 是一个平衡的多路搜索树。多用于文件系统,数据库的实现

  • 一个节点可以存储超过2个元素、可以拥有超过2个子节点
  • 拥有二叉搜索树的一些性质
  • 平衡、每个节点的所有子树高度一致
  • 比较矮

红黑树

**由于线性数据结构在搜索的时候存在一定瓶颈,所以有了树状结构来解决。线性结构的搜索效率一般都是O(n). 如果是有序的话(二分查找是logN 、但是删除和添加负责度也是On) 二叉搜索树的 logN次。 **

AVL树是为了防止二叉搜索树退化成链表的。所有有了 自平衡二叉树

AVL树缺点是在删除的时候,可能会有logN次的调整。所以有了红黑树,红黑树的添加删除都是O1级别来的

性质

也是一种自平衡的二叉搜索树。也可以叫他为平衡二叉B树

红黑树和4阶B树是等价的

添加

由于 红黑树和B树等价的性质,在由于B树添加的节点都是在叶子节点。 所以推论如下

1、一共添加分为12中情况

2、 四种父节点为黑色。 则直接添加不需要调整,他可以完全满足红黑树五条性质

3、八种需要修改。 他不能满足性质4 (八种里面。前四种添加之后不会上溢)

(后四种情况他的叔父节点是红色。 前面四种是黑色(null 也是黑哦))

  • LL\RR 情况。 parent染成black 。 grand染成red 然后 grand 旋转

  • LR\RL 如上图如果加的是48 和 74 。 则染色是一直的。 只不过是需要旋转两次。 和AVL一样。

  • 上溢 - LL / 上溢 - RR 两种类似

  • 上溢 - LR / 上溢 - RL . 都和上面的类似就不截图了 。 代码如下

    if(isRed(uncle)) {
    	black(parend);
    	blacl(grand);
    	affteradd(red(grand));
    }
    

删除

由于红黑树和B树等价。 所以删除红黑树肯定是删除他的子节点。 有下面八种情况。

  • 其中四种是删除红色节点, 红色节点的删除对原二叉树性质没有影响,所以直接删除
  • 但是25的话是不可能被删除的。 因为删除25的时候会找他的前驱或者后继来覆盖他。 那其实就是删除17、33、就是这样理解的
  • 所以就剩下 46、76、88 . 三种情况
  • 46 和 76 的判断条件是 替代他的节点是红色。 删除他们也很简单, 就是帮他删除然后替代他的染成黑色。 入下图2.。

  • 如果删除图1 的88 节点的话。 则有一个问题就是 B树 下溢。解决下溢两种八法

  • 1、找兄弟节点借 。 如下图、 如果可以借的话。则有两个条件。 兄弟节点是黑色,切兄弟节点有红色子节点

  • 2、兄弟是黑色,但是他没有红色子节点,那他就不能借了。 就找父节点下来合并

    如果父节点是红色。直接下来就可以。 如果是黑色,则父节点也会下溢。那就帮父节点当成新的要删除的节点来处理就可以。如下图

  • 3、兄弟是红色。则如下图。 让兄弟节点的儿子成为自己的兄弟节点。 然后在删除

AVL 的平衡是靠平衡因子。 平衡因子 1,0 , -1

红黑树怎么保证平衡的 ?

1、红黑树的五条性质,可以保证红黑树等价四阶B树。 从B树的角度来说B树是非常平衡的。

他能保证,最大路径最多是最短路径的两倍。因为最大路径和最短路径的黑色节点相同,最多多出一倍的红色节点。

AVL 是强平衡。 但是红黑树是弱平衡。没有那么的平衡。

集合

特点

  1. 不存放重复的元素。
  2. 没有索引
  • 常用于存储新增的东西。(新增IP等)
  • 去重

底部封装

内部可以,用动态数据、链表, 红黑树,等数据结构来实现

  1. 使用链表封装

查找 On

删除 On

添加 On

  1. 使用红黑树封装

搜索 O(logn)

添加 O(logn)

删除 O(logn)

对比可知。 如果大数据操作。红黑树性能要比链表高的多。 二叉树本来就是去重的功能

但是红黑树也是有限制的。 就是元素必须要有可比性,如果没有可比性就没有办法加进去。

那就使用什么呢 ? ~~~

映射

Map 在有些语言里面叫做字典 (Python,OC,Swift)

底部可使用链表。 或者红黑树来实现。 但是链表的性能比较差。 所以使用红黑树。

红黑树的节点,包含key value。 key 作为可比较的值。

缺点和集合一样。 他的key 要有可比较性。 而且查找 containsValue 比较麻烦必须要遍历查找。 所以是On 级别的

如果用value 作为可比较,那查找key就会比较麻烦

Java官方的 TreeMap 用的就是红黑树

Map和Set的关系

如果Map 去掉 value 就是一个Set。

iOS 的 Set 和 dict 不一定是红黑树, 他没有开源。 但是我想不是吧,因为红黑树节点要有可比性。

复杂度

添加、删除、搜索 都是 logN。 但是value

特点

key 有可比较性

元素分布有顺序。 前序遍历可以安顺序遍历的出来。

但是在实际开发过程中,很多key是不需要顺序的。
而且 key 是不需要有可比较性的。 

这个时候,用TreeMap 是不是有点浪费了 ?

不考虑顺序,不考虑Key的,可比较性,Map是否有更好的实现方案,平均时间复杂度可以达到O1 的?

那就使用Hash表来实现吧。