本笔记来自 计算机程序的思维逻辑 系列文章
ArrayList
内部组成
-
Object[] elementData
,动态数组,存放数据 -
int size
,记录实际元素个数 -
int modCount
,记录修改次数 -
int DEFAULT_CAPACITY
,数组初始化默认分配空间为 10
可迭代接口
-
Iterable
表示可迭代; -
iterator()
返回一个实现了Iterator
接口的对象
public interface Iterable {
Iterator iterator();
}
迭代器接口
public interface Iterator {
boolean hasNext();
E next();
void remove();
}
ListIterator
扩展了Iterator
接口,增加向前遍历,返回索引,添加元素,修改元素等
迭代中删除元素
使用迭代器遍历,调用迭代器的remove
方法进行删除
必须先调用
next()
,使cursor
指向该元素再进行删除
实现接口
ArrayList
实现了Collection
RandomAccess
List
-
Collection
表示数据集合,数据间没有位置或顺序的概念 -
RandomAccess
表示可以随机访问 -
List
表示有顺序或位置的数据集合
LinkedList
内部组成
由长度、头节点和尾节点组成双向链表
节点
private static class Node {
E item;
Node prev;
Node next;
Node(Node prev, E element, Node next) {
this.item = element;
this.prev = prev;
this.next = next;
}
}
特点
- 按需分配内存
- 不可以随机访问,按照索引访问效率比较低,从头或尾顺着链表找,效率为
O(N/2)
- 按照内容查找,效率为
O(N)
- 在两端添加或删除元素,效率为
O(1)
- 在中间插入,删除元素,需定位,效率为
O(N)
,修改本身效率为O(1)
实现接口
实现了List
Queue
Deque
接口
队列 Queue
特点
先进先出
方法
add
offer
element
peek
remove
poll
注意
- 队列为空时,
element
和remove
会抛异常,而peek
和poll
会返回null
- 队列为满时,
add
会抛异常,而offer
会返回false
栈 Stack
特点
先进后出
方法
push
peek
pop
注意
- 栈为空时,
pop
会抛异常,peek
返回null
- 栈为满时,
push
会抛异常
双端队列 Deque
特点
扩展了Queue
,包含栈的操作方法,额外添加其他方法
方法
addFirst
addLast
getFirst
getLast
removeFirst
removeLast
offerFirst
offerLast
peekFirst
peekLast
pollFirst
pollLast
注意
- 队列为空时,
get
和remove
会抛异常,peek
和poll
会返回null
- 队列为满时,
add
会抛异常,offer
会返回false
迭代器descendingIterator
,从后往前遍历
HashMap
键值对结构,一个键映射一个值;键不能重复,即对同一个键重复操作会覆盖原来的值
内部组成
-
Node
,初始化为空数组[] table -
Set
,键值对集合> entrySet -
int size
,实际键值对的个数 -
int modCount
,记录修改次数 -
int threshold
,阈值,当键值对个数大于该值时才进行扩展,threshold = table.length * loadFactor
-
float loadFactor
,负载因子,表示table
被占用的程度,默认为 0.75
Map.Entry
interface Entry {
K getKey();
V getValue();
V setValue(V value);
}
HashMap.Node
内部类,HashMap
的核心元素
static class Node implements Map.Entry {
final int hash;
final K key;
V value;
Node next;
Node(int hash, K key, V value, Node next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
保存键值对
- 如果为空数组,则初始化,默认长度为 16
- 计算
key
的hash
值 - 再计算其存储位置
- 再判断该位置是否有值
- 有则判断
key
是否一样,是则修改其value
并返回oldValue
- 没有则直接插入该链表头部,返回
null
- 有则判断
HashSet
没有重复元素,无序的集合
内部组成
-
HashMap
,使用map map
的key
来存储元素 -
Object PRESENT
,map
的固定value
排序二叉树
特点
- 没有重复元素,有序
- 如果左子树不为空,则左子树所有节点都小于该节点
- 如果右子树不为空,则右子树所有节点都大于该节点
查找
- 跟根节点比较,相同则找到
- 小于根节点,则到左子树递归查找
- 大于根节点,则到右子树递归查找
最值
最左边是最小值,最右边是最大值
遍历
递归
访问左子树,访问当前节点,访问右子树
不递归
先找到最左边节点,然后依次找后继节点
后继节点查找方式
- 如果该节点有右孩子,则后继节点为右子树中最小的节点
- 如果该节点没有右孩子,则后继节点为父节点或某个祖先节点
- 从当前节点往上找,如果它是父节点的右孩子,则继续找父节点
- 直到它不是右孩子或父节点为空,第一个非右孩子节点的父节点就是后继节点
- 如果找不到,则后继节点为空,遍历结束
插入
- 首先查找插入位置,即插入节点的父节点,从根节点开始找
- 与当前节点相同,说明已存在,不能再插入
- 小于当前节点,则到左子树查找,如果左子树为空,则当前节点为插入节点的父节点
- 大于当前节点,则到右子树查找,如果右子树为空,则当前节点为插入节点的父节点
- 找到父节点后,如果插入元素小于父节点,则作为左孩子插入,反之作为右孩子插入
删除
- 节点为叶子节点
- 即没有孩子,直接删除,修改父节点的对应孩子为空
- 节点只有一个孩子
- 替换待删节点为孩子节点
- 节点有两个孩子
- 先找到该节点的后继节点(该节点为右子树的最小节点,没有左孩子),找到后,替换待删节点为后继节点,再删除后继节点(节点只有一个孩子的情况)
平衡二叉树
节点分布比较均匀,即为平衡
AVL树
- 任何节点的左右子树的高度差最多为一
- 实现方法:在每次的插入和删除操作时,通过一次或多次的旋转操作来重新平衡树
TreeMap
有序的Map
构造方法
- 空构造,要求
Map
中的键实现Comparable
接口 - 带
Comparator
参数的构造,传入一个comparator
对象,TreeMap
内部会用它比较元素
内部组成
-
Comparator super K> comparator
,没传则为null
-
Entry
,指向根节点root -
int size
,记录元素个数 -
int modCount
,记录修改次数
SortedMap 接口
public interface SortedMap extends Map {
Comparator super K> comparator();
SortedMap subMap(K fromKey, K toKey);
SortedMap headMap(K toKey);
SortedMap tailMap(K fromKey);
K firstKey();
K lastKey();
Set keySet();
Collection values();
Set> entrySet();
}
NavigableMap 接口
public interface NavigableMap extends SortedMap {
Map.Entry lowerEntry(K key);
K lowerKey(K key);
Map.Entry floorEntry(K key);
K floorKey(K key);
Map.Entry ceilingEntry(K key);
K ceilingKey(K key);
Map.Entry higherEntry(K key);
K higherKey(K key);
Map.Entry firstEntry();
Map.Entry lastEntry();
Map.Entry pollFirstEntry();
Map.Entry pollLastEntry();
NavigableMap descendingMap();
NavigableSet navigableKeySet();
NavigableSet descendingKeySet();
NavigableMap subMap(K fromKey, boolean fromInclusive,
K toKey, boolean toInclusive);
NavigableMap headMap(K toKey, boolean inclusive);
NavigableMap tailMap(K fromKey, boolean inclusive);
}
TreeMap.Entry
static final class Entry implements Map.Entry {
K key;
V value;
Entry left;
Entry right;
Entry parent;
boolean color = BLACK;
Entry(K key, V value, Entry parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
}
TreeSet
构造方法
- 空构造,要求元素实现
Comparable
接口 - 带
Comparator
参数的构造,内部使用它比较元素
内部组成
-
NavigableMap
,同m HashSet
,使用map
的key
存储元素 -
Object PRESENT
,同HashSet
,固定的value
内部操作
都是调用内部变量m
的对应方法
堆
概念上是完全二叉树,使用数组进行物理存储
满二叉树
除最后一层外,每个节点都有两个孩子,最后一层都是叶子节点,没有孩子
完全二叉树
不要求最后一层是满的,但如果不满,要求所有节点必须集中在最左边,从左到右是连续的,中间不能有空的
特点:在完全二叉树中,可以给每个节点一个编号,编号从1开始递增,从上到下,从左到右;当给定任意一个节点的编号,可以快速计算出其父节点和孩子节点编号
这个特点使得逻辑概念上的二叉树可以方便的存储到数组中
最大堆 最小堆
与排序二叉树不同,在堆中,可以有重复元素,元素间不是完全有序的,父子节点之间有一定的顺序要求
算法(最小堆)
添加元素
log2(N)
siftup
- 添加元素到最后位置
- 与父节点比较,如果大于等于父节点,则满足堆的性质,结束
- 否则与父节点交换,再与父节点比较,直到父节点为空或大于等于父节点
从头部删除元素
log2(N)
siftdown
- 用最后一个元素替换头部元素,并删除最后一个元素
- 将新的头部元素与俩孩子节点中较小的的比较,如果不大于该孩子节点,则满足堆的性质,结束
- 否则与该孩子节点交换,再与较小的孩子节点比较,直到没有孩子或者不大于两个孩子
从中间删除元素
- 还是用最后一个元素替换头部元素,并删除最后一个元素
- 然后判断,如果该元素大于某个孩子节点,则需向下调整,如果小于父节点,则需向上调整
构建初始堆
O(N)
heapify
- 从最后一个非叶子节点开始,一直往前直到根,对每个节点,执行向下调整
查找和遍历
O(N)
- 就是数组的查找和遍历
PriorityQueue
有优先级的队列
特点
- 长度没有限制,默认为 11
- 元素有优先级,队头的元素永远是优先级最高的
- 内部元素不是完全有序的,逐个出队会得到有序的输出
- 与
TreeMap
TreeSet
类似,要么元素实现Comparable
接口,要么传入一个Comparator
比较器
内部组成
-
Object[] queue
,存储元素的数组 -
int size
,记录元素个数 -
Comparator super E> comparator
,比较器 -
int modCount
,记录修改次数
ArrayDeque
使用数组存储的双端队列
内部组成
-
Object[] elements
,存储元素的数组 -
int head
,头部元素索引 -
int tail
,尾部元素索引
循环数组
- 如果
head
和tail
相同,则数组为空,长度为 0 - 如果
tail
大于head
,则第一个元素为elements[head]
,最后一个元素为elements[tail - 1]
,长度为tail - head
,元素索引从head
到tail - 1
- 如果
tail
小于head
,且为 0 ,则第一个元素为elements[head]
,最后一个元素为elements[elements.length - 1]
,元素索引从head
到elements.length - 1
- 如果
tail
小于head
,且大于 0 ,则会形成循环,第一个元素为elements[head]
,最后一个元素为elements[tail - 1]
,元素索引从head
到elements.length - 1
,然后再从0
到tail - 1
内部实现
- 数组的长度要严格大于实际存储元素的个数,为了让
head
或tail
指向下一个空位 - 数组长度为 2 的幂次方,方便计算
- 计算方式
- 下一个位置:
(tail + 1) & (elements.length - 1)
- 上一个位置:
(head - 1) & (elements.length - 1)
- 下一个位置:
LinkedHashMap
特点
支持两种顺序:插入顺序和访问顺序
内部组成
-
LinkedHashMap.Entry
,双向链表的头head -
LinkedHashMap.Entry
,双向链表的尾tail -
boolean accessOrder
,是否按访问顺序的标志
LinkedHashMap.Entry
-
before
:前一个节点 -
after
:后一个节点
static class Entry extends HashMap.Node {
Entry before, after;
Entry(int hash, K key, V value, Node next) {
super(hash, key, value, next);
}
}
访问顺序
访问指get
put
操作,对一个键执行get
put
操作后,其对应的键值对会移到链表末尾
构造方法
只有一个构造方法可以指定按访问顺序
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
用途
用于实现缓存,比如 LRU缓存
如果缓存满了,当需要存储新数据时,就需要一定的策略将一些老的数据清理,这个策略称为 替换算法
EnumMap
内部组成
-
Class
,keyType key
的类型 -
K[] keyUniverse
,存储所有key
-
Object[] vals
,存储所有的value
-
int size
,记录元素个数
实现原理
内部维护两个数组,长度相等,一个表示所有的键,一个表示对应的值,值为null
表示没有该键值对,键都有一个对应的索引ordinal
,根据索引可以很方便访问键和值
EnumSet
抽象类,如果枚举个数小于 64 ,则静态工厂方法创建的就是 RegularEnumSet
,大于则是JumboEnumSet
内部组成
-
Class
,元素类型elementType -
Enum>[] universe
,存储所有枚举值
RegularEnumSet
元素个数在 64 以内的集合
-
long elements
,位向量,使用一个long
类型的变量存储所有元素 -
int size()
,返回元素个数,方法实现:Long.bitCount(elements);
用一个位表示一个元素的状态,用一组位表示一个集合的状态,每个位对应一个元素,而状态只可能有两种
JumboEnumSet
元素个数在 64 以上的集合
-
long elements[]
,使用long
数组存储所有元素 -
int size
,记录元素个数
内部操作
- 都是利用位运算来实现的
- 根据枚举值找到索引,对对应的位进行 位或 ,位与 ,取反 操作
容器类
接口
- Collection
- List
- Map
- Set
- Queue
- Deque
抽象类
- AbstractCollection
- AbstractList
- AbstractMap
- AbstractSet
- AbstractQueue
- AbstractSequentialList
AbstractList 与 AbstractSequentialList 对比
-
AbstractList
需要具体子类重写根据索引操作的方法get
set
add
remove
,它提供了迭代器,但迭代器是基于这些方法实现的;它假定子类可以高效的根据索引位置进行操作,适用于内部是随机访问类型的存储结构(如数组),比如ArrayList
就继承自AbstractList
-
AbstractSequentialList
需要具体子类重写迭代器,它提供了根据索引操作的方法get
set
add
remove
,但这些方法是基于迭代器实现的;它适用于内部是顺序访问类型的存储结构(如链表),比如LinkedList
就继承自AbstractSequentialList
操作
查找
- 利用迭代器遍历进行查找
二分查找
- 要求
List
的每个元素实现Comparable
接口或提供一个Comparator
- 如果
List
可以随机访问,即实现RandomAccess
接口,或者元素个数比较少,则直接使用索引访问中间元素进行查找;否则使用迭代器的方式访问中间元素进行查找
查找元素出现个数
-
frequency
方法
查看两个集合是否有交集
-
disjoint
方法
排序
- 先将
List
元素拷贝到一个数组中,然后使用Arrays.sort
方法,排序后,再拷贝回List
交换元素位置
list.set(i, list.set(j, list.get(i)))
翻转列表顺序
- 如果
List
可以随机访问,将第一个和最后一个交换,第二个和倒数第二个交换,依此类推,直到中间两个元素交换完毕;否则,使用一前一后两个listIterator
定位待交换的元素
随机化重排
- 如果
List
可以随机访问,从后往前遍历列表,逐个给每个位置重新赋值,值从前面未重新赋值的元素中随机选取;否则,先将List
拷贝到一个数组中,重排后再拷贝回List
循环移位
- 使用
rotate(list, distance)
方法 -
distance
:正数表示右移,负数表示左移 - 可以看作列表的两个子列表进行顺序交换
- 根据列表长度和移位个数,计算出两个子列表的分割点,然后通过三次翻转得到最终结果