Какая алгоритмическая сложность основных операций с коллекциями в .NET?

Ответ

Сложность операций зависит от внутренней реализации структуры данных. Вот ключевые коллекции .NET:

Списки и массивы

  • List<T>, T[] (массив)
    • Доступ по индексу: O(1)
    • Поиск элемента (Contains, IndexOf): O(n)
    • Добавление в конец (Add): O(1) (амортизированное, за счет увеличения емкости)
    • Вставка/удаление в середину (Insert, RemoveAt): O(n) (требует сдвига элементов)

Хеш-таблицы (основанные на Dictionary<TKey, TValue>)

  • Dictionary<TKey, TValue>, HashSet<T>
    • Вставка, удаление, поиск по ключу: O(1) в среднем случае.
    • В худшем случае (все ключи имеют один хеш) — O(n). Качественная реализация GetHashCode() критически важна.
    • Перебор всех элементов: O(n).

Сбалансированные деревья (основанные на красно-черных деревьях)

  • SortedDictionary<TKey, TValue>, SortedSet<T>
    • Вставка, удаление, поиск: O(log n).
    • Элементы хранятся в отсортированном порядке (по IComparer<T>).

Двусвязный список

  • LinkedList<T>
    • Вставка/удаление в начало/конец или рядом с известным узлом: O(1).
    • Поиск элемента по значению или по индексу: O(n).

Практический пример выбора коллекции:

// Частый поиск по ID -> Используем Dictionary
Dictionary<int, User> userCache = new();
User u = userCache[123]; // O(1)

// Частая вставка/удаление в начале/конце -> LinkedList
LinkedList<LogEntry> recentLogs = new();
recentLogs.AddFirst(newLog); // O(1)

// Нужен отсортированный набор уникальных элементов -> SortedSet
SortedSet<int> uniqueScores = new() { 95, 80, 95, 70 }; // {70, 80, 95}
bool added = uniqueScores.Add(85); // O(log n)

Ответ 18+ 🔞

Да ты посмотри, какие коллекции в .NET есть, блядь! Сложность операций у них — это вообще отдельная песня, зависит от того, как они внутри устроены. Сейчас разжую, чтобы понятно было, а то некоторые думают, что всё работает по волшебству.

Списки и массивы — List<T> и T[] Ну, это как твоя тёща на диване — доступ к любому месту по индексу моментальный, O(1), подошёл и взял. А вот найти что-то конкретное (Contains, IndexOf) — это уже O(n), потому что придётся перебрать всё, как будто ищешь пульт от телика. Добавить в конец (Add) — обычно быстро, O(1), но иногда он внутри переезжает в новую квартиру побольше, и тогда немного тормозит. А вот вставить или удалить что-то из середины — это пиздец, O(n), потому что придётся всю очередь за тобой сдвигать, как в поликлинике.

Хеш-таблицы — Dictionary<TKey, TValue> и HashSet<T> Вот это, блядь, магия! Вставка, удаление, поиск по ключу — в среднем O(1), то есть почти мгновенно. Как будто у тебя всё разложено по полочкам с волшебными метками. Но есть нюанс, ёпта! Если все ключи вдруг окажутся братьями-близнецами и дадут одинаковый хеш — будет полный пиздец, O(n), и превратится всё в тот же долгий список. Поэтому GetHashCode() писать надо с умом, а не как попало. Перебрать всё подряд — O(n), куда ж деваться.

Сбалансированные деревья — SortedDictionary<TKey, TValue> и SortedSet<T> Эти товарищи — перфекционисты. Вставка, удаление, поиск — O(log n), то есть работают чётко и предсказуемо, но не мгновенно. Зато они держат всё в отсортированном порядке, прямо как библиотекарь-педант. Хочешь порядок — плати временем.

Двусвязный список — LinkedList<T> Специалист узкого профиля, блядь. Вставить или удалить в начале, в конце или рядом с известным узлом — O(1), раз плюнуть. Но попробуй найти что-то по значению или добраться до элемента по индексу — и всё, O(n), будешь шагать от начала до конца, как по коридору общежития.

А теперь пример из жизни, чтобы вообще всё встало на свои места:

// Если тебе надо часто искать юзера по айдишнику — бери Dictionary, не мучайся.
Dictionary<int, User> userCache = new();
User u = userCache[123]; // O(1) — взял и готово, красота!

// Если постоянно пихаешь новые записи в начало лога — LinkedList твой друг.
LinkedList<LogEntry> recentLogs = new();
recentLogs.AddFirst(newLog); // O(1) — прилепил и забыл.

// А если нужен отсортированный набор уникальных чисел — вперёд, в SortedSet.
SortedSet<int> uniqueScores = new() { 95, 80, 95, 70 }; // Получится {70, 80, 95}
bool added = uniqueScores.Add(85); // O(log n) — вставит куда надо, на своё законное место.

Короче, выбирай инструмент по задаче, а не потому что он первый в списке всплыл. Иначе потом будешь сидеть и думать, почему всё тормозит, как черепаха в патоках.