Опубликован: 06.10.2011 | Доступ: свободный | Студентов: 1677 / 97 | Оценка: 4.67 / 3.67 | Длительность: 18:18:00
Лекция 7:

Списки

< Лекция 6 || Лекция 7: 123 || Лекция 8 >

6.3. Другие варианты списков

Класс LINKED_LIST представляет одну из возможных реализаций списков. Существуют и другие варианты.

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

В реализации LINKED_LIST предпочтение отдается проходу по списку слева направо, и как следствие, возникает существенное различие в эффективности методов forth и back. Класс TWO_WAY_LIST представляет полностью симметричную структуру. Платой за это являются потери в памяти, так как вместо LINKABLE с одной связью приходится использовать класс BI_LINKABLE, в котором каждая ячейка имеет две ссылки – вперед и назад:

Экземпляры LINKABLE [T] и BI_ LINKABLE [T]

Рис. 6.16. Экземпляры LINKABLE [T] и BI_ LINKABLE [T]

Класс TWO_WAY_LIST содержит не только ссылку first_element, но и, дополняя симметрию, ссылку last_element на последний элемент списка:

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

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

В результате симметрии операции, требующие левого просмотра, такие как remove_left и put_left, так же как и их правосторонние двойники, имеют эффективность O(1), а метод back становится так же хорош, как и метод forth.

Абстракция и ee следствия

Здесь я должен рассказать небольшую историю с полей сражения. Как-то программисты одной из компаний объясняли своему менеджеру, что объектно-ориентированный инструментарий слишком медленный. Менеджер попросил старшего разработчика провести инспекцию кода, в результате чего было обнаружено, что на экземплярах LINKED_LIST многократно выполняется операция back. Заменив этот класс на TWO_WAY_LIST, получили ускорение работы в 23 раза. После этого программисты жили счастливо и никогда не говорили о потере скорости генерируемого кода.

Мораль: абстракция – краеугольный камень современного программирования – учит двум важным урокам.

Во-первых, она показывает существование рисков: вполне разумно и красиво рассматривать back как операцию, применимую ко всем спискам. Действительно, мы видели, что back реализована для односвязных списков. Если рассматривать список просто как список, абстракция с ее техникой полиморфизма и динамического связывания позволяет забыть, что иногда мы имеем дело с двусвязным списком, где back имеет эффективность O(1), а иногда может появляться односвязный список, возвращающий back в медлительное сообщество методов O(n). Наш первый урок состоит в том, что в практике профессиональной разработки ПО, где производительность является одним из важнейших ограничений, не следует позволять преимуществам функциональной абстракции подавлять свойства эффективности.

Если первый урок высвечивает возможную темную сторону абстракции, то второй урок свидетельствует о ее заслугах. Для достижения ускорения достаточно было изменить всего лишь несколько объявлений, заменив тип LINKED_LIST на TWO_WAY_LIST. Без ОО-методов и абстракции детали реализации были бы глубоко запрятаны внутрь приложения и изменения были бы более существенными и трудными.

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

Списки на массивах

Для представления списков можно использовать массивы вместо ссылочных структур:

Массив, реализующий список

Рис. 6.18. Массив, реализующий список

Класс ARRAYED_LIST библиотеки EiffelBase обеспечивает эту реализацию. Пусть вас не смущает, что используется класс ARRAY: экспортируемые методы, видимые клиентам ARRAYED_LIST, те же, что и у класса LIST, реализуются методами класса ARRAY, такими как item и put. Внутренне, как показано на рисунке, lower равно 1, так что имеет место следующий инвариант класса: capacity = upper – lower + 1, откуда следует, что capacity = upper, так что верхняя граница задает физический размер массива.

У массива число его элементов count совпадает с его емкостью capacity. Но это не верно для списка, построенного на массиве: count является числом элементов списка, в то время как capacity – это максимально возможное число элементов, которое может изменяться при необходимости. На приведенном выше рисунке count равно 5, а capacity равно 9, и индексы элементов списка находятся в пределах от 1 до count. Инвариант класса включает свойство count <= capacity.

Курсор задается целочисленной переменной index, которая в классе ARRAYED_LIST реализована как атрибут. Еще один признак, отличающий массив от списка, построенного на массиве, состоит в том, что индекс массива меняется от 1 до capacity ( от lower до capacity), но index может меняться, как принято в списках, от 0 до capacity + 1.

Реализация не обязана размещать элементы списка, начиная с нижней границы. Для поддержки левосторонней вставки полезно рассматривать массив как круговую структуру с двумя маркерами на концах, – эта техника будет описана при рассмотрении круговых очередей. Реализация не представляет сложностей, однако позволяет для очередей избавиться от фатального ограничения списков на массивах – вставка и удаление требуют перемещения всех элементов слева или справа от курсора. Эти операции дорогостоящие и требуют O(n) времени. Удаление элементов можно откладывать, оставляя пустоты, но в какой-то момент потребуется провести O(n) сжатие массива. Если при вставке окажется, что достигнута емкость массива, то придется массив перестраивать, увеличивая емкость, а это, как отмечалось ранее, дорогостоящая операция.

Эти свойства существенно ограничивают полезность списков, построенных на массивах. Они представляют интерес для определенного круга сценариев, где изначально создается список, после чего он остается в относительно стабильном положении с небольшим числом вставок и удалений. Тогда список на массивах дает преимущества, особенно если часто выполняются операции по произвольному доступу к элементам по индексу – операция go_i_th(i) имеет сложность O(1), а не O(n), как для связных списков.

Мультимассивные списки

На тему списков вариаций может быть множество. Одной из таких вариаций является класс MULTI_ARRAY_LIST, соединяющий преимущества массивов и связных списков. Платой за это служит дополнительная сложность, как можно видеть, анализируя код этого класса. Такой список является двусвязным списком, элементы которого – списки, построенные на массивах:

Мультимассивный список

Рис. 6.19. Мультимассивный список

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

< Лекция 6 || Лекция 7: 123 || Лекция 8 >