К каждому собеседованию важно готовиться и проще всего это делать, когда перед глазами есть готовый материал. В данной публикации я хочу поделиться с вами своей шпаргалкой, которую использую перед собеседованиями для повторения структур данных в Java.

Что такое структуры данных, для чего они и какие есть в Java?

Под структурами данных подразумевается хранение данных и их организация таким образом, чтобы решать поставленную задачу наиболее эффективным способом. В Java есть следующие структуры данных:

  • Массив

  • Список (Динамический массив)

  • Стек

  • Очередь

  • Связный список

  • HashTable и HashMap

  • Дерево

Массив

Массив - это нумерованный набор переменных одного типа.

Объявляется следующем образом:

int[] arr = new int[10];
  • Все массивы в Java одномерные. В случае с многомерными массивами каждый элемент содержит только ссылку на вложенный массив

  • Можно создать нулевого размера, может быть полезно если нужно вернуть пустой массив из какого-либо метода

  • Оператор new используется для создания ссылочного типа данных. Ссылка хранится на стеке, а объект в куче. Если на объект нет ссылок, то он будет удалён автоматически. Удаление объекта может быть осуществлено с задержкой

Список (Динамический массив)

Идея списка или же динамического массива заключается в автоматическом расширении емкости.

Объявляется следующем образом:

ArrayList<Integer> arr = new ArrayList<Integer>();
  • Примитивный тип данных передать не можем, поэтому передаем класс обертку. О классах обертках, можно прочитать здесь. При желании можно написать универсальную реализацию ArrayList, сделать его массивом Object и тогда можно будет хранить еще и примитивы благодаря автоупаковке

  • Если не указать в конструктор начальную емкость, то будет создан пустой список с емкостью в 10 элементов

  • В случае, когда зарезервированной емкости не хватает, при достижении максимального количества элементов будет создан новый массив с емкостью: новая_емкость = (старая_емкость * 3) / 2 + 1. Существующие элементы списка будут скопированы в новый массив

  • Чтобы не тратить память напрасно, при удалении элементов следует вызывать метод trimToSize()

Плюсы и минусы

+ доступ к элементам по индексу за O(1), к элементам по значению за O(n);
+ можно хранить любые значения и даже null.


- вставка или удаление элемента в середину списка вызывает перезапись всех элементов, работает за O(n);
- поиск за O(n);
- не синхронизирован.

Стек

Очередь работает по принципу LIFO. В Java наследуется от Vector<E>, реализует следующие интерфейсы: Iterable<E>, Collection<E>, List<E>, RandomAccess, Serializable, Cloneable.

Объявляется следующем образом:

Stack<Integer> arr = new Stack<Integer>();
Основные методы
  • push() - добавляет в конец очереди;

  • peek() - возвращает последний элемент и не удаляет его;

  • pop() - удаляет последний элемент и возвращает его;

  • empty() - вернет true - если очередь пуста и false - в противном случае;

  • search() - возвращает номер позиции с конца очереди.

Очередь

Интерфейс Queue<E> описывает одностороннюю очередь, а Deque<E> - двухстороннюю. Прежде чем перейти к объявлению в Java, стоит отметить иерархию наследования. Иерархия следующая:

  • Iterable<T> => Collection<E> => Queue<E> => Deque<E>

Интерфейсы Queue<E> и Deque<E> реализуют следующие классы:

  • ArrayDeque<E> - двухсторонняя очередь

  • LinkedList<E> - связный список

  • PriorityQueue<E> - очередь с приоритетами

Объявляется следующем образом:

Queue<Integer> arr = new ArrayDeque<Integer>();
Deque<Integer> arr1 = new ArrayDeque<Integer>();
PriorityQueue<Integer> arr2 = new PriorityQueue<Integer>();
// Очередь на LinkedList'е
Queue<Integer> arr = new LinkedList<Integer>();
Deque<Integer> arr = new LinkedList<Integer>();
Разница между реализацией на листе и деку
  • ArrayDeque реализует дек на массиве, поэтому он эффективнее по памяти и работает быстрее, чем LinkedList

Пару слов о PriorityQueue.
Этот класс реализует следующие интерфейсы: Iterable<E>, Collection<E>, Queue<E>, Serializable. У этого класса есть свои особенности:

  • Из очереди первым возвращается элемент с наибольшим приоритетом

  • Значение null добавить нельзя

