20_总结

一、动态数组

  • 普通动态数组
  • 环形动态数组
接口设计
  • int size(){} // 元素的数量
  • boolean isEmpty(){} // 是否为空
  • void add(E element){} // 添加元素到最后面
  • void set(int index,E element){} // 设置index位置的元素
  • void add(int index,E element){} // 在index位置插入一个元素
  • void remove(int index){} // 删除index位置的元素
  • boolean contains(E element){} // 是否包含某个元素
  • E get(int index){} // 获取index位置的元素
  • int indexOf(E element){} // 查看元素的索引
  • void clear(){} // 清除所有元素
复杂度分析
  • add(int index,E element)
    最好:O(1) 最坏:O(n) 平均:O(n)
  • add(E element)
    最好:O(1) 最坏:O(1) 平均:O(1) 均摊:O(1)
  • remove(int index)
    最好:O(1) 最坏:O(n) 平均:O(n)
  • set(int index,E element)
    最好:O(1) 最坏:O(1) 平均:O(1)
  • get(int index)
    最好:O(1) 最坏:O(1) 平均:O(1)

二、链表

  • 单向链表
  • 单向循环链表
  • 双向链表
  • 双向循环链表
  • 静态链表
接口设计

同动态数组相同

复杂度分析
  • 添加
    最好:O(1) 最坏:O(n) 平均:O(n)
  • 添加元素到结尾
    最好:O(1) 最坏:O(1) 平均:O(1)
  • 删除
    最好:O(1) 最坏:O(n) 平均:O(n)
  • 设置index位置的值
    最好:O(1) 最坏:O(n) 平均:O(n)
  • 获取inde位置的值
    最好:O(1) 最坏:O(n) 平均:O(n)
双向链表 vs 动态数组
  • 复杂度对比

    Snip20200914_22.png
  • 动态数组:开辟、销毁内存空间的次数相对较少,但可能造成内存空间浪费(可以通过缩容来解决)

  • 双向链表:开辟、销毁内存空间的次数相对较多,但不会造成内存浪费

  • 如果频繁在尾部进行添加、删除操作,动态数组、双向链表均可选择

  • 如果频繁在头部进行添加、删除操作,建议选择使用双向链表(环形数组也可以)

  • 如果有频繁的(在任意位置)添加、删除操作,建议选择使用双向链表

  • 如果有频繁的查询操作(随机访问操作),建议选择使用动态数组

三、栈

  • 栈是一种特殊的线性表,只能在一端进行操作
  • 往栈中添加元素的操作,一般叫做push,入栈
  • 从栈中移除元素的操作,一般叫做pop,出栈(只能移除栈顶元素,也叫做:弹出栈顶元素)
  • 后进先出的原则,Last In First Out ,LIFO
Snip20200914_23.png
栈的接口设计
  • int size(){} // 元素的数量
  • boolean isEmpty() {} // 是否为空
  • void push(E element){} // 入栈
  • E pop(){} // 出栈
  • E top(){} // 获取栈顶元素
  • void clear(){} // 清空
栈的实现
  • 利用动态数组
  • 利用双向链表
复杂度分析

时间复杂度: O(1)

四、队列

  • 队列是一种特殊的线性表,只能在头尾两端进行操作
  • 队尾(rear):只能从队尾添加元素,一般叫做enQueue,入队
  • 队头(front):只能从队头移除元素,一般叫做deQueue,出队
  • 先进先出的原则,First In First Out,FIFO
队列的类型
  • 普通队列
    只能从队尾添加元素,只能从队头移除元素,先进先出,优先使用双向链表实现
  • 双端队列
    可以从两端添加或者删除元素,优先使用双向链表实现
  • 循环队列
    其实队列底层也可以使用动态数组实现,并且各项接口也可以优化到O(1)的时间复杂度,这个用数组实现并且优化之后的队列叫做循环队列
  • 循环双端队列
    可以进行两端添加、删除操作的循环队列
