数据结构:插入,删除,包含,获取随机元素,全部在O(1)

|| 采访中给了我这个问题。您将如何回答? 设计一个数据结构,以在O(1)时间内提供以下操作: 插 去掉 包含 获得随机元素     
已邀请:
考虑由哈希表H和数组A组成的数据结构。哈希表键是数据结构中的元素,值是它们在数组中的位置。 insert(value):将值附加到数组,并使其成为A中的索引。设置H [value] = i。 remove(value):我们将用A中的最后一个元素替换包含A中的value的单元格。令d为数组A中索引为m的最后一个元素。令我为H [值],即要删除的值数组中的索引。设置A [i] = d,H [d] = i,将数组的大小减小1,然后从H中删除值。 contains(value):返回H.contains(value) getRandomElement():让r = random(A的当前大小)。返回A [r]。 由于数组需要自动增加大小,因此将分摊O(1)来添加元素,但是我想这没关系。     
O(1)查找表示散列数据结构。 通过对比: 使用O(N)查找的O(1)插入/删除表示一个链表。 O(1)插入,O(N)删除和O(N)查找表示数组支持的列表 O(logN)插入/删除/查找表示树或堆。     
您可能不喜欢这样,因为他们可能正在寻找一个聪明的解决方案,但有时还是值得坚持……哈希表已经满足要求-总体上可能比其他任何方法都要好(尽管显然是摊销了)持续时间,并且对其他解决方案有不同的妥协)。 棘手的要求是选择“随机元素”:在哈希表中,您需要扫描或探查此类元素。 对于封闭式哈希/开放式寻址,任何给定存储桶被占用的机会为
size() / capacity()
,但至关重要的是,哈希表实现通常将其保持在恒定的乘法范围内(例如,该表可以比其当前内容大1.2) x至〜10x,具体取决于性能/内存调整)。这意味着我们平均可以搜索1.2到10个桶-完全独立于容器的总大小;摊销O(1)。 我可以想象出两种简单的方法(还有很多更巧妙的方法): 从随机桶中线性搜索 考虑空/值存储桶ala \“-AC ----- B--D \”:您可以说第一个“ random”选择是公平的,即使它偏爱B,因为B不再比其他元素更容易受到偏爱的可能性,但是如果您使用相同的值进行重复的“随机”选择,那么显然反复受到偏爱B可能是不希望的(尽管,问题中甚至没有要求概率) 反复尝试随机存储桶,直到找到一个填充的桶 “仅”访问的平均存储桶容量()/大小(如上所述)-但实际上更昂贵,因为随机数生成相对昂贵,并且如果无限不可能的最坏情况行为则无限坏... 更快的折衷方法是使用从初始随机选择的存储桶中预先生成的随机偏移量列表,将其%添加到存储桶计数中 这不是一个很好的解决方案,但是与始终保持第二个索引数组的内存和性能开销相比,这可能是一个更好的总体折衷方案。     
最好的解决方案可能是哈希表+数组,它真正快速且具有确定性。 但是评分最低的答案(仅使用哈希表!)实际上也很棒! 具有重新哈希或新的存储桶选择的哈希表(即每个存储桶一个元素,没有链表) getRandom()反复尝试选择一个随机存储桶,直到它为空。 作为一种故障保险,可能是getRandom(),在尝试了N次(元素数量)失败之后,在[0,N-1]中选择了一个随机索引i,然后线性遍历哈希表并选择了第#i个元素。 人们可能不会因为“可能的无限循环”而喜欢这种方法,而且我已经看到非常聪明的人也有这种反应,但这是错误的!绝对不可能发生的事件根本不会发生。 假设您的伪随机源的行为良好-对于这种特定行为,这并不难建立-并且哈希表始终至少已满20%,很容易看到: getRandom()绝不可能尝试超过1000次。只是从来没有。确实,此类事件的概率为0.8 ^ 1000,即10 ^ -97,因此我们必须重复执行10 ^ 88次,才能使十亿分之一的事件发生一次。即使该程序在人类所有计算机上全天候运行,直到太阳死了,这也永远不会发生。     
对于这个问题,我将使用两个数据结构 哈希图 ArrayList / Array / Double LinkedList。 脚步 :- 插入:-检查X是否已存在于HashMap中-时间复杂度O(1)。如果不存在,则添加到ArrayList的末尾-时间复杂度O(1)。 将其添加到HashMap中,将x添加为键,将最后一个Index添加为值-时间复杂度O(1)。 删除:-检查HashMap中是否存在X-时间复杂度O(1)。如果存在,则找到其索引并将其从HashMap-时间复杂度O(1)中删除。将此元素与ArrayList中的最后一个元素交换,并删除最后一个元素-时间复杂度O(1)。更新HashMap中的最后一个元素的索引-时间复杂度O(1)。 GetRandom:-生成从0到ArrayList的最后一个索引的随机数。在生成的随机索引处返回ArrayList元素-时间复杂度O(1)。 搜索:-在HashMap中查看x作为关键字。 -时间复杂度O(1)。 代码:-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;


