红黑树
- 左旋右旋速记口令:
父与左子为右旋,
旋完子右变父左,
其他都不变。
父与右子为左旋,
旋完子左变父右,
其他都不变。 - 红黑树之插入
将新插入结点染为红色
WHILE若新插入点不为null,不是根结点,并且其父结点颜色为红色
IF若当前结点的父结点是当前结点的爷爷结点的左子结点
IF若当前结点的叔叔结点为红
将当前结点的父结点染为黑
将叔叔结点染为黑
将爷爷结点染为红
将爷爷结点置为当前结点
ELSE若当前结点的叔叔结点颜色为黑或NULL
若当前结点为右子结点
将父结点置为当前结点
左旋当前结点
将当前结点的父结点染为黑
将当前结点的爷爷结点染为红
右旋当前结点的爷爷结点
ELSE对称…
将根结点染为黑 - 红黑树之删除(按照Java中TreeMap的逻辑进行书写,顺序为由上至下):
若要删除的结点左右子结点均存在:
寻找右子结点往下的最后的左子结点,
以该结点作为后继结点(即下文中的当前结点)。
若要删除的结点只存在唯一结点,
则以唯一结点作为后继结点。
若要删除的结点为黑色,则需对该树进行修复操作。
修复:
WHILE若当前结点为黑色,且不是根结点:
IF若当前结点为左子结点:
若当前结点的兄弟结点为红色
将兄弟结点染为黑色,将父结点染为红色,左旋父结点。
IF若当前节点的兄弟结点有左右两个子结点,并且均为黑色
将兄弟结点染为红色,将当前结点的父结点置为当前结点。
ELSE若当前结点的兄弟结点只有右子结点为黑
将兄弟结点的左子结点染为黑色,将兄弟结点染为红色,右旋兄弟结点。
将当前兄弟结点染为父节点的颜色,将父节点染为黑色,将兄弟结点的右子结点染为黑色,
左旋父节点,将根节点赋值给当前结点。
ELSE对称。。。
将当前结点染为黑色。
时间复杂度
常数阶O(1)<
对数阶O(log2n)<
线性阶O(n)<
线性对数阶O(nlog2n)<
平方阶O(n2)`<`k次方阶O(nk)
<
指数阶O(2n)`<`O(n!)`<`O(nn)
HashMap
从结构实现来讲,HashMap是数组+链表+红黑树实现的,HashMap底层是由数组组成Node[],哈希桶数组,Node是HashMap的一个内部类,实现了Map.Entry接口,该类中有几个重要字段:hash字段,key字段,value字段,next字段,在往HashMap中存储键值对时,过程大概是这样的:
- 判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容
- 根据键key计算hash值,得到插入的数组索引i,如果table[i] == null,直接新建结点添加,转向6,若果table[i]不为空,转向3;
- 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向4,这里的相同指的是hashCode以及equals;
- 判断table[i]是否为treeNode,即table[i]是否是红黑树,如果是红黑树,则直接在树种插入键值对,否则转向5;
- 遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
- 插入成功后,判断实际存在的键值对数量size是否超过了最大容量threshold,如果超过,进行扩容。
- HashMap默认初始长度length为16,Load factor为负载因子(默认值是0.75),threshold是HashMap所能容纳的最大数据了的Node个数。threshold = length * Load factor(默认为12)。若实际存在的键值对数量超过了12,就会进行一次扩容,扩容大小为原来的一倍,也就是扩大到了32.
- 为什么初始长度会设置为16呢?以及为什么哈希桶数组的长度为什么必须是2的次幂呢?(程序中有处理,若开发人员手动传入的长度不为2的次幂,也会将其转换为大于这个数,且离这个数最近的2的次幂)主要是为了在取模和扩容时做优化,同时为了减少冲突,HashMap定位哈希桶索引位置时,也加入了高位参与运算的过程,当开发人员存储一个key,value的键值对进入HashMap时,首先会将key值转为二进制的形式,然后和length-1(默认值即15二进制为1111)进行&运算,得到hash值假设为5,创建对应的Node对象后存储到数组索引为5的地方。
- HashMap进行扩容resize时,会伴随着rehash的过程,而这个过程比较耗费性能,因此若能预知该HashMap需要存储的数据量,最好给HashMap指定初始值。(指定初始值时需考虑其threshold的值).
ArrayList
- ArrayList默认容量为10,若容量满时,会扩充为原来的1.5倍,若扩充后不是整数,则取整数部分。因为ArrayList每次扩容都会伴随着深拷贝的发生,因此若能预知该ArrayList需要存储的数据量,最好给ArrayList指定初始值
List
- ArrayList 采用索引(随机访问方式)遍历效率最高
- LinkedList 采用for增强循环遍历效率最高,采用索引遍历效率最低,不能用!
- Vector 采用索引(随机访问方式)遍历效率最高,若容量满时,容量大小会增加一倍
- Stack(栈)继承于Vector
- 实际使用的时候, 若需批量插入数据,可以使用LinkedList, 若数据全部插入后,在另外一个地方要对数据进行遍历, 可通过
new ArrayList<>(linkedList)
将LinkedList的引用对象转为ArrayList之后进行遍历 - 在JDK8中,若是小数据量的插入LinkedList快,若是大数据量的插入ArrayList快