队列的接口设计
1. 普通队列
  • int size(){} // 元素的数量
  • boolean isEmpty(){} // 是否为空
  • void clear(){} // 清空
  • void enQueue(E element){} // 入队
  • E deQueue(){} // 出队
  • E front(){} // 获取队列的头元素
2. 双端队列
  • int size(){} // 元素的数量
  • boolean isEmpty(){} // 是否为空
  • void clear(){} // 清空
  • void enQueueRear(E element){} // 从队尾入队
  • E deQueueFront(){} // 从队头出队
  • void enQueueFront(E element){} // 从队头入队
  • E deQueueRear(){} // 从队尾出队
  • E front(){} // 获取队列的头元素
  • E rear(){} // 获取队列的尾元素
复杂度分析

时间复杂度:O(1)

五、二叉树

二叉树的特点
  • 每个节点的度最大为2(最多拥有2颗子树)
  • 左子树和右子树是有顺序的,因此二叉树是有序树
  • 即使某节点只有一棵子树,也要区分左右子树
二叉树的性质
  • 非空二叉树的第i层,最多有2^(i-1)个节点(i >= 1)
  • 在高度为h的二叉树上最多有2^h-1个节点(h >= 1)
  • 对于任何一棵非空二叉树,如果叶子节点个数为n0,度为2的节点个数为n2,则有:n0 = n2 + 1;
    • 假设度为1的节点个数为n1,那么二叉树的节点总数 n = n0 + n1 + n2;
    • 二叉树的边树 T = n1 + 2*n2 = n - 1 = n0 + n1 + n2 - 1
    • 因此 n0 = n2 + 1;
真二叉树

所有节点的度要么为0,要么为2,也就是所有非叶子节点的度都为2

满二叉树
  • 最后一层节点的度都为0,其他节点的度都为2,也就是所有非叶子节点的度都为2,且所有的叶子节点都在最后一层
  • 在同样高度的二叉树中,满二叉树的叶子节点数量最多、总节点数量最多
  • 满二叉树一定是真二叉树,真二叉树不一定是满二叉树
  • 假设二叉树的高度为h(h>=1),那么
    • 第 i 层的节点数量:2^(i-1)
    • 叶子节点数量:(2^(h-1))
    • 总节点数量 n = 2^h - 1;
    • h = log2(n+1)
完全二叉树
  • 完全二叉树:对节点从上至下、左至右开始编号,其所有编号都能与相同高度的满二叉树中的编号对应
  • 叶子节点只会出现最后2层,最后1层的叶子节点都靠左对齐
  • 完全二叉树从根节点至倒数第2层是一棵满二叉树
  • 满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树
完全二叉树的性质
  • 度为1的节点只有左子树

  • 度为1的节点要么是1个,要么是0个

  • 同样节点数量的二叉树,完全二叉树的高度最小

  • 假设完全二叉树的高度为h(h>=1),那么

    • 至少有2^(h-1)个节点
    • 最多有2^h - 1 个节点
    • 假设总节点数量为n
      2^(h-1) <= n < 2^h
      h-1 <= log2(n) < h
      那么 h = floor(log2(n)) + 1 (floor是向下取整,另外ceiling是向上取整)
  • 一棵有n个节点的完全二叉树(n > 0 ),从上到下、从左到右对节点从1开始进行编号,对任意第i个节点

    • 如果 i = 1 ,它是根节点
    • 如果 i > 1,它的父节点编号为 floor( i/2 )
    • 如果 2i <= n,它的左子节点编号为2i
    • 如果 2i > n ,它无左子节点
    • 如果 2i + 1 <= n, 它的右子节点编号为 2i + 1
    • 如果 2i + 1 > n ,它无右子节点
  • 一棵有n个节点的完全二叉树(n > 0 ),从上到下、从左到右对节点从0开始进行编号,对任意第i个节点

    • 如果 i = 0 ,它是根节点
    • 如果 i > 0,它的父节点编号为 floor( (i-1)/2 )
    • 如果 2i + 1 <= n-1,它的左子节点编号为2i+ 1
    • 如果 2i + 1 > n - 1 ,它无左子节点
    • 如果 2i + 2 <= n - 1, 它的右子节点编号为 2i + 2
    • 如果 2i + 2 > n - 1,它无右子节点