public class JavaApplication1 {

    public static void main(String args[]){
       Scanner sc = new Scanner(System.in);
        ArrayList<Integer> al =new ArrayList<Integer>();
        HashMap<Integer,Integer> mp = new HashMap<Integer,Integer>();  
        while(true){
            System.out.println(\"**menu**\");
            System.out.println(\"1.insert\");
            System.out.println(\"2.remove\");
            System.out.println(\"3.search\");
            System.out.println(\"4.rendom\");
            int ch = sc.nextInt();
            switch(ch){
                case 1 : System.out.println(\"Enter the Element \");
                        int a = sc.nextInt();
                        if(mp.containsKey(a)){
                            System.out.println(\"Element is already present \");
                        }
                        else{
                            al.add(a);
                            mp.put(a, al.size()-1);

                        }
                        break;
                case 2 : System.out.println(\"Enter the Element Which u want to remove\");
                        a = sc.nextInt();
                        if(mp.containsKey(a)){

                            int size = al.size();
                            int index = mp.get(a);

                            int last = al.get(size-1);
                            Collections.swap(al, index,  size-1);

                            al.remove(size-1);
                            mp.put(last, index);

                            System.out.println(\"Data Deleted\");

                        }
                        else{
                            System.out.println(\"Data Not found\");
                        }
                        break;
                case 3 : System.out.println(\"Enter the Element to Search\");
                        a = sc.nextInt();
                        if(mp.containsKey(a)){
                            System.out.println(mp.get(a));
                        }
                        else{
                            System.out.println(\"Data Not Found\");
                        }
                        break;
                case 4 : Random rm = new Random();
                        int index = rm.nextInt(al.size());
                        System.out.println(al.get(index));
                        break;

            }
        }
    }

}
-时间复杂度O(1)。 -空间复杂度O(N)。     
这是针对该问题的C#解决方案,我前不久被问到相同的问题时想到了。它与其他标准.NET接口一起实现“添加”,“删除”,“包含”和“随机”。并不是说您在面试时就需要如此详细地实现它,但是有一个具体的解决方案可以很好地看一下...
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

/// <summary>
/// This class represents an unordered bag of items with the
/// the capability to get a random item.  All operations are O(1).
/// </summary>
/// <typeparam name=\"T\">The type of the item.</typeparam>
public class Bag<T> : ICollection<T>, IEnumerable<T>, ICollection, IEnumerable
{
    private Dictionary<T, int> index;
    private List<T> items;
    private Random rand;
    private object syncRoot;

    /// <summary>
    /// Initializes a new instance of the <see cref=\"Bag&lt;T&gt;\"/> class.
    /// </summary>
    public Bag()
        : this(0)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref=\"Bag&lt;T&gt;\"/> class.
    /// </summary>
    /// <param name=\"capacity\">The capacity.</param>
    public Bag(int capacity)
    {
        this.index = new Dictionary<T, int>(capacity);
        this.items = new List<T>(capacity);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref=\"Bag&lt;T&gt;\"/> class.
    /// </summary>
    /// <param name=\"collection\">The collection.</param>
    public Bag(IEnumerable<T> collection)
    {
        this.items = new List<T>(collection);
        this.index = this.items
            .Select((value, index) => new { value, index })
            .ToDictionary(pair => pair.value, pair => pair.index);
    }

    /// <summary>
    /// Get random item from bag.
    /// </summary>
    /// <returns>Random item from bag.</returns>
    /// <exception cref=\"System.InvalidOperationException\">
    /// The bag is empty.
    /// </exception>
    public T Random()
    {
        if (this.items.Count == 0)
        {
            throw new InvalidOperationException();
        }

        if (this.rand == null)
        {
            this.rand = new Random();
        }

        int randomIndex = this.rand.Next(0, this.items.Count);
        return this.items[randomIndex];
    }

    /// <summary>
    /// Adds the specified item.
    /// </summary>
    /// <param name=\"item\">The item.</param>
    public void Add(T item)
    {
        this.index.Add(item, this.items.Count);
        this.items.Add(item);
    }

    /// <summary>
    /// Removes the specified item.
    /// </summary>
    /// <param name=\"item\">The item.</param>
    /// <returns></returns>
    public bool Remove(T item)
    {
        // Replace index of value to remove with last item in values list
        int keyIndex = this.index[item];
        T lastItem = this.items[this.items.Count - 1];
        this.items[keyIndex] = lastItem;

        // Update index in dictionary for last item that was just moved
        this.index[lastItem] = keyIndex;

        // Remove old value
        this.index.Remove(item);
        this.items.RemoveAt(this.items.Count - 1);

        return true;
    }

    /// <inheritdoc />
    public bool Contains(T item)
    {
        return this.index.ContainsKey(item);
    }

    /// <inheritdoc />
    public void Clear()
    {
        this.index.Clear();
        this.items.Clear();
    }

    /// <inheritdoc />
    public int Count
    {
        get { return this.items.Count; }
    }

    /// <inheritdoc />
    public void CopyTo(T[] array, int arrayIndex)
    {
        this.items.CopyTo(array, arrayIndex);
    }

    /// <inheritdoc />
    public bool IsReadOnly
    {
        get { return false; }
    }

    /// <inheritdoc />
    public IEnumerator<T> GetEnumerator()
    {
        foreach (var value in this.items)
        {
            yield return value;
        }
    }

    /// <inheritdoc />
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    /// <inheritdoc />
    public void CopyTo(Array array, int index)
    {
        this.CopyTo(array as T[], index);
    }

    /// <inheritdoc />
    public bool IsSynchronized
    {
        get { return false; }
    }

    /// <inheritdoc />
    public object SyncRoot
    {
        get
        {
            if (this.syncRoot == null)
            {
                Interlocked.CompareExchange<object>(
                    ref this.syncRoot,
                    new object(),
                    null);
            }

            return this.syncRoot;

        }
    }
}
    
尽管这已经很老了,但是由于C ++中没有答案,所以这是我的两分钱。
#include <vector>
#include <unordered_map>
#include <stdlib.h>

template <typename T> class bucket{
    int size;
    std::vector<T> v;
    std::unordered_map<T, int> m;
public:
    bucket(){
        size = 0;
        std::vector<T>* v = new std::vector<T>();
        std::unordered_map<T, int>* m = new std::unordered_map<T, int>();
    }
    void insert(const T& item){
        //prevent insertion of duplicates
        if(m.find(item) != m.end()){
            exit(-1);
        }
        v.push_back(item);
        m.emplace(item, size);
        size++;

    }
    void remove(const T& item){
        //exits if the item is not present in the list
        if(m[item] == -1){
            exit(-1);
        }else if(m.find(item) == m.end()){
            exit(-1);
        }

        int idx = m[item];
        m[v.back()] = idx;
        T itm = v[idx];
        v.insert(v.begin()+idx, v.back());
        v.erase(v.begin()+idx+1);
        v.insert(v.begin()+size, itm);
        v.erase(v.begin()+size);
        m[item] = -1;
        v.pop_back();
        size--;

    }

     T& getRandom(){
      int idx = rand()%size;
      return v[idx];

     }

     bool lookup(const T& item){
       if(m.find(item) == m.end()) return false;
       return true;

     }
    //method to check that remove has worked
    void print(){
        for(auto it = v.begin(); it != v.end(); it++){
            std::cout<<*it<<\" \";
        }
    }
};
这是一段测试该解决方案的客户端代码。
int main() {

    bucket<char>* b = new bucket<char>();
    b->insert(\'d\');
    b->insert(\'k\');
    b->insert(\'l\');
    b->insert(\'h\');
    b->insert(\'j\');
    b->insert(\'z\');
    b->insert(\'p\');

    std::cout<<b->random()<<std::endl;
    b->print();
    std::cout<<std::endl;
    b->remove(\'h\');
    b->print();

    return 0;
}
    
在C#3.0 + .NET Framework 4中,通用ѭ5甚至比哈希表更好,因为您可以使用
System.Linq
扩展方法
ElementAt()
来索引存储
KeyValuePair<TKey,TValue>
元素的基础动态数组:
using System.Linq;

Random _generator = new Random((int)DateTime.Now.Ticks);

Dictionary<string,object> _elements = new Dictionary<string,object>();

....

Public object GetRandom()
{
     return _elements.ElementAt(_generator.Next(_elements.Count)).Value;
}
但是,据我所知,哈希表(或其字典后代)不是此问题的真正解决方案,因为Put()只能摊销O(1),而不是真正的O(1),因为它是O(N )在动态调整大小边界。 是否有解决此问题的真正方法?我能想到的就是,如果您指定的Dictionary / Hashtable初始容量超出预期的数量级,那么您将获得O(1)操作,因为您无需调整大小。     
我们可以使用散列来支持Θ(1)时间的运算。 插入(x) 1)通过进行哈希映射查找来检查x是否已经存在。 2)如果不存在,则将其插入数组的末尾。 3)还要在哈希表中添加x,作为键,最后一个数组索引作为索引。 删除(x) 1)通过进行哈希映射查找来检查x是否存在。 2)如果存在,则找到其索引并将其从哈希映射中删除。 3)将最后一个元素与此数组中的元素交换,然后删除最后一个元素。 进行交换是因为可以在O(1)时间内删除最后一个元素。 4)更新哈希图中最后一个元素的索引。 getRandom() 1)生成一个从0到最后一个索引的随机数。 2)将数组元素返回到随机生成的索引处。 搜索(x) 在哈希图中查找x。     
我同意阿农。除了最后一个要求获取具有相等公平性的随机元素的要求以外,所有其他要求只能使用单个基于哈希的DS来解决。我将在Java中为此选择HashSet。元素的哈希码取模将给我O(1)时间中基础数组的索引号。我可以将其用于添加,删除和包含操作。     
不能使用Java的HashSet做到这一点吗?默认情况下,它在O(1)中提供插入,删除和搜索。 对于getRandom,我们可以使用Set的迭代器,该迭代器始终会产生随机行为。我们可以从集合中迭代第一个元素,而不必担心其余元素
public void getRandom(){
    Iterator<integer> sitr = s.iterator();
    Integer x = sitr.next();    
    return x;
}
    
/* Java program to design a data structure that support folloiwng operations
   in Theta(n) time
   a) Insert
   b) Delete
   c) Search
   d) getRandom */
import java.util.*;

// class to represent the required data structure
class MyDS
{
   ArrayList<Integer> arr;   // A resizable array

   // A hash where keys are array elements and vlaues are
   // indexes in arr[]
   HashMap<Integer, Integer>  hash;

   // Constructor (creates arr[] and hash)
   public MyDS()
   {
       arr = new ArrayList<Integer>();
       hash = new HashMap<Integer, Integer>();
   }

   // A Theta(1) function to add an element to MyDS
   // data structure
   void add(int x)
   {
      // If ekement is already present, then noting to do
      if (hash.get(x) != null)
          return;

      // Else put element at the end of arr[]
      int s = arr.size();
      arr.add(x);

      // And put in hash also
      hash.put(x, s);
   }

   // A Theta(1) function to remove an element from MyDS
   // data structure
   void remove(int x)
   {
       // Check if element is present
       Integer index = hash.get(x);
       if (index == null)
          return;

       // If present, then remove element from hash
       hash.remove(x);

       // Swap element with last element so that remove from
       // arr[] can be done in O(1) time
       int size = arr.size();
       Integer last = arr.get(size-1);
       Collections.swap(arr, index,  size-1);

       // Remove last element (This is O(1))
       arr.remove(size-1);

       // Update hash table for new index of last element
       hash.put(last, index);
    }

    // Returns a random element from MyDS
    int getRandom()
    {
       // Find a random index from 0 to size - 1
       Random rand = new Random();  // Choose a different seed
       int index = rand.nextInt(arr.size());

       // Return element at randomly picked index
       return arr.get(index);
    }

    // Returns index of element if element is present, otherwise null
    Integer search(int x)
    {
       return hash.get(x);
    }
}

// Driver class
class Main
{
    public static void main (String[] args)
    {
        MyDS ds = new MyDS();
        ds.add(10);
        ds.add(20);
        ds.add(30);
        ds.add(40);
        System.out.println(ds.search(30));
        ds.remove(20);
        ds.add(50);
        System.out.println(ds.search(50));
        System.out.println(ds.getRandom());`enter code here`
    }
}
    
我认为我们可以将双链表与哈希表一起使用。键将是元素,其关联值将是双链表中的节点。 insert(H,E):在双链表中插入节点,并以H [E] = node的形式输入; O(1) delete(H,E):通过H(E)获取节点地址,转到该节点的前一个,然后删除并将H(E)设置为NULL,因此O(1) contains(H,E)和getRandom(H)显然是O(1)     

要回复问题请先登录注册