问题描述
|
我最近问的一个问题使我对以下内容感到疑惑:
存储这样的集合(键,值)对的数据结构是什么:
其元素是有序的
d[key] -> val
具有复杂度O(dict)
d(index) -> (key,val)
具有复杂度O(list)
提供反向查找d{val} -> (index,key)
,复杂度O(dict)
使用最少的存储空间
当我键入“ 6”时,表示操作的复杂性与数据结构“ 7”相同。
例如,如果有序集合是:
c = {key_1:val_1,key_2:val_2,key_3:val_3}
我想获得
c[key_1] # returns val_1,as in a dictionary
c(1) # returns val_2,as in a list
c{val_3} # returns (2,key_3) as in a sort of \"indexed dictionary\"
解决方法
您正在通过键和索引以及O(1)值查询来请求O(1)查询。您可以通过维护键/值的哈希数据结构,反向查找的第二个哈希结构以及有序列表->键映射的列表数据结构来做到这一点。不过,您仍然会有O(n)个插入和删除,而且您的空间复杂度将是通常情况下的3倍。
如果您愿意牺牲速度,可以节省空间,那么有很多基于树的设置数据结构(例如Java中的TreeSet),其操作具有复杂度log(n)。
总是要权衡
, 您没有提到插入成本,这也是一个重要的问题。您可以使用按词法排序的字典来执行此操作,并使用二进制搜索(
log(n)
)处理查找。但是,您将需要维护两个这样的结构,一个结构为key-> val,一个结构为val-> key,因此插入成本将增加一倍,并且在中间插入元素的费用为O(n)
(即与列表相同)。
, 我有同样的问题。因此,我采用了java.util.TreeMap的源代码并编写了IndexedTreeMap。它实现了我自己的IndexedNavigableMap:
public interface IndexedNavigableMap<K,V> extends NavigableMap<K,V> {
K exactKey(int index);
Entry<K,V> exactEntry(int index);
int keyIndex(K k);
}
该实现基于更改时更改红黑树中的节点权重。权重是给定节点下的子节点数加上一个-self。例如,当树向左旋转时:
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
Entry<K,V> r = p.right;
int delta = getWeight(r.left) - getWeight(p.right);
p.right = r.left;
p.updateWeight(delta);
if (r.left != null) {
r.left.parent = p;
}
r.parent = p.parent;
if (p.parent == null) {
root = r;
} else if (p.parent.left == p) {
delta = getWeight(r) - getWeight(p.parent.left);
p.parent.left = r;
p.parent.updateWeight(delta);
} else {
delta = getWeight(r) - getWeight(p.parent.right);
p.parent.right = r;
p.parent.updateWeight(delta);
}
delta = getWeight(p) - getWeight(r.left);
r.left = p;
r.updateWeight(delta);
p.parent = r;
}
}
updateWeight只是将权重更新为根:
void updateWeight(int delta) {
weight += delta;
Entry<K,V> p = parent;
while (p != null) {
p.weight += delta;
p = p.parent;
}
}
当我们需要按索引查找元素时,这里是使用权重的实现:
public K exactKey(int index) {
if (index < 0 || index > size() - 1) {
throw new ArrayIndexOutOfBoundsException();
}
return getExactKey(root,index);
}
private K getExactKey(Entry<K,V> e,int index) {
if (e.left == null && index == 0) {
return e.key;
}
if (e.left == null && e.right == null) {
return e.key;
}
if (e.left != null && e.left.weight > index) {
return getExactKey(e.left,index);
}
if (e.left != null && e.left.weight == index) {
return e.key;
}
return getExactKey(e.right,index - (e.left == null ? 0 : e.left.weight) - 1);
}
查找键的索引也非常方便:
public int keyIndex(K key) {
if (key == null) {
throw new NullPointerException();
}
Entry<K,V> e = getEntry(key);
if (e == null) {
throw new NullPointerException();
}
if (e == root) {
return getWeight(e) - getWeight(e.right) - 1;//index to return
}
int index = 0;
int cmp;
if (e.left != null) {
index += getWeight(e.left);
}
Entry<K,V> p = e.parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
while (p != null) {
cmp = cpr.compare(key,p.key);
if (cmp > 0) {
index += getWeight(p.left) + 1;
}
p = p.parent;
}
} else {
Comparable<? super K> k = (Comparable<? super K>) key;
while (p != null) {
if (k.compareTo(p.key) > 0) {
index += getWeight(p.left) + 1;
}
p = p.parent;
}
}
return index;
}
您可以在http://code.google.com/p/indexed-tree-map/中找到这项工作的结果