完全二叉树面试题

如果一颗完全二叉树有768个节点,求叶子节点的个数。
解题思路:

  1. 假设叶子节点个数为n0,度为1的节点个数为n1,度为2的节点个数为n2
  2. 总节点个数n = n0 + n1 + n2 ,而且n0 = n2 + 1,得出n = 2n0 + n1 -1
  3. 又已知完全二叉树的n1要么为0,要么为1
    • n1为1时,n = 2n0,n必然是偶数,叶子节点个数n0 = n/2,非叶子节点个数 n1 + n2 = n/2
    • n1为0时,n = 2n0 - 1,n必然是奇数,叶子节点个数n0 = (n + 1)/2,非叶子节点个数 n1 + n2 = (n - 1) /2

总结:
叶子节点个数 n0 = floor((n+1)/2) = ceiling(n/2)
非叶子节点个数 n1+ n2 = floor(n/2) = ceiling( (n-1)/2)

因此叶子节点个数为384

二叉树的遍历
  • 前序遍历
  • 中序遍历
  • 后序遍历
  • 层序遍历
二叉树的高度
判断一棵二叉树是否为完全二叉树
前驱节点
后继节点
表达式树

如果将表达式的操作数作为叶子节点,运算符作为父节点(假设只是四则运算),这些节点刚好可以组成一棵二叉树

比如表达式:A / B + C * D - E,组成的二叉树如下:

Snip20200911_7.png

如果对这棵二叉树进行遍历

  • 前序遍历,刚好就是前缀表达式
    -+/AB*CDE
  • 中序遍历,刚好就是中缀表达式
    A/B+C*D-E
  • 后序遍历,刚好就是后缀表达式
    AB/CD*+E-

六、二叉搜索树

二叉搜索树是二叉树的一种,是应用非常广泛的一种二叉树,英文简称BST,又被称为二叉查找树、二叉排序树

二叉搜索树的性质
  • 二叉搜索树的任意一个节点的值都大于其左子树所有节点的值
  • 二叉搜索树任意一个节点的值都小于其右子树所有节点的值
  • 二叉搜索树的左右子树也是一棵二叉搜索树
  • 二叉搜索树存储的元素必须具备可比较性
  • 二叉搜索树存储的元素不允许为null
二叉搜索树的接口设计
  • int size() // 元素的数量
  • boolean isEmpty() // 是否为空
  • void clear() // 清空所有元素
  • void add(E element) // 添加元素
  • void remove(E element) // 删除元素
  • boolean contains(E element) // 是否包含某元素
接口的实现
  • 添加
    1. 如果添加的是第一个节点,创建节点并赋值给根节点,size++
    2. 如果添加的不是第一个节点,从根节点开始遍历,找到父节点parent,创建节点并添加到父节点的left或者right;如果遇到相等的值覆盖
  • 删除
    1. 删除叶子节点,直接删除
    2. 删除度为1的节点,用子节点替代原节点的位置
    3. 删除度为2的节点,先用前驱或者后继节点的值覆盖原节点的值,然后删除相应的前驱或者后继节点
复杂度分析

二叉搜索树的添加、删除、搜索的时间复杂度和树的高度有关,也就是O(h)

  1. 最好情况:二叉搜索树的添加、删除、搜索的时间复杂度都为O(logn)

如果按照7、4、9、2、5、8、11的顺序添加节点,会得到一棵满二叉搜索树,已知满二叉树的h = log2(n+1),所以当二叉搜索树是一棵满二叉树的时候,二叉搜索树的添加、删除、搜索的时间复杂度都为O(logn)

  1. 最坏情况:二叉搜索树退化成链表,添加、删除、搜索的时间复杂度都为O(n)

