Почему повторение списка над списком будет быстрее, чем индексирование через него?

Считывая документацию Java для списка ADT, он говорит:

Интерфейс List предоставляет четыре метода для позиционного (индексированного) доступа к элементам списка. Списки (например, массивы Java) основаны на нуле. Обратите внимание, что эти операции могут выполняться во времени, пропорциональном значению индекса для некоторых реализаций (например, класс LinkedList). Таким образом, итерация по элементам в списке обычно предпочтительнее для индексации через него, если вызывающий объект не знает реализации.

Что именно это означает? Я не понимаю вывод, который нарисован.

Ответ 1

В связанном списке каждый элемент имеет указатель на следующий элемент:

head -> item1 -> item2 -> item3 -> etc.

Чтобы получить доступ к item3, вы можете ясно видеть, что вам нужно идти от головы через каждые node, пока не достигнете item3, так как вы не можете прыгать напрямую.

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

for(int i = 0; i < 4; i++) {
    System.out.println(list.get(i));
}

что происходит:

head -> print head
head -> item1 -> print item1
head -> item1 -> item2 -> print item2
head -> item1 -> item2 -> item3 print item3

Это ужасно неэффективно, потому что каждый раз, когда вы индексируете его, он перезапускается с самого начала списка и проходит через каждый элемент. Это означает, что ваша сложность эффективно O(N^2) просто для перемещения по списку!

Если бы я сделал это:

for(String s: list) {
    System.out.println(s);
}

тогда произойдет следующее:

head -> print head -> item1 -> print item1 -> item2 -> print item2 etc.

все за один обход, который равен O(N).

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

Ответ 2

Здесь подразумевается ответ:

Обратите внимание, что эти операции могут выполняться во времени, пропорциональном значению индекса для некоторых реализаций (например, класс LinkedList)

Связанный список не имеет встроенного индекса; вызов .get(x) потребует реализации списка для поиска первой записи и вызова .next() x-1 раз (для O (n) или линейного доступа по времени), где список с поддержкой массива может просто индексироваться в backingarray[x] в O (1) или постоянное время.

Если вы посмотрите JavaDoc для LinkedList, вы увидите комментарий

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

тогда как JavaDoc для ArrayList имеет соответствующий

Реализованная реализация интерфейса списка. Выполняет все необязательные операции с списками и разрешает все элементы, включая null. В дополнение к реализации интерфейса List этот класс предоставляет методы для управления размером массива, который используется внутри для хранения списка. (Этот класс примерно эквивалентен Vector, за исключением того, что он несинхронизирован.)

Операции size, isEmpty, get, set, iterator и listIterator выполняются в постоянное время. Операция add работает в режиме амортизированного постоянного времени, то есть для добавления n элементов требуется время O (n). Все остальные операции выполняются в линейном времени (грубо говоря). Постоянный коэффициент является низким по сравнению с тем, который используется для реализации LinkedList.

A связанный с этим вопрос под названием "Big-O Summary for Java Collections Framework" имеет ответ, указывающий на этот ресурс, "Коллекции Java JDK6" , которые могут оказаться полезными.

Ответ 3

Несмотря на то, что принятый ответ, безусловно, правильный, могу указать на незначительный недостаток. Котировка Tudor:

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

Это не совсем так. По правде говоря,

С помощью ArrayList ручная подсчитанная петля примерно в 3 раза быстрее

источник: проектирование производительности, Google Android doc

Обратите внимание, что рукописный цикл относится к индексированной итерации. Я подозреваю его из-за итератора, который используется с расширенными для циклов. Он создает незначительную производительность в виде штрафа в структуре, которая поддерживается непрерывным массивом. Я также подозреваю, что это может быть справедливо для класса Vector.

Мое правило заключается в том, что, когда это возможно, используйте расширенный цикл, и если вы действительно заботитесь о производительности, используйте индексированную итерацию только для ArrayLists или Vectors. В большинстве случаев вы даже можете игнорировать это: компилятор может оптимизировать это в фоновом режиме.

Я просто хочу отметить, что в контексте разработки в Android оба обхода ArrayLists не обязательно эквивалентны. Просто пища для размышлений.

Ответ 4

Итерация по списку со смещением для поиска, например i, аналогична алгоритму живописца Шлемиэля.

Шлемиэль получает работу как уличный художник, рисуя пунктирные линии вниз по середине дороги. В первый день он берет банку с краской на дорогу и заканчивается в 300 ярдах от дороги. "Это довольно хорошо!" - говорит его босс, "ты быстрый рабочий!" и платит ему копейку.

На следующий день Шлемиэль получает всего 150 ярдов. "Ну, это не почти так же хорошо, как вчера, но вы все еще быстрый рабочий. 150 м является респектабельным", и платит ему копейку.

На следующий день Шлемиэль рисует в 30 ярдах от дороги. "Всего 30!" крики его босс. "Это неприемлемо! В первый день вы сделали десять раз много работа! Что происходит?"

"Я не могу помочь", - говорит Шлемиэль. "Каждый день я все дальше и дальше от краски может!"

Источник.

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

Ответ 5

Чтобы найти i-й элемент a LinkedList, реализация проходит через все элементы до i-го.

Итак,

for(int i = 0; i < list.length ; i++ ) {
    Object something = list.get(i); //Slow for LinkedList
}