Algorithm 什么样的数据结构最能混合字典和列表的优点

Algorithm 什么样的数据结构最能混合字典和列表的优点,algorithm,data-structures,Algorithm,Data Structures,我最近问了一个问题,这让我想知道以下几点: 存储集合(键、值)对的数据结构是什么,以便: 它的元素是有序的 d[key]->val具有复杂性O(dict) d(索引)->(键,val)具有复杂性O(列表) 提供反向查找d{val}->(索引,键)的复杂性O(dict) 使用尽可能少的存储空间 当我键入O(type)时,我的意思是操作的复杂性与数据结构type相同 例如,如果有序集合为: c = {key_1:val_1, key_2:val_2, key_3:val_3} 我想获得 c[

我最近问了一个问题,这让我想知道以下几点:

存储集合(键、值)对的数据结构是什么,以便:

  • 它的元素是有序的
  • d[key]->val
    具有复杂性
    O(dict)
  • d(索引)->(键,val)
    具有复杂性
    O(列表)
  • 提供反向查找
    d{val}->(索引,键)
    的复杂性
    O(dict)
  • 使用尽可能少的存储空间
当我键入
O(type)
时,我的意思是操作的复杂性与数据结构
type
相同

例如,如果有序集合为:

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),它们的操作具有复杂性日志(n)

这总是一种折衷

您要求按键和索引进行O(1)查找,以及O(1)值查找。您可以通过维护键/值的哈希数据结构、反向查找的第二个哈希结构以及有序列表->键映射的列表数据结构来实现这一点。不过,您仍将有O(n)个插入和删除,而且您的空间复杂性将是正常情况下的3倍

如果你愿意在速度上妥协,你可以节省空间,有很多基于树的集合数据结构(例如Java中的TreeSet),它们的操作具有复杂性日志(n)


这始终是一种权衡

您没有提到插入成本,这也是一个重要的问题。您可以使用按词典顺序排列的词典来完成这项工作,并使用二进制搜索(即
log(n)
)来处理查找。但是,你需要维护两个这样的结构,一个是KEY-VALL,一个是Val->key,所以插入成本将是两倍,而在中间插入元素的需要使得在<代码> O(n)< /代码>(即,与列表相同)。

你没有提到插入成本,这也是一个重要的问题。您可以使用按词典顺序排列的词典来完成这项工作,并使用二进制搜索(即

log(n)
)来处理查找。但是,你需要维护两个这样的结构,一个是KEY-VALL,一个是Val->key,所以插入成本会增加一倍,而在中间插入元素的需要会导致在<代码> O(n)< /代码>(即,与列表相同)。

< P>我也遇到了同样的问题。因此,我获取了java.util.TreeMap的源代码并编写了IndexedTreeMap。它实现了我自己的索引导航地图

public interface IndexedNavigableMap<K, V> extends NavigableMap<K, V> {
   K exactKey(int index);
   Entry<K, V> exactEntry(int index);
   int keyIndex(K k);
}
public interface IndexedNavigableMap扩展了NavigableMap{
K精确键(int索引);
条目精确条目(int索引);
int键索引(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;
    }
  }
private void rotateLeft(条目p){
如果(p!=null){
条目r=右侧;
int delta=getWeight(左)-getWeight(右);
p、 右=右=左;
p、 更新视图(增量);
如果(右左!=null){
r、 left.parent=p;
}
r、 父母=父母;
如果(p.parent==null){
根=r;
}else if(p.parent.left==p){
delta=getWeight(r)-getWeight(p.parent.left);
p、 parent.left=r;
p、 parent.updatewight(delta);
}否则{
delta=getWeight(r)-getWeight(p.parent.right);
p、 parent.right=r;
p、 parent.updatewight(delta);
}
delta=重量(p)-重量(r.左);
r、 左=p;
r、 更新视图(增量);
p、 父代=r;
}
}
UpdateView只更新根目录下的权重:

   void updateWeight(int delta) {
        weight += delta;
        Entry<K, V> p = parent;
        while (p != null) {
            p.weight += delta;
            p = p.parent;
        }
    }
void updatewight(int delta){
重量+=增量;
条目p=父项;
while(p!=null){
p、 重量+=增量;
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 K exactKey(int索引){
如果(索引<0 | |索引>大小()-1){
将新的ArrayIndexOutOfBoundsException()抛出;
}
返回getExactKey(根,索引);
}
私有K getExactKey(条目e,int索引){
if(e.left==null&&index==0){
返回e.key;
}
if(e.left==null&&e.right==null){
返回e.key;
}
if(e.left!=null&&e.left.weight>index){
返回getExactKey(如左,索引);
}
if(e.left!=null&&e.left.weight==index){
返回e.key;
}
返回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;
}
public int-keyIndex(K键){
if(key==null){
抛出新的NullPointerException();
}
条目e=获取条目(键);
如果(e==null){
抛出新的NullPointerException();
}
if(e==根){
return getWeight(e)-getWeight(e.right)-1;//要返回的索引
}
int指数=0;
int-cmp;
如果(e.left!=null){
索引+=获取权重(如左);
}
条目p=e.parent;
//拆分比较器和可比较路径

Comparator我也有同样的问题,所以我使用java.util.TreeMap的源代码编写了IndexedTreeMap.I