如果按照从小到大添加节点,二叉搜索树退化成了链表,h = n,二叉搜索树的添加、删除、搜索的时间复杂度都为O(n)

七、平衡二叉搜索树

二叉搜索树在添加、删除节点时,都可能会导致二叉搜索树退化成链表,为了防止二叉搜索树退化成链表,让添加、删除、搜索的复杂度维持在O(logn),我们需要在添加或者删除节点操作之后,想办法让二叉搜索树恢复平衡。

平衡:当节点数量固定时,左右子树的高度越接近,这棵二叉树就越平衡(高度越低)

最理想平衡,就是像完全二叉树、满二叉树那样,高度是最小的

二叉搜索树调整方案:用尽量少的调整次数达到适度平衡即可,一棵达到适度平衡的二叉搜索树,可以称之为平衡二叉搜索树

平衡二叉搜索树

Balanced Binary Search Tree,简称 BBST,经典常见的平衡二叉搜索树有:

AVL树
红黑树
一般也称它们为:自平衡的二叉搜索树

八、AVL树

AVL树的特点
  • 每个节点的平衡因子(某节点的左右子树的高度差)只可能是1、0、-1(绝对值<=1,如果超过1,称之为失衡)
  • 每个节点的的左右子树的高度差不超过1
  • 搜索、添加、删除的时间复杂度是O(logn)
添加导致的失衡
  • 最坏情况:可能会导致所有祖先节点都失衡
  • 父节点、非祖先节点,都不可能失衡
  • 只要让高度最低的失衡点恢复平衡,整棵树就恢复平衡,仅需O(1)次调整

恢复平衡的几种情况:
首先找到失衡节点g,接着找到它左右子树较高的子节点p,接着找到p左右子树较高的子节点n,然后判断n位于g的位置

  • LL
    g右旋(zig),让父节点成为这棵树的根节点,是整棵树都达到平衡
  • RR
    g左旋(zag),让父节点成为这棵树的根节点,使整棵树都达到平衡
  • LR
    p左旋,g右旋
  • RL
    p右旋,g左旋
private void rotateRight(Node<E> grand){
        Node<E> parent = grand.left;
        Node<E> child = parent.right;
        grand.left = child;
        parent.right = grand;
        afterRotate(grand,parent,child);

    }

    private void rotateLeft(Node<E> grand){
        Node<E> parent = grand.right;
        Node<E> child = parent.left;
        grand.right = child;
        parent.left = grand;

        afterRotate(grand,parent,child);
    }

    private void afterRotate(Node<E> grand,Node<E> parent,Node<E> child){

        // 更新parent的父节点
        parent.parent = grand.parent;

        // 更新parent在父节点中的位置
        if (grand.isLeftChild()){
            grand.parent.left = parent;
        }else if(grand.isRightChild()){
            grand.parent.right = parent;
        }else {
            root = parent;
        }

        //更新child的父节点
        if (child != null){
            child.parent = grand;
        }

        //更新grand的父节点
        grand.parent = parent;

        //更新高度
        updateHeight(grand);
        updateHeight(parent);
    }

删除导致的失衡
  • 可能会导致父节点或祖先节点失衡(只有一个节点会失衡),其他节点都不可能失衡

  • 如果恢复平衡之后,树的高度和之前不一样,可能会导致更高层的祖先节点失衡,需要再次恢复平衡,极端情况下,所有祖先节点都需要进行恢复平衡的操作,最多需要O(logn)次调整

时间复杂度分析
  • 搜索:O(logn)
  • 添加:O(logn),仅需O(1)次的选择操作
  • 删除:O(logn),最多需要O(logn)次的选择操作

九、B树

B树是一种平衡的多路搜索树,多用于文件系统、数据库的实现。

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

m阶:一个节点最多拥有m个子节点

