Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лекции / Лекция_9_Структуры_данных_продолжение_ipynb_Colab (1)

.pdf
Скачиваний:
0
Добавлен:
28.06.2026
Размер:
1.8 Mб
Скачать

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

Двусвязный список разновидность связанного списка. Узел содержит данные и две ссылки (указатели) на предыдущий и следующий элемент списка.

Преимущества и недостатки двусвязных списков

Преимущества:

Простота вставки и удаления элемента в начале и конце списка

Недостатки:

Сложность получения элемента по индексу Удвоенный (по сравнению с односвязным списком) расход памяти на хранение указателей

Узел списка

Узел списка представляет собой составную структуру. Обычно реализуется с помощью класса или структуры (в процедурных языках). Содержит значения двух типов. Одно для хранения данных (числа, строки и т. д.), и две ссылки на следующий узел или предыдущий узел (реализуется как указатель или ссылка, тип которых совпадает с типом узел).

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

В одной из самых распространенных реализаций двусвязного списка используются два фиктивных элемента 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.