web-dev-qa-db-fra.com

Structure de données: insérer, supprimer, contenir, obtenir un élément aléatoire, le tout à O(1)

J'ai eu ce problème en entrevue. Comment auriez-vous répondu?

Concevez une structure de données qui offre les opérations suivantes dans O(1):

  • insérer
  • retirer
  • contient
  • obtenir un élément aléatoire
83
guildner

Considérons une structure de données composée d'une table de hachage H et d'un tableau A. Les clés de table de hachage sont les éléments de la structure de données et les valeurs sont leurs positions dans le tableau.

  1. insert (value): ajoute la valeur à array et laisse i son index dans A. Définissez H [value] = i.
  2. remove (valeur): Nous allons remplacer la cellule qui contient la valeur dans A par le dernier élément de A. Soit d le dernier élément du tableau A à l’indice m. que je sois H [valeur], l'index dans le tableau de la valeur à supprimer. Définissez A [i] = d, H [d] = i, diminuez la taille du tableau de un et supprimez la valeur de H.
  3. contient (valeur): retourne H.contains (valeur)
  4. getRandomElement (): let r = random (taille actuelle de A). retourne A [r].

comme le tableau doit augmenter automatiquement en taille, il va falloir amortir O(1) pour ajouter un élément, mais je suppose que ça va.

126
r0u1i

La recherche O (1) implique une structure de données hachée .

Par comparaison:

  • O (1) insérer/supprimer avec O(N) recherche implique une liste chaînée.
  • O (1) insérer, O(N) supprimer et O(N) rechercher implique une liste sauvegardée par tableau
  • O (logN) insérer/supprimer/rechercher implique un arbre ou un tas.
19
Anon

Cela ne vous plaira peut-être pas, car ils recherchent probablement une solution intelligente, mais il est parfois avantageux de s'en tenir à leurs armes ... A La table de hachage répond déjà aux exigences - probablement globalement mieux que tout le reste évidemment en temps constant amorti et avec des compromis différents d’autres solutions).

L'exigence qui est délicate est la sélection "élément aléatoire": dans une table de hachage, vous devrez analyser ou rechercher un tel élément.

Pour un adressage en hachage fermé/ouvert, la possibilité d'occuper un compartiment donné est size() / capacity(), mais elle est généralement conservée dans une plage multiplicative constante par une implémentation de table de hachage (par exemple, la table peut être plus grande que son contenu actuel, par exemple. 1.2 x à ~ 10x selon les performances/le réglage de la mémoire). Cela signifie en moyenne que nous pouvons nous attendre à rechercher entre 1,2 et 10 seaux - totalement indépendants de la taille totale du conteneur; amorti O (1).

Je peux imaginer deux approches simples (et beaucoup plus délicates):

  • rechercher linéairement à partir d'un seau aléatoire

    • considérez les seaux vides/porteurs de valeur ala "--AC ----- B - D": vous pouvez dire que la première sélection "aléatoire" est juste même si elle favorise B, car B n'en a plus la probabilité d'être favorisé par rapport aux autres éléments, mais si vous faites des sélections répétées "aléatoires" en utilisant les mêmes valeurs, il peut être indésirable que B soit constamment favorisé (rien dans la question n'exige même des probabilités)
  • essayez plusieurs fois des seaux aléatoires jusqu'à ce que vous en trouviez un rempli

    • "uniquement" contenance ()/taille () moyenne compartiments visités (comme ci-dessus) - mais, dans la pratique, plus onéreuse, car la génération de nombres aléatoires est relativement coûteuse et infiniment mauvaise si le comportement dans le pire des cas est infiniment improbable ...
      • un compromis plus rapide consisterait à utiliser une liste de décalages aléatoires pré-générés à partir du compartiment initial sélectionné de manière aléatoire, en les pondérant dans le nombre de compartiments.

Pas une bonne solution, mais peut-être un meilleur compromis que les frais de mémoire et de performance liés au maintien d’un second tableau d’index à tout moment.

4
Tony Delroy

La meilleure solution est probablement la table de hachage + tableau, c'est très rapide et déterministe.

Mais la réponse la moins bien notée (utilisez simplement une table de hachage!) Est également excellente!

  • table de hachage avec ré-hachage ou nouvelle sélection de compartiment (c'est-à-dire un élément par compartiment, aucune liste chaînée)
  • getRandom () tente à plusieurs reprises de choisir un compartiment aléatoire jusqu'à ce qu'il soit vide.
  • en tant que sécurité, getRandom (), après N (nombre d'éléments) sans succès, sélectionne un index aléatoire i dans [0, N-1], puis parcourt la table de hachage de manière linéaire, puis le # i-ème élément .

Les gens pourraient ne pas aimer cela à cause de "boucles infinies possibles", et j'ai vu des gens très intelligents réagir aussi, mais c'est faux! Des événements infiniment improbables ne font que not ​​ne se produisent pas.

En supposant le bon comportement de votre source pseudo-aléatoire - ce qui n'est pas difficile à établir pour ce comportement particulier - et que les tables de hachage sont toujours remplies à au moins 20%, il est facile de voir que:

Il arrivera jamais que getRandom () doive essayer plus de 1000 fois. Juste jamais. En effet, la probabilité d’un tel événement est de 0,8 ^ 1 000, ce qui correspond à 10 ^ -97 - nous devrions donc le répéter 10 fois sur 88 pour avoir une chance sur un milliard de se produire une fois. Même si ce programme fonctionnait à plein temps sur tous les ordinateurs de l’humanité jusqu’à la mort du Soleil, cela se produirait jamais.

3
user1147505

Pour cette question, je vais utiliser deux structures de données 

  • HashMap 
  • ArrayList/Array/Double LinkedList.

Pas :-

  1. Insertion: - Vérifiez si X est déjà présent dans HashMap --Time complexité O(1). S'il n'est pas présent, puis ajoutez à la fin de ArrayList - Complexité temporelle O (1) . Ajoutez-le également dans HashMap, x comme clé et le dernier Index comme valeur - Complexité temporelle O (1).
  2. Supprimer: - Vérifiez si X est présent dans HashMap --Time complexité O (1). S'il est présent, recherchez-le et supprimez-le de HashMap --Time complex O (1). échangez cet élément avec le dernier élément de ArrayList et supprimez le dernier élément --Time complexité O (1). Mettre à jour l'index du dernier élément dans HashMap - Complexité temporelle O (1). 
  3. GetRandom: - Génère un nombre aléatoire de 0 au dernier index de ArrayList. renvoie l'élément ArrayList à l'index aléatoire généré - Complexité temporelle O (1).
  4. Rechercher: - Voir dans HashMap pour x en tant que clé. --Time complexité O (1).

Code: -

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;

            }
        }
    }

}

- Complexité temporelle O (1) .-- Complexité spatiale O (N).

2
HeadAndTail

Voici une solution C # à ce problème que j’ai trouvé il ya quelque temps quand on me posait la même question. Il implémente Add, Remove, Contains et Random avec d'autres interfaces .NET standard. Non pas que vous ayez besoin de la mettre en œuvre de manière aussi détaillée lors d'une interview, mais c'est bien d'avoir une solution concrète à regarder ...

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;

        }
    }
}
1
Scott Lerch

Bien que ce soit bien vieux, mais comme il n'y a pas de réponse en C++, voici mes deux sous.

#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<<" ";
        }
    }
};

Voici un morceau de code client pour tester la solution.

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;
}
1
Antithesis

Ne pouvons-nous pas faire cela en utilisant HashSet of Java? Il fournit insert, del, recherche tout dans O(1) par défaut . Pour getRandom, nous pouvons utiliser l'itérateur de Set qui donne de toute façon un comportement aléatoire. On peut simplement itérer le premier élément d'un ensemble sans se soucier du reste des éléments

public void getRandom(){
    Iterator<integer> sitr = s.iterator();
    Integer x = sitr.next();    
    return x;
}
0
user1108687

Dans C # 3.0 + .NET Framework 4, un Dictionary<TKey,TValue> générique est encore meilleur qu'un Hashtable car vous pouvez utiliser la méthode d'extension System.LinqElementAt() pour indexer dans le tableau dynamique sous-jacent où les éléments KeyValuePair<TKey,TValue> sont stockés:

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;
}

Cependant, autant que je sache, une table de hachage (ou sa descendance de dictionnaire) n’est pas une solution réelle à ce problème car Put () ne peut être amorti que O(1), pas vrai O(1), car il s'agit de O(N) à la limite de redimensionnement dynamique.

Y at-il une vraie solution à ce problème? Tout ce que je peux penser, c’est que si vous spécifiez une capacité initiale de Dictionary/Hashtable d’un ordre de grandeur supérieur à ce que vous prévoyez d’avoir besoin, vous obtiendrez O(1) opération car vous n’auriez jamais besoin de la redimensionner.

0
BaltoStar

Je suis d'accord avec Anon. À l'exception de la dernière exigence, où l'obtention d'un élément aléatoire avec une équité égale est requise, toutes les autres exigences peuvent être traitées uniquement à l'aide d'un seul DS basé sur Hash. Je vais choisir HashSet pour cela en Java. Le modulo de code de hachage d'un élément me donnera le numéro d'index du tableau sous-jacent dans O(1) temps. Je peux l'utiliser pour ajouter, supprimer et contient des opérations.

0
Ayaskant

Nous pouvons utiliser le hachage pour prendre en charge les opérations dans (1) temps.

insert (x) 1) Vérifiez si x est déjà présent en effectuant une recherche de carte de hachage . 2) S'il n'est pas présent, insérez-le à la fin du tableau . 3) Ajoutez également dans la table de hachage, x est ajouté en tant que clé et le dernier index du tableau en tant qu'index.

remove (x) 1) Vérifiez si x est présent en effectuant une recherche de carte de hachage . 2) Si présent, recherchez son index et supprimez-le de la carte de hachage . 3) Permutez le dernier élément avec cet élément dans le tableau et supprime le dernier élément . L'échange est effectué car le dernier élément peut être supprimé dans O(1) time . 4) Met à jour l'index du dernier élément dans le hachage carte.

getRandom () 1) Générez un nombre aléatoire compris entre 0 et le dernier index . 2) Renvoyez l'élément de tableau à l'index généré de manière aléatoire.

search (x) Fais une recherche de x dans la carte de hachage.

0
Shobhit Raj
/* 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`
    }
}
0
Ahmed Taha