假设一个节点存储的元素个数为x

  • 根节点:1 <= x <= m-1
  • 非根节点:⌈m/2⌉ - 1 <= x <= m-1(⌈⌉ :向上取整,⌊⌋:向下取整)
  • 如果有子节点,子节点个数 y = x+1
    • 根节点:2 <= y <= m
    • 非根节点:⌈m/2⌉ <= y <= m

比如m = 3, 2<=y<=3,因此可以称为(2,3)树,2-3树;
比如m = 4, 2<=y<=4,因此可以称为(2,4)树,2-3-4树;
比如m = 5, 3<=y<=5,因此可以称为(3,5)树,3-4-5树;
比如m = 6, 3<=y<=6,因此可以称为(3,6)树;
比如m = 7, 4<=y<=7,因此可以称为(4,7)树;

B树VS二叉搜索树

B树和二叉搜索树,在逻辑上是等价的

  1. 多代节点合并,可以获得一个超级节点
    • 2代合并的超级节点,最多拥有4个子节点(至少是4阶B树)
    • 3代合并的超级节点,最多拥有8个节点(至少是8阶B树)
    • m代合并的超级节点,最多拥有2^n 个子节点(至少是2^n阶B树)
  2. m阶B树,最多需要log2(m)代合并
搜索

和二叉搜索树的搜索类似

  1. 先在节点内部从小到大开始搜索元素
  2. 如果命中,搜索结束
  3. 如果未命中,再去对应的子节点中搜索元素,重复步骤1
添加

新添加的元素必定是添加到叶子节点

假设下图是一个4阶B树


Snip20200901_13.png

插入55


Snip20200901_10.png

插入95
Snip20200901_11.png

插入98
最右下角的叶子节点的元素个数将超过限制,这种现象可以称之为:上溢

添加 -上溢的解决
  • 上溢节点的元素个数必然等于m
  • 假设上溢节点最中间元素的位置为k,将k位置的元素向上与父节点合并
  • 将[0,k-1]和[k+1,m-1]位置的元素分裂成2个子节点,这2个子节点的元素个数,必然不会低于最低限制
  • 一次分裂完毕后,有可能导致父节点上溢,依然按照上述方法解决
  • 最极端的情况,有可能一直分裂到根节点
Snip20200901_18.png
Snip20200901_19.png
Snip20200901_17.png
删除
  • 删除-非叶子节点
    假如需要删除的元素在非叶子节点中
    1. 先找到前驱或后继元素,覆盖所需要删除元素的值
    2. 再把前驱或后继元素删除
      非叶子节点的前驱或者后继元素,必定在叶子节点中,所以真正删除的元素在叶子节点中,因此,真正的删除元素都是发生在叶子节点中
  • 删除 - 叶子节点
    假如需要删除的元素在叶子节点中,那么直接删除即可。但是叶子节点被删掉一个元素后,元素个数可能会低于最低限制,这种现象称为下溢
删除 - 下溢的解决
  • 下溢节点的元素数量必然等于⌈m/2⌉ - 2
  • 如果下溢节点临近的兄弟节点,有至少⌈m/2⌉个元素,可以向其借一个元素
    • 将父节点的元素b插入到下溢节点的0位置(最小位置)
    • 用兄弟节点的元素a(最大的元素)替代父节点的元素b
    • 这种操作可以称为:旋转


      Snip20200901_20.png
Snip20200901_21.png
  • 如果下溢节点临近的兄弟节点,只有⌈m/2⌉-1个元素
    • 将父节点的元素b挪下来跟左右子节点进行合并
    • 合并后的节点元素个数等于⌈m/2⌉ + ⌈m/2⌉ - 2,不超过m-1
    • 这个操作可能会导致父节点下溢,依然依照上述方法解决,下溢现象可能会一直往上传播。


      Snip20200901_22.png
Snip20200901_23.png

例如:
Snip20200901_24.png

十、红黑树

地址

AVL树 vs 红黑树

地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,332评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,930评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,204评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,348评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,356评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,447评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,862评论 3 394
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,516评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,710评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,518评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,582评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,295评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,848评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,881评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,121评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,737评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,280评论 2 341