Лекции / Лекция_9_Структуры_данных_продолжение_ipynb_Colab (1)
.pdf
Двусвязный список
Двусвязный список — разновидность связанного списка. Узел содержит данные и две ссылки (указатели) на предыдущий и следующий элемент списка.
Преимущества и недостатки двусвязных списков
Преимущества:
Простота вставки и удаления элемента в начале и конце списка
Недостатки:
Сложность получения элемента по индексу Удвоенный (по сравнению с односвязным списком) расход памяти на хранение указателей
Узел списка
Узел списка представляет собой составную структуру. Обычно реализуется с помощью класса или структуры (в процедурных языках). Содержит значения двух типов. Одно для хранения данных (числа, строки и т. д.), и две ссылки на следующий узел или предыдущий узел (реализуется как указатель или ссылка, тип которых совпадает с типом узел).
Двусвязный список
В одной из самых распространенных реализаций двусвязного списка используются два фиктивных элемента head и tail (начало и конец списка соответственно). Остальные узлы вставляются между ними.
Добавление значения в начало списка
Добавление значения в конец списка
Добавление значения в произвольное место списка
Удаление значения из начала списка
Удаление элемента из конца списка
Удаление элемента из произвольного места списка
Получение размера списка
Для получения размера списка стоит объявить переменную с начальным значением 0. Начиная с начала списка выполнять переход по ссылке к следующему узлу. На каждом переходе увеличивать значение этой переменной на 1. Закончить на tail.
Работа с индексами
Для работы с индексами можно использовать подход аналогичный вычислению длины. Стоит объявить переменную начальное значение которой равно начальному индексу (произвольный выбор). Начиная с головы списка выполняем проход по next (от узла к узлу), то тех пор пока значение этой переменной не станет равно искомому индексу.
Реализация на Python
Описание структуры узла и списка
class Node:
def __init__(self, data=None, next=None, prev=None):
#Инициализузладанныеция: ссылка,наследующий узелссылка, напредыдущийузел self.data=data
self.next=next
self.prev=prev |
|
|
|
|
def __str__(self): |
|
|
|
|
#Припреобразованиистрокувозвращаемданные |
узла(опечаткаdate->data): |
|||
return str(self.data) |
|
|||
class DoublyLinkedList: |
|
|
||
def __init__(self): |
|
|
||
#Создаёмфиктивныеузлыheadиtail |
|
|||
self.head=Node |
|
() |
#Головасписка(фиктивныйузел) |
|
self.tail=Node |
|
() |
#Хвоспискат(фиктивныйузел) |
|
#Связываемheadиtail:headуказываетнаtail, |
tailуказываетнаhead |
|||
self.head=.next |
|
self.tail |
|
|
self.tail=.prev |
|
self.head |
|
|
#Длинаспис(каоличествореальныхузлов) |
|
|||
self.length= |
0 |
|
|
|
Методы добавления |
|
|
|
|
def add_first(self, value): |
|
|||
#Создаёмновыйузел: |
|
|
|
|
#-data=value |
|
|
|
|
#-next=self.head(текущий.nextпервыйреальный |
узел) |
|||
#-prev=self.(headфиктивнаяголова) |
|
|||
add_node=Node(value, |
|
|
self.head.next, self.head) |
|
#Текущийпервыйреальныйузелтеперьдолжен |
указыватьнановыйузелкакнапредыдущий |
|||
self.head.next=add.prevnode |
|
|||
#Головатеперьуказываетнановыйузелкакна |
первыйреальный |
|||
self.head=.addnextnode |
|
|
|
|
#Увеличиваемдлинусписка |
|
|
|
|
self.length+= |
1 |
|
|
|
def add_last(self, value): |
|
|||
#Создаёмновыйузел: |
|
|
|
|
#-data=value |
|
|
|
|
#-next=self.(tailфиктихвостный) |
|
|||
#-prev=self.tail(текущий.prevпоследний |
реальныйузел) |
|||
add_node=Node(value, |
|
|
self.tail, self.tail.prev) |
|
#Текущийпоследнийреальныйузелтеперьдолжен |
указыватьнановыйузелкакнаследующий |
|||
self.tail.prev=add.nextnode |
|
|||
#Хвосттеперьуказываетнановыйузелкакна |
последнийреальный |
|||
self.tail=.addprevnode |
|
|
|
|
#Увеличиваемдлинусписка |
|
|
|
|
self.length+= |
1 |
|
|
|
Методы удаления |
|
|
|
|
def remove_first(self): |
|
|
||
#Еслисписокпустничего, неделаем |
|
|
||
if self.is_empty(): |
|
|
||
return |
|
|
|
|
#Узелкоторый, нужноудалить(первыйреальный |
узел) |
|||
remove=_node |
self.head.next |
|
||
#Головатеперьуказываетнаследующийпосле |
удаляемогоузел |
|||
self.head=.rnemovext_node.next |
|
|||
#Новыйпервыйреальныйузелтеперьуказываетна |
головукакнапредыдущий |
|||
remove_node=.next.prev |
self.head |
|||
#Уменьшаемдлинусписка |
|
|
|
|
self.length-= |
1 |
|
|
|
def remove_last(self): |
|
|
|
|
#Еслисписокпустничего, неделаем |
|
|
||
if self.is_empty(): |
|
|
||
return |
|
|
|
|
#Узелкоторый, нужноудалить(последнийреальный |
узел) |
|||
remove=_node |
self.tail.prev |
|
||
#Хвосттеперьуказываетнапредыдущийперед |
удаляемымузел |
|||
self.tail=.removeprev_node.prev |
|
|||
#Новыйпоследнийреальныйузелтеперьуказывает |
нахвосткакнаследующий |
|||
remove_node=.prev.next |
self.tail |
|||
#Уменьшаемдлинусписка |
|
|
|
|
self.length-= |
1 |
|
|
|
def remove_by_value(self, value):
#Еслисписокпустничего, неделаем if self.is_empty():
return |
|
|
|
#Начинаемобходспервогореальногоузла |
|
||
current=_node |
self.head.next |
|
|
#Ищемузелснужнымзначениемпоканедой, дёмо |
хвоста |
||
while current!=node |
self.tail |
and current_node!=value:.data |
|
current=currentnode _node.next |
|
||
#Еслидошлихвостаиненашлизначение, |
выходим |
||
if current==node |
|
self.tail: |
|
return |
|
|
|
#Удаляемнайденныйузел: |
|
|
|
#Предыдущийузелтеперьуказываетнаследующий |
послеудаляемого |
||
current_node=cur.prentv.nextnode.next |
|
||
#Следующийузелтеперьуказываетнапредыдущий |
передудаляемым |
||
current_node=current.next.prevnode.prev |
|
||
#Уменьшаемдлинусписка |
|
|
|
self.length-= |
1 |
|
|
Методы для поиска узла по индексу
def get_node_by_index(self, index): |
|
|||
#Проверяемчтоиндекс,находитсявдопустимых |
пределах |
|||
if index< |
0 or index>= |
self.length: |
|
|
raise IndexError("Indexoutofrange" |
) |
|||
#Оптимизацвыбирняа:емправлениеобхода |
|
зависимостиотпоз индексации |
||
#Еслииндексближекконцусписка(вовторой |
|
половине) |
||
if index> |
self.length// |
2: |
|
|
#Начинаемсконцаномер:элемента=последний |
индекс(length-1) |
|||
number= |
|
self.length- |
1 |
|
#Начинаемобходспоследнегореальногоузла |
(передфиктивнымtail) |
|||
current=_node |
self.tail.prev |
|
||
#Двигаемсяназадпока,недойдёмонужного |
индекса |
|||
while number!=index: |
|
|
||
current=currentnode _node.prev |
#Переходкпредыдущемуузлу |
|||
number-= |
|
1 |
|
|
#Еслииндексближекначалусписка(впервой |
|
половинеилис редине) |
||
else: |
|
|
|
|
#Начинаемсначаланомер:элемента=0 |
|
|
||
number= |
|
0 |
|
|
#Начинаемобходспервогореальногоузла(после |
фиктивногоhead) |
|||
current=_node |
self.head.next |
|
||
#Двигаемсявперёдпокане,дойдёмонужного |
индекса |
|||
while number!=index: |
|
|
||
current=currentnode _node.next |
#Переходкследующемуузлу |
|||
number+= |
|
1 |
|
|
#Возвращаемнайденныйузел return current_node
Методы для работы с индексами
def get_by_index(self, index): |
|
|||
#Проверяемчтоиндекс,находитсявдопустимых |
пределах |
|||
if index< |
0 or index>= |
self.length: |
|
|
raise IndexError("Indexoutofrange" |
) |
|||
#Получаузепомлиндексувозвращаемегоданные |
|
|||
return self.get_node_by_index(index).data |
|
|||
def remove_by_index(self, index): |
|
|||
#Проверяемчтоиндекс,находитсявдопустимых |
пределах |
|||
if index< |
0 or index>= |
self.length: |
|
|
raise IndexError("Indexoutofrange" |
) |
|||
#Получаузекоторыймл, нужноудалить |
|
|
||
remove=_node |
|
self.get_node_by_index(index) |
||
#Перенаправссылкияем: |
|
|
||
#Следующийузелтеперьуказываетнапредыдущий |
передудаляемым |
|||
remove_node=remove.next.prevnode.prev |
|
|
||
#Предыдущийузелтеперьуказываетнаследующий |
послеудаляемого |
|||
remove_node=remo.preve.nextnode.next |
|
|
||
#Отвязывудаляемыйузел(длябезопасности) |
|
|||
remove_node= .prev |
None |
|
|
|
remove_node= .next |
None |
|
|
|
#Уменьшаемдлинусписка |
|
|
||
self.length-= |
|
1 |
|
|
def insert_by_index(self, index, value): |
|
|||
#Проверяемчтоиндекс,находитсявдопустимых |
пределах |
|||
if index< |
0 or index>= |
self.length: |
|
raise IndexError("Indexoutofrange" |
) |
||
#Получаузенамл,позициюкоторогобудем |
|
вставлять |
|
current=_node |
self.get_node_by_index(index) |
||
#Создаёмновыйузел: |
|
|
|
#-data=value |
|
|
|
#-next=current(следующийnodeпосле |
|
вставляемого) |
|
#-prev=current_node(предыдущий.prevперед |
|
вставляемым) |
|
add_node=Node(value,currentcurrentnode, _n |
|
ode.prev) |
|
#Предыдущийузелтеперьуказываетнановыйузел |
|
|
|
current_node=add.prevnode.next |
|
|
|
#Текущийузелтеперьуказываетнановыйузелкак |
|
напредыдущий |
|
current_no=adde.nodeprev |
|
|
|
#Увеличиваемдлинусписка |
|
|
|
self.length+= |
1 |
|
|
def set_by_index(self, index, value): |
|
||
#Проверяемчтоиндекс,находитсявдопустимых |
пределах |
||
if index< |
0 or index>= |
self.length: |
|
raise IndexError("Indexoutofrange" |
) |
||
#Получаузепомлиндексу |
|
|
|
current=_node |
self.get_node_by_index(index) |
||
#Заменяемданныевузленановоезначение current_node=value.data
Методы для получения размера
def get_length(self): |
|
|
||
#Начинаспервогомреальногоузла(после |
фиктивногоhead) |
|||
current=_node |
self.head.next |
|
||
#Счётчикдлинысписка |
|
|
|
|
length= |
0 |
|
|
|
#Проходимповсемреальнымузлампока,недойдём |
дофиктивногоtail |
|||
while current!=node |
self.tail: |
|
||
length+= |
|
1 |
|
#Увеличиваемсчётчик |
current=currentnode _node.next |
#Переходимкследующемуузлу |
|||
#Возвращаемколичествореальныхузловсписке return length
Стек на основе связанного списка
Стек — это абстрактный тип данных, представляющий собой список элементов, организованных по принципу LIFO (англ. last in — first out, «последним пришёл — первым вышел»).
Стек является динамической структурой данных.
Поддерживаемые операции:
Добавление элемента в вершину стека (push) Удаление элемента из вершины стека (pop)
Получение элемента с вершины стека без удаления (peek) Получение размера стека (size)
Реализация стека с помощью односвязного списка
Для односвязного списка наиболее эффективными операциями являются операции добавления и удаления элемента из начала списка. Это дает возможность реализовать стек на основе связанного списка. Такая реализация будет оптимальной и
производительной. Для реализации стека стоит использовать следующие операции односвязного списка:
push (добавление элемента в голову списка) pop (удаление элемента из головы списка) peek (получение значения из головы списка) size (получение размера списка)
Изображение реализации стека с помощью связанного списка
Добавление значения в стек
Получение значения с удалением
Получение значения без удаления
Проверить значение head на пустоту. Если ссылка не пустая, то вернуть значение данных в узле.
Получение размера
Для получения размера стоит объявить переменную с начальным значением 0. Начиная с начала списка выполнять переход по ссылке к следующему узлу. На каждом переходе увеличивать значение этой переменной на 1. Закончить на узле для которого next==null.
Реализация на Python
Описание узла и стека
class Node: |
|
|
|
|
def __init__(self, data=None, next=None): |
|
|||
|
#Инициализузладанныеция: ссылканаследующий |
узел |
||
|
self.data=data |
|
|
|
|
self.next=next |
|
|
|
def __str__(self): |
|
|||
|
#Припреобразованиистрокувозвращаемданные |
узла(опечаткаdate->data): |
||
|
return str(self.data) |
|
||
class Stack: |
|
|
|
|
def __init__(self): |
|
|||
|
#Присозданиистекаголовапуста(стекне |
содержитэлементов) |
||
|
self.head= |
None |
|
|
Метод добавления |
|
|
||
def push(self, value): |
|
|||
#Создаёмновыйузелспередазначениемным |
|
|||
new_node=Node(value) |
|
|
||
#Новыйузелуказыванатекущуютголовустека |
|
|||
new_node= .next |
|
self.head |
|
|
#Головойстекастановитсяновыйузел |
|
|||
self.head=new_node |
|
|
||
Методы для получения с удалением и без |
|
|||
def pop(self): |
|
|
|
|
#Еслистекпустничего, невозвращаем(илиможно |
вернутьNone) |
|||
if self.head is None: |
|
|||
|
return None |
|
|
|
#Сохраняемданныеизголовыстека |
|
|||
value= |
self.head.data |
|
||
#Перемещаемголовунаследующийузел(удаляем |
верхнийэлемент) |
|||
self.head= |
self.head.next |
|
||
#Возвращаемсохранённыеданные |
|
|||
return value |
|
|
|
|
def peek(self): |
|
|
|
|
#Еслистекпустничего, невозвращаем(илиможно |
вернутьNone) |
|||
if self.head is None: |
|
|||
|
return None |
|
|
|
#Сохраняемданныеизголовыстека value= self.head.data
#Возвращаемданныебезудаленияэлемента return value
Метод для получения размера
def size(self): |
|
|
|
#Счётчикдлиныстека |
|
|
|
length= |
0 |
|
|
#Начинаемобходсголовыстека |
|
||
current=_node |
self.head |
|
|
#Проходимповсемузлампока,недойдёмоконца |
|
||
while current_node is not None: |
|
||
length+= |
|
1 |
#Увеличиваемсчётчик |
current=currentnode |
_node.next |
#Переходимкследующемуузлу |
|
#Возвращаемколичествоэлементовстеке return length
Стек на основе массива
Реализация стека на основе массива
В качестве основы стека можно использовать массивы переменной длинны. В таком случае получение и добавления элемента в стек реализуются как изменение и получения элемента массива по индексу. Отдельно нужно будет рассмотреть случай необходимости увеличения размера стека. Для хранения индекса вершины стека можно использовать отдельную переменную, она же будет использована для вычисления размера стека.
В качестве основы стека берем массив нужного типа данных. Его размер будем называть capacity(емкость). Также введем дополнительную переменную size(размер), она будет указателем на вершину стека (место для добавления элемента). При создании стека size устанавливается как индекс первого элемента в массиве.
Добавление значения в стек (размер меньше емкости)
Если size меньше чем capacity, то добавляем элемент на индекс size и увеличиваем size на единицу
Добавление значения (размер равен емкости )
Если size равно capacity, то создаем новый массив размером (capacity * 3)/2 + 1. Копируем данные из базового массива в новый. Указываем, что теперь для хранения используется новый массив. Добавляем элемент на индекс size и увеличиваем size на единицу.
Получение элемента без удаления
При получении значения без удаления, сначала проверяют значение size. Если size равен первому индексу в массиве, то стек пуст. В противном случае возвращаем элемент по индексу size-1
Получение элемента c удалением
При получении значения с удалением сначала проверяют значение size. Если size равен первому индексу в массиве, то стек пуст. В противном случае уменьшаем size на единицу и возвращаем элемент по индексу size. При необходимости удаляем элемент по индексу size.
Уменьшение размера стека
В большинстве случаев стек на основе массива только увеличивает свою емкость. Автоматического уменьшения емкости не предусматривают. Для уменьшения емкости используют функцию, вызов которой осуществляется по желанию разработчика. В этой функции обычно устанавливают capacity равное size. Создают новый массив размером size. Копируют данные из основного массива в новый. Указываем что новый массив теперь используется вместо основного.
Получение размера стека
Для получения размера стека нужно использовать значение size.
