目录
一、集合常用属性对比表
集合 | 默认长度 | 容量增长方式 | 数据结构 | 空值null | 线程安全 |
ArrayList | 10 | 增长为当前容量的一半 | 数组 | 重复允许 | 不安全 |
LinkedList | 0 | 与新增数目一致 | 链表 | 重复允许 | 不安全 |
Vector | 10 | 可自定义增长长度 若自定义增长为0或负数 则每次增长一倍 | 数组 | 重复允许 | 安全 |
PriorityQueue | 11 | 低于64每次增长2 否则每次增长一半 | 构造堆队列 | 不允许 | 不安全 |
ArrayDeque | 16 | 给程序计算时 最小容量为8 每次为当前值的最小幂 | 双向循环 数组 | 不允许 | 不安全 |
HashMap | 16 | 每次增长容量 为原来的一倍 | 哈希数组 +链表+红黑树 | 允许 | 不安全 |
HashSet | 16 | 每次增长容量 为原来的一倍 | 哈希数组 +链表+红黑树 | 允许 | 不安全 |
LinkedHashMap | 16 | 每次增长容量 为原来的一倍 | 哈希数组 +链表+红黑树+记录插入顺序的链表 | 允许 | 不安全 |
TreeMap | 0 | 与新增数目一致 | 红黑树 | 允许 | 不安全 |
二、PriorityQueue之构造堆
堆是一种物理存储结构为数组,但逻辑存储结构为完全二叉树的一种数据结构。而PriorityQueue队列只需要保证第一位数是最大的或者是最小的,因此实现此功能采用的是构造堆,构造堆和堆排序的不同点是构造堆只保证根节点是最小的或者最大的,堆排序则保证队列中是升序或者降序的,而队列只需要保证第一个进去或出来的值有序,因此只需要采用构造堆。
特性:
- 右子节点:(n * 2) + 1;
- 左子节点:(n * 2) + 2;
- 父节点:(n - 1) / 2 ;
- 当前节点一定小于(或大于)该树上所有子节点,堆树的根节点一定是最小(或最大)的值;
- 比较交换或删除增加时,只需要比较当前路径上的节点即可,不会对其他节点造成影响;
- 每次增加节点默认放到数组的最后面,至于具体位置放哪儿,只需判断该路径节点大小即可。
数据结构示意图:
从乱序数组中建立构造堆步骤:
- 遍历[size - 1,0]区间位置的数据;
- 如有comparator则调用使用comparator比较的方法,否则调用元素自身的comparaTo方法;
- 若遍历位置小于size/2则开始循环;
- 判断左右子节点哪个节点较小(或较大),进行大小和位置索引值k交换。
从原本的升序(或降序)排序堆中添加索引值k为10,x值为0的节点:
- 判断k是否大于0,大于则开始循环;
- 获得k位置的父节点,若父节点大于(或小于)k节点则将父节点设置为k位置的值,并且k索引与父节点索引交换;
- 继续轮回索引直到k等于0,将k设置到队列第一个值。
从原本的升序(或降序)排序堆中删除堆顶值:
- 将size减一,并获得size - 1位置的值,同时使size - 1位置为null;
- 从0号位置开始依次遍历到size/2位置;
- 从子节点中找到较小的一个值和位置索引,并将其赋值到K位置;
三、ArrayDeque之双向循环数组
ArrayDeque使用此数据结构来实现队列、栈和数组的功能,使用二进制位来巧妙地实现长度、边界判断和扩容判断等,当往头部插入时head就一直减一,运用二进制位的特殊情况省去了小于0判断和长度判断,从而可以直接在数组中循环插入。
特性:
- head永远是从右往左顺移,当继续顺移为-1位置时则从数组末尾开始顺移;
- tail永远是从左往右顺移,当顺移为size+1时则从头开始;
- 当head和tail两个索引值相等时则代表数组容量已满;
- 当删除某一位置时,最坏情况会调用copy函数两次;
- head索引的当前位置一定有值,tail索引位置的上一位置一定有值,而当前位置为null。
数据结构示意图:
四、HashMap之哈希表+链表+红黑树
HashMap的实现方法采用的是哈希表+链表+红黑树组合的数据结构,使用链表和红黑树来处理哈希冲突。当哈希冲突较少时,直接使用链表,当哈希冲突较多时,将链表转化成红黑树。
特性:
- 无哈希冲突时,最好的结果是哈希表刚好存储每一位的值;
- 哈希冲突少于8时,冲突的值在后面以链表的形式存在;
- 当哈希冲突大于等于8时,冲突的链表将转换成红黑树,以提高搜索效率。
数据结构示意图:
红黑树特征:
- 节点只能是红色或黑色;
- 根节点颜色为黑色;
- 每个叶子节点都是黑色的空节点;
- 每个红色节点的子节点一定是黑色;
- 从任意的节点到该任意节点的每个叶子节点路径都有相同数量的黑色节点,且任意节点的左右子树深度差不会超过2倍;
- 插入时的节点一定都是红色,后续经过变色后才会变成黑色或不变色;
左旋:当新插入的节点为子右节点,父节点是红色,并且父节点的兄弟节点为空或为黑色,则进行左旋。左旋图示如下:
右旋:当新插入的节点为子左节点,父节点是红色,并且父节点的兄弟节点为空或为黑色,则进行右旋。右旋图示如下:
变色:当新插入的节点为p的子节点时,若p节点是红色,且p节点的兄弟节点是红色,则进行变色。变色图示如下:
插入的旋转除了上面三种较为常见的情况外还有一种情况,即左旋和右旋的前置旋转,前提是父节点的兄弟节点为空或者为黑色,才能进行。下图将插入节点3右旋前置旋转后就可以对得到的结果进行左旋了:
下列一系列图为{1, 2, 3, 4, 5, 6, 7 , 8, 9, 10}十位数依次添加进红黑树的图解:
1.添加1和2:
2.添加3:
3.添加4:
4.添加5:
5.添加6:
6.添加7:
7.添加8:
8.添加9:
9.添加10:
接下来讲解一下红黑树的删除,删除相较于插入平衡来说更为复杂,因为插入只会插入到叶子节点,而删除可以删除任意节点。删除用到的操作也是左旋、右旋和变色,只是条件稍微不一样。
删除时共有下列三种情况:
- 如果X没有孩子,且如果X是红色,直接删除X;如果X是黑色,则以X为当前节点进行旋转调色,最后删掉X;
- 如果X只有一个孩子C,交换X和C的数值,再对新X进行删除。根据红黑树特性,此时X不可能为红色,因为红色节点要么没有孩子,要么有两个黑孩子。此时以新X为当前节点进行情况1的判断;
- 如果X有两个孩子,则从后继中找到最小节点D,交换X和D的数值,再对新X进行删除。此时以新X为当前节点进行情况1或2的判断。
此三种情况的转换关系图:
删除之前会进行平衡操作,先确定下图中各个节点所代表的的意思:
- X代表当前节点;
- P代表父节点;
- B代表兄弟节点,视情况,可有可无;
- L代表兄弟节点的左子节点,视情况,可有可无;
- R代表兄弟节点的右子节点,视情况,可有可无;
- 前驱:该节点左子树中最大的节点;
- 后继:该节点右子树中最小的节点。
我们可以使用上图来囊括平衡的情况,其左右可互换,原理一样:
- X没有子节点,且X为红色,则删除前形成上图;
- X没有子节点,且X为黑色,若B为红色,则L、R为黑色;B为黑色,则P颜色不限,L、R不存在。此两种情况可形成上图;
- 若X有子节点,那么X必定为黑色,否为红色则违反了红黑树特征,X的子节点一定为左子节点,将X和左子节点值互换,再将X删除,X的左子节点就成了新X,也形成了上图情形
有了上图就可以很清楚的知道大致树平衡的时候大致是什么情况,接下来一一分析:
1.X是根或者X是红色,则直接将X设为黑色;
2.X为叶子节点,且为红色,则直接删除X;
3.X是叶子节点,且X为黑色,则直接将父节点变黑色,且兄弟节点变成红色;
4.X只有一个左子节点或右子节点,此情况X只能是黑色,并且子节点只能是红色,否则违背红黑树特征。此情况只需要将X的子节点变成黑色,和子节点互换再删除X节点即可;
5.X为叶子节点,B为黑色节点,L、R若其中没有一个为红色(可以为空),则直接将B变红色,此操作无关乎P点颜色,变色后和情况6相符;
6.X为叶子节点,且P节点为黑色,B节点为红色,L、R均为黑色,此时只需要将P节点进行左旋即可,左旋后P、X、L符合情况3;
7.X节点为叶子节点,P任何颜色都行,B为黑色,L节点为红色,此时需要将B节点进行右旋,节点结构编程情况8;
8.X为叶子节点,P任意颜色,B为黑色,L节点可有可无,但颜色为红色,R为红色,此时对P进行左旋,同时P和B颜色互换,此时便可删除X节点,使树达到平衡。
总得来说,红黑树最重要的约束便是4和5,平衡时做的左旋、右旋和变色基本都是为了满足此两个条件。当插入且树平衡时,可以把左右子树的黑色节点都设为N,当在左子树插入一个红色时且父节点不为红色时,N不变,若父节点为红色,则只需要进行左旋或右旋,并且保持N不变,即可完成插入操作,相对删除而言简单一些;而当删除且树平衡时,删除节点为红色,N保持不变,可直接删除,若右子树删除节点为黑色,那么右子树N-1<左子树N,需要往右边添加黑色节点或在左子树减少黑色节点,因此需要进行变色和右旋。若在左子树进行类似的删除节点,原理也是和右子树一致。
五、TreeMap之序列集合转变为红黑树
如先存在SortedMap实现类,其遍历顺序为{1,2,3,4,5,6,7,8,9,10},现在要将其集合构造成红黑树,具体流程如下图:
如上图所示,通过二分法先将顺序集合分段,如从层数最大的分起为:{4},{3,4},{1},{1,2},{1,2,3,4},{1,2,3,4,5},{7},{6,7},{6,7,8},{10},{9,10},{6,7,8,9,10},{1,2,3,4,5,6,7,8,9,10}。但是从树的构建顺序来说就是从第一个数依次读到最后一个数,并先构建树的右节点,再构建树的mid节点,最后构建树的右节点。其遍历方法代码大致如下:
private Integer buildFromSorted(int level, int lo, int hi){
if (hi < lo)
return null;
int mid = (lo + hi) >>> 1;
Integer left = null;
if (lo < mid) {
left = buildFromSorted(level+1, lo, mid - 1);
}
// TODO 此处构建值
if (mid < hi) {
Integer right = buildFromSorted(level+1, mid+1, hi);
}
return mid;
}
通过此方法构建序列集合后的红黑树如下图: