Przedstawiono mi ten problem w wywiadzie. Jak byś odpowiedział?
Zaprojektuj strukturę danych, która oferuje następujące operacje w czasie O (1):
- wstawić
- usunąć
- zawiera
- uzyskać element losowy
data-structures
gildiarz
źródło
źródło
Odpowiedzi:
Rozważmy strukturę danych składającą się z tablicy hashy H i tablicy A. Klucze tablicy hashy to elementy struktury danych, a wartości to ich pozycje w tablicy.
ponieważ tablica musi automatycznie zwiększyć rozmiar, będzie amortyzowana O (1), aby dodać element, ale myślę, że to jest OK.
źródło
Wyszukiwanie O (1) implikuje zakodowaną strukturę danych .
W stosunku:
źródło
hashtable.get((int)(Math.random()*hashtable.size()));
Może ci się to nie podobać, ponieważ prawdopodobnie szukają sprytnego rozwiązania, ale czasami opłaca się trzymać się swoich pistoletów ... Tabela haszująca już spełnia wymagania - prawdopodobnie ogólnie lepiej niż cokolwiek innego (choć oczywiście w stałej amortyzowanej czas iz różnymi kompromisami do innych rozwiązań).
Podchwytliwym wymaganiem jest wybór „elementu losowego”: w tablicy haszującej musiałbyś przeskanować lub sondować taki element.
W przypadku adresowania zamkniętego / otwartego prawdopodobieństwo zajętości dowolnego zasobnika jest
size() / capacity()
, ale co najważniejsze, jest to zazwyczaj utrzymywane w stałym zakresie mnożenia przez implementację tablicy mieszającej (np. Tabela może być utrzymywana jako większa niż jej obecna zawartość, powiedzmy 1,2x do ~ 10x w zależności od wydajności / dostrojenia pamięci). Oznacza to, że średnio możemy spodziewać się przeszukiwania od 1,2 do 10 wiader - całkowicie niezależnie od całkowitej wielkości kontenera; amortyzowane O (1).Mogę sobie wyobrazić dwa proste podejścia (i wiele bardziej skomplikowanych):
szukaj liniowo z losowego zasobnika
wypróbuj losowe zasobniki wielokrotnie, aż znajdziesz wypełniony
Nie jest to świetne rozwiązanie, ale nadal może być lepszym ogólnym kompromisem niż narzuty dotyczące pamięci i wydajności związane z utrzymywaniem przez cały czas drugiej tablicy indeksów.
źródło
Najlepszym rozwiązaniem jest prawdopodobnie tablica mieszająca + tablica, jest to naprawdę szybkie i deterministyczne.
Ale najniżej oceniona odpowiedź (po prostu użyj tabeli skrótów!) Jest w rzeczywistości świetna!
Ludziom może się to nie podobać z powodu „możliwych nieskończonych pętli” i widziałem, jak bardzo mądrzy ludzie też mają taką reakcję, ale to źle! Nieskończenie nieprawdopodobne wydarzenia po prostu się nie zdarzają.
Zakładając dobre zachowanie twojego pseudolosowego źródła - co nie jest trudne do ustalenia dla tego konkretnego zachowania - i że tabele skrótów są zawsze zapełnione w co najmniej 20%, łatwo zauważyć, że:
Nigdy się nie zdarzy, że getRandom () będzie musiało wykonać więcej niż 1000 razy. Po prostu nigdy . Rzeczywiście, prawdopodobieństwo takiego zdarzenia wynosi 0,8 ^ 1000, czyli 10 ^ -97 - więc musielibyśmy powtórzyć je 10 ^ 88 razy, aby mieć jedną szansę na miliard, że kiedykolwiek się wydarzy. Nawet jeśli ten program działał w pełnym wymiarze godzin na wszystkich komputerach ludzkości aż do śmierci Słońca, to się nigdy nie wydarzy.
źródło
W tym pytaniu użyję dwóch struktur danych
Kroki :-
Kod :-
- Złożoność czasowa O (1). - Złożoność przestrzeni O (N).
źródło
Oto rozwiązanie C # tego problemu, które wymyśliłem jakiś czas temu, gdy zadałem to samo pytanie. Implementuje Add, Remove, Contains i Random wraz z innymi standardowymi interfejsami .NET. Nie żebyś kiedykolwiek musiał wdrażać to tak szczegółowo podczas rozmowy kwalifikacyjnej, ale miło jest mieć konkretne rozwiązanie, na które warto spojrzeć ...
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<T>"/> class. /// </summary> public Bag() : this(0) { } /// <summary> /// Initializes a new instance of the <see cref="Bag<T>"/> 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<T>"/> 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; } } }
źródło
ArgumentException
z komunikatem „Element z tym samym kluczem został już dodany”. zostanie wyrzucony (z bazowego słownika indeksu).Możemy użyć hashowania do obsługi operacji w czasie Θ (1).
insert (x) 1) Sprawdź, czy x jest już obecne, wykonując wyszukiwanie mapy hash. 2) Jeśli nie ma, włóż go na koniec tablicy. 3) Dodaj również tablicę skrótów, x jest dodawany jako klucz i ostatni indeks tablicy jako indeks.
remove (x) 1) Sprawdź, czy x jest obecne, wykonując wyszukiwanie mapy hash. 2) Jeśli jest obecny, znajdź jego indeks i usuń go z mapy skrótów. 3) Zamień ostatni element na ten element w tablicy i usuń ostatni element. Zamiana jest wykonywana, ponieważ ostatni element można usunąć w czasie O (1). 4) Zaktualizuj indeks ostatniego elementu w mapie skrótów.
getRandom () 1) Generuje liczbę losową od 0 do ostatniego indeksu. 2) Zwróć element tablicy do losowo wygenerowanego indeksu.
search (x) Wyszukaj x na mapie skrótów.
źródło
Chociaż to już dawno, ale ponieważ nie ma odpowiedzi w C ++, oto moje dwa centy.
Oto fragment kodu klienta do testowania rozwiązania.
źródło
W C # 3.0 + .NET Framework 4 generic
Dictionary<TKey,TValue>
jest nawet lepszy niż Hashtable, ponieważ można użyćSystem.Linq
metody rozszerzeniaElementAt()
do indeksowania podstawowej tablicy dynamicznej, w którejKeyValuePair<TKey,TValue>
są przechowywane elementy:Jednak, o ile wiem, Hashtable (lub jego potomstwo Dictionary) nie jest prawdziwym rozwiązaniem tego problemu, ponieważ Put () można zamortyzować tylko O (1), a nie O (1), ponieważ jest O (N) ) na granicy dynamicznej zmiany rozmiaru.
Czy istnieje prawdziwe rozwiązanie tego problemu? Wszystko, co przychodzi mi do głowy, to to, że jeśli określisz początkową pojemność Dictionary / Hashtable o rząd wielkości przekraczającą to, czego spodziewasz się kiedykolwiek potrzebować, otrzymasz operacje O (1), ponieważ nigdy nie musisz zmieniać rozmiaru.
źródło
Zgadzam się z Anonem. Z wyjątkiem ostatniego wymogu, w którym wymagane jest uzyskanie elementu losowego o jednakowej uczciwości, wszystkie inne wymagania można rozwiązać tylko za pomocą pojedynczego DS opartego na hash. W tym celu wybiorę HashSet w Javie. Modulo kodu skrótu elementu da mi numer indeksu tablicy bazowej w czasie O (1). Mogę tego użyć do operacji dodawania, usuwania i zawierania.
źródło
Czy nie możemy tego zrobić za pomocą HashSet of Java? Domyślnie zapewnia insert, del, search all in O (1). Dla getRandom możemy skorzystać z iteratora Set, który i tak daje losowe zachowanie. Możemy po prostu iterować pierwszy element z zestawu, nie martwiąc się o resztę elementów
źródło
źródło
Dlaczego nie użyjemy epoch% arraysize do znalezienia losowego elementu. Znalezienie rozmiaru tablicy to O (n), ale zamortyzowana złożoność wyniesie O (1).
źródło
Myślę, że możemy użyć podwójnej listy linków z tabelą skrótów. klucz będzie elementem, a skojarzona z nim wartość będzie węzłem w podwójnej liście dowiązań.
źródło