Связный список

LinkedList<E> реализует связный список, элементы которого хранят ссылки на предыдущий и следующий элементы.

Класс реализует следующие интерфейсы:
Iterable<E>, Collection<E>, List<E>, Queue<E>, Deque<E>, Serializable, Cloneable.

Объявляется следующем образом:

LinkedList<Integer> arr = new LinkedList<Integer>();
Сравнение ArrayList и LinkedList по сложности выполнения операций

Операция

ArrayList

LinkedList

add (E element)

O(1)
O(n) - при копировании

O(1)

add (int index, E element)

O(n/2) - с середины
O(n) - с начала
O(1) - с конца

O(n/4)
O(n) - в конец или начало

remove (int index)

O(n/2) - с середины
O(n) - с начала
O(1) - с конца

O(n/4)
O(n) - в конец или начало

get (int index)

O(1)

O(n/4)

LinkedList занимает гораздо больше памяти, чем ArrayList. Использовать нужно в определенных случаях, чаще всего когда речь идет о двусвязном списке. Также стоит отметить, что элементы у ArrayList в памяти хранятся линейно, поэтому доступ по индексу происходит за O(1)

HashTable и HashMap

HashTable считается устаревшей, поэтому приведена лишь разница между мапой и таблицей. HashMap используется для хранения пары «ключ-значение». В качестве примера использования хэш-мапы можно привести пациента больницы, у которого есть Ф.И.О. и номер медицинского полиса.

  • Если конструктору не передать никаких значений, то будет создан пустой словарь с емкостью в 16 элементов и коэффициентом заполнения 0.75

  • Если коэффициент заполнения достигает максимума, то число bucket'ов увеличивается в два раза

Класс HashMap<K, V> реализует следующие интерфейсы:
Map<K, V>, Serializable, Cloneable.

Объявляется следующем образом:

HashMap<String, Integer> map = new HashMap<String, Integer>();
Разница между HashTable и HashMap
  • Хэш-Таблица не может хранить null, в отличии от Хэш-Мапы

  • В Хэш-Таблице все методы синхронизированы, что сказывается на скорости работы

  • Хэш-Таблица не рекомендуется к использования, так как считается устаревшей, Хэш-Мапа предпочтительнее

    P.S. Если требуется выбрать структуру, которая справится с параллельными вычислениями, то есть ConcurrentHashMap

Дерево

Стоит заметить, что готовой реализации бинарного дерева в Java нет, но есть TreeMap<K, V> и TreeSet<E>, которые описывают словари, в котором ключи хранятся в отсортированном порядке. TreeSet инкапсулирует в себе TreeMap, который в свою очередь использует сбалансированное бинарное красно-черное дерево для хранения элементов.

Класс TreeSet<E> реализует следующие интерфейсы:
Itearble<E>, Collection<E>, Set<E>, SortedSet<E>, NavigatbleSet<E>, Serializable, Cloneable.

Класс TreeMap<K, V> реализует следующие интерфейсы:
Map<K,V>, SortedMap<K, V>, NavigatbleMap<K, V>, Serializable, Cloneable.

Преимущества иерархической организации данных
  • простота восприятия человеком;

  • высокое быстродействие при транзакционной обработке.

Недостатки
  • медленный доступ к данным нижних уровней;

  • склонность к избыточности;

  • на больших объёмах данных требуется индексация элементов.

Объявляется следующем образом:

TreeSet<Integer> set = new TreeSet<Integer>();
TreeMap<String, Integer> map = new TreeMap<String, Integer>();
В чем разница между HashSet и TreeSet
  • Под капотом у TreeSet лежит красно-черное дерево и упорядочивание элементов происходит именно по принципу красно-черных деревьев. HashSet не поддерживает упорядочивание

  • Сложность TreeSet - O(log(n)), HashSet - O(1) (речь идет о методах add(), contains(), remove())

Заключение

В своей шпаргалке я постарался уделить внимание вопросам, которые быстрее всего забываю. Будет круто увидеть в комментариях замечания, дополнения или же ответы на вопросы, связанные со структурами данных в Java, которые вам задавали на собеседованиях. Надеюсь, что прочитанное было полезно и помогло освежить в памяти знания.


