O(1)复杂度的实现
实现O(1)复杂度要求:
- 存储单元中存储的内容与存储地址是一一对应的
- 存储单元是可以随机读取的(任意顺序直接读取–数组)
public class CapableTest {
private Object[] values=new Object[10];
public void add(int item) {
values[item]=item;
}
public void remove(int item) {
values[item]=null;
}
public boolean contain(int item) {
if(values[item]==null) {
return false;
}else {
return true;
}
}
}
以上是一个简易的O(1)复杂度的容器。
我们不再把values[1]称作存储单元,操作系统中的存储单元为一个字节,而int为4个字节,我们把values[1],values[2]称作一个槽。
如果需要将20个数据放入10个槽中,将会发生数据碰撞。解决数据碰撞的办法可以用链接法,就是两个数据共用一个地址,但是在不同的链表节点。也就是将槽拓展为链表。
链接法处理碰撞
public class CapableTest2 {
private Object[] values=new Object[10];
private int H(int num) {
if(num>0 && num<=9) {
return num;
}else {
return num-10;
}
}
public void add(int item) {
if(values[H(item)]==null) {
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.add(item);
values[H(item)]=linkedList;
}else {
@SuppressWarnings("unchecked")
LinkedList<Integer> linkedList=(LinkedList<Integer>)values[H(item)];
linkedList.add(item);
}
}
public void remove(int item) {
@SuppressWarnings("unchecked")
LinkedList<Integer> linkedList=(LinkedList<Integer>) values[H(item)];
linkedList.remove((int)item);
}
public boolean contain(int item) {
if(values[H(item)]==null) {
return false;
}else {
@SuppressWarnings("unchecked")
LinkedList<Integer> linkedList=(LinkedList<Integer>) values[H(item)];
return linkedList.contains(item);
}
}
}
这样就实现了一个槽存放多个元素
如何让21亿人使用10个地址?
好吧,有了链接法,我们有了足够的房子以应对可能发生的碰撞。但是我们仍然希望碰撞发生的几率越小越好,特别是当我们把数值范围由 0~19 扩大到 0~int.MaxValue 时候。有什么办法能把21亿个数值映射成10个数值,并且尽量减少碰撞?
除法散列法
h(k) = k mod m
其中,k为槽中的数值,m是数组的大小(为了简单起见本例中固定为10)。这样我们得到第一个正整数范围内通用的 IntSet
public class IntSet
{
private object[] _values = new object[10];
private int H(int value)
{
return value % 10;
}
// 其它部分与 之前 相同
}
再大的数除于10的余数都一定介于0-9之间。
其次让h(k)得出1的数值为1,11,21,31…等。碰撞的K值比较分散(我们想要尽量减少碰撞)
需要注意的是 m 不应是 2 的幂即 2p 的形式,此时 h(k) 将等于 k 的二进制的最低 p 位。以 m = 23和k=170为例
h(k) = 170 mod 23 = (27+ 25 + 23 + 22 + 21 ) mod 23 = 21>
也就是说只有最低的 p 位不能被 2p 整除。这有什么问题呢?问题是我们不想假设 k 的分布,所以通常希望 h(k) 的值依赖于 k 的所有位而不是最低 p 位。天知道 k 不会是“11010000、00110000、10010000……”
乘法散列法
h(k) = ⌊m(kA mod 1)⌋
其中,A 是一个大于0小于1的常数,例如可以取 A = 2654435769 / 232。kA mod 1 的意思是取 kA 的小数部分。