Желаю вам успехов и благодарю за интерес к моей публикации!

Источники

Комментарии (10)


  1. panzerfaust
    01.08.2023 06:43
    +3

    Дерево, TreeMap, TreeSet

    Неа. Типичное заблуждение. Поведение объекта характеризуется его интерфейсом. TreeMap и TreeSet - это прежде всего Map и Set. Они не реализуют какие-то специфичные древесные интерфейсы. Соответственно, вы не можете работать с ними как с деревьями и выполнять например поиски в глубину и ширину. В плане ООП мы не можем сказать TreeSet is a Tree. Зато можем сказать TreeSet has a Tree - но это совсем другая история.


    1. dcct0r Автор
      01.08.2023 06:43

      Спасибо за фидбэк, учту)


  1. aleksandy
    01.08.2023 06:43

    При желании можно написать универсальную реализацию ArrayList, сделать его массивом Object

    Интересно, зачем так делать, если всё уже сделано.

    Если не указать в конструктор начальную емкость, то будет создан пустой список с емкостью в 10 элементов

    Если говорить про ArrayList, то это не так, как минимум с 1.8+. Если не указать размерность, то никаких дополнительных массивов не будет создаваться до первого добавления элемента в список.

    Стек

    Цитата прям из документации.

    A more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class.

    Связный список

    Самого главного про этот список и нет.

    Does anyone actually use LinkedList? I wrote it, and I never use it.
    Joshua Bloch

    Про TreeXXX уже всё сказали.


    1. dcct0r Автор
      01.08.2023 06:43

      А что бы вы еще добавили про связный список?


      1. aleksandy
        01.08.2023 06:43

        Что нет никакого практического смысла его использовать.

        Не саму структуру, но её конкретную реализацию java.util.LinkedList.


  1. Andrey_Solomatin
    01.08.2023 06:43

    В данной публикации я хочу поделиться с вами своей шпаргалкой, которую
    использую перед собеседованиями для повторения структур данных в Java.

    Я неплохо прокачался в структурах данных реализуя их или используя для получения лучшего (алгоритмического или по памяти) результата решая задачки на litcode.

    Там к задачам есть много разных решения с комментариями, поэтому почитав с десяток можно примерно понять какая структура в данном случае предпочтительна.


    1. dcct0r Автор
      01.08.2023 06:43

      Я решаю задачи на литкоде, в основном конечно именно те, которые дают на собеседованиях и как правило, они проверяют в том числе еще и знание структур данных языка


  1. sshikov
    01.08.2023 06:43
    +1

    Java есть следующие структуры данных
    Я не очень понимаю смысл таких собеседований и таких вопросов.

    Во-первых, это далеко не все. А как же ConcurentMap и иже с ним?


    А во-вторых, много-много лет существует такая штука, как Google Guava (также Google Collections), где есть еще с десяток наверное структур данных. Ну просто чтоб далеко не ходить — MultiMap, где по одному ключу лежит N значений. Или реверсивный мап, который можно повернуть в сторону value->key. И еще — куча других библиотек, реализующих еще десятки других структур.


    Не говоря уже о том, что каждый, кто немного владеет языком, может создать свою структуру данных достаточно несложно.


    1. dcct0r Автор
      01.08.2023 06:43

      Cогласен, это далеко не все структуры данных и Google Guava может упростить жизнь, но ведь как правило же об этом не спрашивают на собеседованиях на позицию джуна или интерна


      1. sshikov
        01.08.2023 06:43
        +1

        как правило же об это не спрашивают на собеседованиях

        Ну вот это и смешно :) Знаете, я вот лет пять пишу под Hadoop, так там конфликт версий Guava — это прям настолько типичные грабли, что чуть ли не каждый наступал. Т.е. гугль имел такое свойство, объявлять методы deprecated, и потом их выпиливать. И вот вы такой модный и молодежный, взяли свежую версию гуавы, собрали приложение под хадуп, и выяснили, что в самом хадупе тоже лежит Guava, более старой версии. И при замене ее на вашу более новую, что-то в самом хадупе перестает работать.


        Я это к чему — что почти все разработчики приложений под хадуп так или иначе гуавой попользовались в свое время.