Содержание

Связанные списки в Python 

Связанный список — это линейная структура данных, элементы которой не хранятся в непрерывном месте. Это означает, что связанный список содержит отдельные вакуоли, известные как «узлы», которые содержат данные, для которых они были созданы, и ссылку на другой узел в списке.

Некоторыми преимуществами связного списка являются его прерывистый характер и время чтения, никогда не превышающее O(n). Связанный список не нужно хранить постоянно, потому что он имеет ссылки, встроенные в каждый узел, для расположения следующего по порядку. Если в памяти вашего компьютера мало места, идеально подойдет связанный список, поскольку всю структуру не нужно хранить в одном месте. Это также означает, что чтение связанного списка является линейным, поскольку размер списка приближается к бесконечности.

Однако недостатки делают его особенным. Кроме того, если в список вносится добавление, необходимо обновить весь список, чтобы ссылки оставались правильными.

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

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

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

На изображении выше мы видим макет связанного списка. Начиная слева и продвигаясь вправо, мы видим, что заголовок списка содержит данные и указатель на следующий узел. Этот шаблон продолжается до конца списка, где он не является узлом. Значение NULL означает, что список закончился.

Посмотрите ниже код для создания односвязного списка.

# Node class
class Node:
   
    # Function to initialize the node object
    def __init__(self, data):
        self.data = data  # Assign data
        self. nextnode = None  # Initialize next as null
        
        
a = Node(1) # declaring data in each node
b = Node(2)
c = Node(3)
a.nextnode = b # link first node to second node
b.nextnode = c # link second node to third node
a.next.value # will return the value of the next node b = 2

В двусвязном списке каждый узел содержит данные, ссылку на узел, после него, и ссылку на узел, предшествующий ему.

Разница между односвязным списком и двусвязным списком — это «предыдущие» стрелки, указанные на рисунке выше. Преимущества дополнительного указателя означают, что список можно перемещать в любом направлении при доступе к информации — это означает, что переход от узла 5 к узлу 8 может быть проще, чем обход с начала списка.

Новые узлы в начале и в конце списка называются «дозорными». Эти узлы заменяют значения NULL, которые вы видите выше. Они позволяют быстро вставлять или удалять вверху или внизу списка.

Посмотрите ниже код для двусвязного списка.

# Code to create the nodes for a doubly linked list
# A linked list node
class Node:
    def __init__(self):
        self. data = None # container of the data
        self.next_node = None # standard pointer to next node
        self.prev_node = None # pointer to previous node
        
        
# Creating each node
a = Node(1)
b = Node(2)
c = Node(3)
# Connect the nodes
# Connect nodes a & b to each other in both directions
a.next_node = b
b.prev_node = a
# Connect nodes b & c to each other in both directions
b.next_node = c
c.prev_node = b
"""
We could create a circularly linked list by connecting the head of the list
to the tail
"""

Теперь, когда у нас есть код, необходимый для создания связанных списков, давайте рассмотрим некоторые общие вопросы собеседования, варианты которых вы можете найти на Leetcode.

1. Сторнирование связанного списка

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

Уловка, стоящая за этой проблемой, заключается в том, что разворот должен происходить в постоянном пространстве.  Поэтому мы не создаем новый список и не добавляем или не используем прокси. Это необходимо сделать, работая непосредственно с этим списком.

def reversal(head):
  
  # set variables for the node position
  current = head # 
  prev = None
  next_node = None
  
  # let's us know that we haven't reached the end of the list
  while current:
    
    # copy the pointer of the head before we override it
    next_node = current.nextnode
    
    # change the pointer to point at None (because it is the new tail)
    current.nextnode = prev
    
    # move us along on the list, current becomes previous
    prev = current
    # and the next node becomes the current node we are looking at
    current = nextnode
    
    return prev

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

 Посмотрите картинку ниже для дальнейшей визуализации.

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

2. Связанный список с N-го до последнего узла

Создайте функцию, которая принимает головной узел и целое число n, а затем возвращает n-й и последний узел из списка.

Уловка для этой задачи состоит в том, чтобы пройти по списку двумя маркерами. Первый маркер выполняет итерацию и проверяет, действительно ли существует предпоследний узел. Второй маркер идет позади и заканчивается в этом узле.

def nth_last_node(n,head):
  
  # create two pointers that both start at the head of the list
  pointer_1 = head
  pointer_2 = head
  
  
  # we will iterate through the list, moving the right pointer
  # until there are n-spaces between them
  for i in range(n-1):
    
    # edge case, if no nextnode, raise an error
    if not pointer_1. nextnode:
      raise LookupError('Error: Command continues past index of list')
      
    # if not edge case, move the right pointer to next node
    pointer_1 = pointer_1.nextnode
    # now there is a n-size block between them
   
  # as long as the right pointer does not reach the end of the list
  # the left pointer will move
    while pointer_1.nextnode:
      
      # once the right pointer hits the end the left pointer has found the
      # nth to last node from the end
      pointer_2 = pointer_2.nextnode
      pointer_1 = pointer_1.nextnode
   
  
  # return the nth to last node
  return pointer_2
    

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

Как вы можете видеть между первым и вторым шагами, идея состоит в том, что указатель 1 должен создавать расстояние между собой и указателем 2, равное n узлам. Как только это безопасно, два указателя могут перемещаться в унисон — указатель 1 проверяет, не закончился ли список, и указатель 2 готов к объявлению своей текущей позиции как n-го до последнего узла.

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

16. Связные списки — Учимся с Python

16.1. Знакомьтесь: связный список

Мы уже видели примеры атрибутов, которые ссылаются на другие объекты. Ссылки на объекты использует и широко известная структура данных связный список

.

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

Связный список относится к рекурсивным структурам данных, поскольку его определение рекурсивно.

Связный список представляет собой либо

  • пустой список, представленный значением None, либо
  • узел, содержащий данные и ссылку на связный список.

Рекурсивные структуры данных могут обрабатываться рекурсивными методами.

16.2. Класс Node

Как обычно при определении нового класса, мы начнем с инициализирующего метода и метода __str__, обеспечив возможность создания и отображения объектов нового класса:

class Node:
    def __init__(self, cargo=None, next=None):
        self. cargo = cargo
        self.next  = next
    def __str__(self):
        return str(self.cargo)

Параметры инициализирующего метода опциональны. По умолчанию и данные, cargo, и ссылка, next, получают значения None.

Строковым представлением узла будет строковое представление данных этого узла. Поскольку функции str можно передать любое значение, в нашем узле можно хранить любое значение.

Для тестирования создадим объект Node и выведем его на печать:

>>> node = Node("test")
>>> print node
test

Чтобы было интереснее, нам нужен список с более чем одним узлом:

>>> node1 = Node(1)
>>> node2 = Node(2)
>>> node3 = Node(3)

Этот код создает три узла, но у нас нет списка, так как ни один узел не связан с другим. Посмотрите на рисунок:

Чтобы связать узлы, нам нужно заставить первый узел ссылаться на второй, а второй — на третий:

>>> node1.next = node2
>>> node2.next = node3

Ссылка в третьем узле имеет значение None, что означает конец списка.

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

16.3. Списки как коллекции

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

Чтобы передать связный список в качестве аргумента, достаточно передать ссылку на первый узел. Например, функция print_list принимает один узел в качестве аргумента. Начиная с первого узла списка, она выводит на печать каждый узел, пока не достигнет конца списка:

def print_list(node):
    while node:
        print node,
        node = node.next
    print

Вызовем функцию, передав ей ссылку на первый узел:

>>> print_list(node1)
1 2 3

Хотя у print_list есть ссылка на первый узел списка, внутри функции нет переменных, ссылающихся на другие узлы. Функция получает ссылку на следующий узел, используя значение атрибута next каждого узла.

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

16.4. Списки и рекурсия

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

  1. Разделить список на две части: первый узел (голова) и все остальные (хвост).
  2. Распечатать хвост в обратном порядке.
  3. Распечатать голову.

Разумеется, шаг 2, рекурсивный вызов, предполагает, что у нас имеется способ распечатать список в обратном порядке. Но если мы примем, что рекурсивный вызов работает – нужно в это поверить – то мы можем убедиться в том, что алгоритм работает.

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

def print_backward(list):
    if list == None: return
    head = list
    tail = list. next
    print_backward(tail)
    print head,

Первая строка обрабатывает базовый случай. Две следующие строки кода разделяют список на head (англ.: голова) и tail (англ.: хвост). Две последние строки кода выводят список на печать. Запятая в конце последней строки удерживает Python от перевода строки после печати каждого узла.

Вызовем функцию так же, как вызывали print_list:

>>> print_backward(node1)
3 2 1

В результате на печать выведен список в обратном порядке.

Возможно, вы недоумеваете, почему print_list и print_backward являются функциями, а не методами класса Node. Причина в том, что мы используем None для представления пустого списка, а вызвать метод для None нельзя. Это ограничение не позволяет написать чистый объектно-ориентированный код для манипулирования списком.

Но можем ли мы доказать, что print_backward всегда завершится? Другими словами, всегда ли алгоритм приведет нас к базовому случаю? На самом деле, ответ отрицательный. Некоторые списки сломают наш метод.

16.5. Бесконечные списки

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

Если вызвать print_list с этим списком, функция зациклится. Если вызвать print_backward, возникнет бесконечная рекурсия. Таким образом, работа с бесконечными списками сопряжена с определенными сложностями.

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

И все же тот факт, что мы не можем доказать, что print_list и print_backward завершатся, представляет проблему. Лучшее, что мы можем сделать, это выдвинуть гипотезу: Если список не содержит циклов, то эти методы завершатся. Такого рода утверждения называются предусловием. Предусловие налагает ограничение на параметры и описывает поведение функции в случае, когда это ограничение выполняется. Вскоре мы встретимся с другими примерами предусловий.

16.6. Неоднозначность ссылки на узел списка

Следующий фрагмент print_backward может вызвать удивление:

head = list
tail = list.next

После выполнения первого предложения присваивания head и list имеют один и тот же тип и одно и то же значение. В таком случае, зачем мы создаем новую переменную?

Причина в том, что эти две переменные играют разные роли. Мы думаем о head как о ссылке на один узел, а о list – как о ссылке на первый узел списка. Эти роли не являются частью программы; они существуют в уме программиста.

В общем случае, глядя на код программы, мы не можем сказать, какую роль играет та или иная переменная. Эта неоднозначность может быть полезной, но также может затруднить чтение программы. Часто, чтобы подчеркнуть роль, которую играет переменная, мы используем переменные с “говорящими” именами, например, node и list, а иногда создаем дополнительные переменные с этой целью.

Можно переписать print_backward без переменных head и tail, что сделает функцию более компактной, но менее ясной:

def print_backward(list) :
    if list == None : return
    print_backward(list.next)
    print list,

Глядя на два вызова функции, нужно помнить, что print_backward рассматривает свой аргумент как коллекцию, а print – как единичный объект.

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

16.7. Изменение списков

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

В качестве примера давайте напишем функцию, которая удаляет второй узел из списка и возвращает ссылку на удаленный узел:

def remove_second(list):
    if list == None: return
    first = list
    second = list. next
    # make the first node refer to the third
    first.next = second.next
    # separate the second node from the rest of the list
    second.next = None
    return second

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

>>> print_list(node1)
1 2 3
>>> removed = remove_second(node1)
>>> print_list(removed)
2
>>> print_list(node1)
1 3

Следующий рисунок иллюстрирует результат работы функции remove_second:

А что случится, если вызвать эту функцию со списком из одного элемента? С пустым списком? Существует ли предусловие для этой функции? Если да, то добавьте в функцию проверку предусловия и обработку его нарушения.

16.8. Обертки и помощники

Если нам понадобится вывести связный список в обратном порядке, заключенный в квадратные скобки, то, как вариант, мы можем воспользоваться функцией print_backward чтобы вывести 3 2 1, и отдельно вывести открывающую и закрывающую скобки. Назовем новую функцию print_backward_nicely:

def print_backward_nicely(list) :
    print "[",
    print_backward(list)
    print "]",

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

16.9. Класс LinkedList

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

Создадим новый класс LinkedList. Его атрибутами будут целое число, представляющее длину списка, и ссылка на первый узел списка. С помощью объектов LinkedList удобно манипулировать списками объектов Node:

class LinkedList:
    def __init__(self):
        self.length = 0
        self.head   = None

Класс LinkedList оказывается удобным местом для помещения в него таких функций, как print_backward_nicely, и превращения их в методы этого класса:

class LinkedList:
    . ..
    def print_backward(self):
        print "[",
        if self.head != None:
            self.head.print_backward()
        print "]",
class Node:
    ...
    def print_backward(self):
        if self.next != None:
            tail = self.next
            tail.print_backward()
        print self.cargo,

Мы переименовали print_backward_nicely, и теперь у нас два метода с именами print_backward: один в классе Node (помощник), и один в классе LinkedList (обертка). Когда метод-обертка вызывает self.head.print_backward, он вызывает метод-помощник, поскольку self.head ссылается на объект Node.

Еще один плюс класса LinkedList состоит в том, что он облегчает добавление и удаление первого элемента списка. Например, следующий метод add_first класса LinkedList принимает в качестве аргумента данные для узла и помещает новый узел с этими данными в начало списка:

class LinkedList:
    ...
    def add_first(self, cargo):
        node = Node(cargo)
        node.next = self.head
        self. head = node
        self.length += 1

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

>>> linkedlist = LinkedList()
>>> linkedlist.add_first(1)
>>> linkedlist.add_first(2)
>>> linkedlist.add_first(3)
>>> linkedlist.print_backward()
[ 1 2 3 ]

Поскольку каждый раз новый узел добавляется в начало списка, то последовательно добавленные нами узлы со значениями 1, 2, 3 расположились в обратном порядке, и метод print_backward вывел узлы в порядке их добавления.

В качестве упражнений вам будет предложено добавить другие полезные методы в класс LinkedList и поэкспериментировать с ними.

16.10. Инварианты

Некоторые списки построены правильно; другие нет. Например, если список содержит цикл, он сломает многие из наших методов. Поэтому мы можем потребовать, чтобы списки не содержали циклов. Другое разумное требование состоит в том, чтобы значение length в объекте LinkedList всегда равнялось реальному числу узлов в списке.

Требования, подобные этим, называются инвариантами, поскольку, в идеале, они должны выполняться для каждого объекта в любой момент времени. Указание инвариантов для объектов — полезный прием программирования. Он облегчает доказательство корректности кода, проверку целостности структур данных, и способствует обнаружению ошибок.

Заметим, что в отдельные моменты времени инварианты все же не выполняются. Например, в середине метода addFirst, после того, как мы добавили узел, но перед тем, как мы увеличили length, инвариант оказывается нарушенным. Такого рода нарушение приемлемо; действительно, часто невозможно изменить объект, не нарушая инвариант хотя бы ненадолго. Достаточно потребовать, чтобы метод, который нарушает инвариант, восстанавливал его.

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

16.11. Глоссарий

инвариант
Утверждение, которое должно выполняться для объекта в любое время (за исключением того времени, когда объект изменяется).
обертка
Метод, который выступает посредником между вызывающим кодом и методом-помощником; способствует упрощению и повышению надежности кода.
помощник
Метод, не вызываемый непосредственно, а используемый вызываемым методом для выполнения части работы.
предусловие
Утверждение, которое должно быть справедливым, для того чтобы функция выполнялась корректно.
связный список
Структура данных, реализующая коллекцию, используя последовательность связанных узлов.
узел списка
Элемент списка, обычно реализуемый как объект, содержащий ссылку на объект того же типа и некоторые полезные данные.

16.12. Упражнения

  1. Традиционно списки выводятся на печать в скобках, с запятыми между элементами, например: [1, 2, 3]. Добавьте в класс LinkedList метод print_list так, чтобы он возвращал список в таком формате.
  2. Добавьте в класс LinkedList метод last, который вернет последний узел в списке или None в случае, когда список пуст. Протестируйте работу метода для пустого и непустого списков.
  3. Добавьте в класс LinkedList метод append(self, cargo) для добавления узла в конец списка. Используйте метод list из предыдущего упражнения для получения узла, к которому будет добавляться новый узел. Протестируйте работу метода.
  4. Добавьте в класс LinkedList метод find(self, cargo) для поиска в списке узла с cargo, ближайшего к началу списка. Протестируйте метод для пустого списка, списка, содержащего узел с искомым cargo, и для списка, не содержащего такого узла.
  5. Добавьте в класс LinkedList метод __contains__(self, cargo) чтобы реализовать оператор in для LinkedList. Используйте метод find из предыдущего упражнения в методе __contains__. Протестируйте работу оператора in для пустого списка, списка, содержащего узел с cargo, и списка, не содержащего такого узла.

Связанные списки Python — pythobyte.com

Автор оригинала: Frank Hofmann.

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

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

Один элемент списка называется узлом. Узлы не похожи на массивы, которые последовательно хранятся в памяти. Вместо этого он, скорее всего, найдет их в разных сегментах памяти, которые вы можете найти, следуя указателям от одного узла к другому. Обычно конец списка помечается нулевым элементом, представленным эквивалентом Python None .

Рисунок 1: Односвязный список

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

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

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

Язык Python не содержит предопределенного типа данных для связанных списков. Чтобы справиться с этой ситуацией, мы должны либо создать свой собственный тип данных, либо использовать дополнительные модули Python, которые обеспечивают реализацию такого типа данных.

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

Шаг 1: Узел как структура данных

Чтобы иметь структуру данных, с которой мы можем работать, мы определяем узел. Узел реализуется как класс с именем ListNode . Класс содержит определение для создания экземпляра объекта, в данном случае с двумя переменными – data для сохранения значения узла и next для хранения ссылки на следующий узел в списке. Кроме того, узел имеет следующие методы и свойства:

  • __init_() : инициализировать узел с данными
  • self.data : значение, хранящееся в узле
  • self.next : опорный указатель на следующий узел
  • has_value() : сравнение значения со значением узла

Эти методы гарантируют, что мы можем правильно инициализировать узел с нашими данными ( __init__ () ) и покрывать как извлечение данных, так и их хранение (через свойство self. data ), а также получение ссылки на подключенный узел (через свойство self.next ). Метод has_value() позволяет сравнить значение узла со значением другого узла.

Листинг 1: Класс ListNode

class ListNode:
    def __init__(self, data):
        "constructor to initiate this object"
        
        # store data
        self.data = data
        
        # store reference (next item)
        self.next = None
        return
    
    def has_value(self, value):
        "method to compare the value with the node data"
        if self.data == value:
            return True
        else:
            return False

Создание узла так же просто, как и создание экземпляра объекта класса ListNode :

Листинг 2: Создание экземпляров узлов

node1 = ListNode(15)
node2 = ListNode(8. 2)
node3 = ListNode("Berlin")

Сделав это, мы имеем в наличии три экземпляра класса ListNode . Эти экземпляры представляют собой три независимых узла, которые содержат значения 15 (целое число), 8.2 (поплавок) и “Берлин” (строка).

Шаг 2: Создание класса для односвязного списка

В качестве второго шага мы определяем класс с именем Single Linked List , который охватывает методы, необходимые для управления узлами списка. Он содержит эти методы:

  • __init__() : инициировать объект
  • list_length() : возвращает количество узлов
  • output_list() : выводит значения узлов
  • add_list_item() : добавить узел в конец списка
  • unordered_search() : поиск в списке узлов с заданным значением
  • remove_list_item_by_id() : удалить узел в соответствии с его идентификатором

Мы рассмотрим каждый из этих методов шаг за шагом.

Метод __init__() определяет две внутренние переменные класса с именами head и tail . Они представляют собой начальный и конечный узлы списка. Изначально и head , и tail имеют значение None до тех пор, пока список пуст.

Листинг 3: Класс Single Linked List (часть первая)

class SingleLinkedList:
    def __init__(self):
        "constructor to initiate this object"
        
        self.head = None
        self.tail = None
        return

Шаг 3: Добавление узлов

Добавление элементов в список осуществляется через add_list_item() . Этот метод требует узла в качестве дополнительного параметра. Чтобы убедиться, что это правильный узел (экземпляр класса ListNode ), параметр сначала проверяется с помощью встроенной функции Python isinstance() . В случае успеха узел будет добавлен в конец списка. Если item не является ListNode , то он создается.

В случае, если список (все еще) пуст, новый узел становится главой списка. Если узел уже находится в списке, то значение tail корректируется соответствующим образом.

Листинг 4: Класс Single Linked List (часть вторая)

    def add_list_item(self, item):
        "add an item at the end of the list"
        
        if not isinstance(item, ListNode):
            item = ListNode(item)
        if self.head is None:
            self.head = item
        else:
            self.tail.next = item
        self.tail = item
            
        return

Метод list_length() подсчитывает узлы и возвращает длину списка. Чтобы перейти от одного узла к следующему в списке, в игру вступает свойство узла self.next и возвращает ссылку на следующий узел. Подсчет узлов выполняется в цикле while до тех пор, пока мы не достигнем конца списка, который представлен ссылкой None на следующий узел.

Листинг 5: Класс Единого связанного списка (часть третья)

    def list_length(self):
        "returns the number of list items"
        
        count = 0
        current_node = self.head
        
        while current_node is not None:
            # increase counter by one
            count = count + 1
            
            # jump to the linked node
            current_node = current_node. next
            
        return count

Метод output_list() выводит значения узлов с помощью свойства node data . Опять же, для перехода от одного узла к другому используется ссылка, предоставляемая через свойство next .

Листинг 6: Класс Single Linked List (часть четвертая)

    def output_list(self):
        "outputs the list (the value of the node, actually)"
        
         current_node = self.head
        
        while current_node is not None:
            print(current_node.data)
            
            # jump to the linked node
            current_node = current_node.next
            
        return

На основе класса SingleLinkedList мы можем создать правильный список с именем track и играть с его методами , как уже описано выше в Listings 3-6 . Поэтому мы создаем четыре узла списка, оцениваем их в цикле for и выводим содержимое списка. Листинг 7 показывает, как это запрограммировать, а Листинг 8 показывает выходные данные.

Листинг 7: Создание узлов и вывод списка

# create four single nodes
node1 = ListNode(15)
node2 = ListNode(8.2)
item3 = "Berlin"
node4 = ListNode(15)
track = SingleLinkedList()
print("track length: %i" % track.list_length())
for current_item in [node1, node2, item3, node4]:
    track.add_list_item(current_item)
    print("track length: %i" % track.list_length())
    track.output_list()

Вывод выглядит следующим образом и показывает, как растет список:

Листинг 8: Добавление узлов в список

$ python3 simple-list. py
track length: 0
track length: 1
15
track length: 2
15
8.2
track length: 3
15
8.2
Berlin
track length: 4
15
8.2
Berlin
15

Шаг 4: Поиск по списку

Поиск по всему списку осуществляется с помощью метода unordered_search() . Для поиска значения требуется дополнительный параметр. Глава списка-это отправная точка.

Во время поиска мы подсчитываем узлы. Чтобы указать совпадение, мы используем соответствующий номер узла. Метод unordered_search() возвращает список номеров узлов, представляющих совпадения. Например, и первый, и четвертый узел содержат значение 15. Поиск 15 результатов в списке с двумя элементами: [1, 4] .

Листинг 9: Метод поиска unordered_search()

    def unordered_search (self, value):
        "search the linked list for the node that has this value"
        
        # define current_node
        current_node = self. head
        
        # define position
        node_id = 1
        
        # define list of results
        results = []
        
        while current_node is not None:
            if current_node.has_value(value):
                results.append(node_id)
                
            # jump to the linked node
            current_node = current_node.next
            node_id = node_id + 1
        
        return results

Шаг 5: Удаление элемента из списка

Удаление узла из списка требует корректировки только одной ссылки – та, которая указывает на удаляемый узел, теперь должна указывать на следующий. Эта ссылка хранится удаляемым узлом и должна быть заменена. В фоновом режиме сборщик мусора Python заботится о несвязанных объектах и приводит их в порядок.

Следующий метод называется remove_list_item_by_id() . В качестве параметра он ссылается на номер узла, аналогичный значению, возвращаемому функцией unordered_search() .

Листинг 10: Удаление узла по номеру узла

    def remove_list_item_by_id(self, item_id):
        "remove the list item with the item id"
        
        current_id = 1
        current_node = self. head
        previous_node = None
        
        while current_node is not None:
            if current_id == item_id:
                # if this is the first node (head)
                if previous_node is not None:
                    previous_node.next = current_node.next
                else:
                    self.head = current_node.next
                    # we don't have to look any further
                    return
            
            # needed for the next iteration
            previous_node = current_node
            current_node = current_node.next
            current_id = current_id + 1
        
        return

Шаг 6: Создание Двусвязного списка

Чтобы создать двусвязный список, естественно просто расширить класс ListNode , создав дополнительную ссылку на предыдущий узел. Это влияет на методы добавления, удаления и сортировки узлов. Как показано в Листинге 11 , для хранения ссылочного указателя на предыдущий узел в списке было добавлено новое свойство с именем previous . Мы изменим наши методы, чтобы использовать это свойство также для отслеживания и обхода узлов.

Листинг 11: Класс узлов расширенного списка

class ListNode:
    def __init__(self, data):
        "constructor class to initiate this object"
        # store data
        self.data = data
        
        # store reference (next item)
        self.next = None
        # store reference (previous item)
        self.previous = None
        return
    def has_value(self, value):
        "method to compare the value with the node data"
        if self.data == value:
            return True
        else:
            return False

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

Листинг 12: Класс Двойного Связанного списка

class DoubleLinkedList:
    def __init__(self):
        "constructor to initiate this object"
        self. head = None
        self.tail = None
        return
    def list_length(self):
        "returns the number of list items"
        
        count = 0
        current_node = self.head
        while current_node is not None:
            # increase counter by one
            count = count + 1
            
            # jump to the linked node
            current_node = current_node.next
        
        return count
    def output_list(self):
        "outputs the list (the value of the node, actually)"
        current_node = self.head
        while current_node is not None:
            print(current_node.data)
            # jump to the linked node
            current_node = current_node.next
        
        return
    def unordered_search (self, value):
        "search the linked list for the node that has this value"
        # define current_node
        current_node = self.head
        # define position
        node_id = 1
        # define list of results
        results = []
        while current_node is not None:
            if current_node. has_value(value):
                results.append(node_id)
            
            # jump to the linked node
            current_node = current_node.next
            node_id = node_id + 1
        
        return results

Как было описано ранее, добавление узлов требует немного больше действий. Листинг 13 показывает, как это реализовать:

Листинг 13: Добавление узлов в двусвязный список

    def add_list_item(self, item):
        "add an item at the end of the list"
        if isinstance(item, ListNode):
            if self.head is None:
                self.head = item
                item.previous = None
                item.next = None
                self. tail = item
            else:
                self.tail.next = item
                item.previous = self.tail
                self.tail = item
        
        return

При удалении элемента из списка необходимо учитывать аналогичные затраты. Листинг 14 показывает, как это сделать:

Листинг 14: Удаление элемента из двусвязного списка

    def remove_list_item_by_id(self, item_id):
        "remove the list item with the item id"
        
        current_id = 1
        current_node = self.head
        while current_node is not None:
            previous_node = current_node.previous
            next_node = current_node.next
            if current_id == item_id:
                # if this is the first node (head)
                if previous_node is not None:
                    previous_node. next = next_node
                    if next_node is not None:
                        next_node.previous = previous_node
                else:
                    self.head = next_node
                    if next_node is not None:
                        next_node.previous = None
                # we don't have to look any further
                return
 
            # needed for the next iteration
            current_node = next_node
            current_id = current_id + 1
                
        return

В листинге 15 показано, как использовать класс в программе Python.

Листинг 15: Построение двусвязного списка

# create three single nodes
node1 = ListNode(15)
node2 = ListNode(8. 2)
node3 = ListNode("Berlin")
node4 = ListNode(15)
track = DoubleLinkedList()
print("track length: %i" % track.list_length())
for current_node in [node1, node2, node3, node4]:
    track.add_list_item(current_node)
    print("track length: %i" % track.list_length())
    track.output_list()
results = track.unordered_search(15)
print(results)
track.remove_list_item_by_id(4)
track.output_list()

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

Шаг 7: Создание Двусвязных списков с помощью deque

Поскольку другие инженеры столкнулись с той же проблемой, мы можем упростить вещи для себя и использовать одну из немногих существующих реализаций. В Python мы можем использовать объект deque из модуля collections . Согласно документации модуля:

Деки-это обобщение стеков и очередей (название произносится как “колода” и является сокращением от “двусторонняя очередь”). Deques поддерживают потокобезопасные, эффективные с точки зрения памяти приложения и всплывающие окна с обеих сторон deque с примерно одинаковой производительностью O(1) в любом направлении.

Например, этот объект содержит следующие методы:

  • append() : добавить элемент в правую часть списка (конец)
  • append_left() : добавить элемент в левую часть списка (head)
  • clear() : удалить все элементы из списка
  • count() : подсчет количества элементов с определенным значением
  • count() : подсчет количества элементов с определенным значением
  • count() : подсчет количества элементов с определенным значением
  • pop() : удаление элемента из правой части списка (конец)
  • pop left() : удаление элемента из левой части списка (head)
  • remove() : удаление элемента из списка
  • reverse() : перевернуть список

Базовая структура данных deque представляет собой список Python, который является двусвязным. Первый узел списка имеет индекс 0. Использование deque приводит к значительному упрощению класса ListNode . Единственное, что мы сохраняем, – это переменная класса data для хранения значения узла. Листинг 16 выглядит следующим образом:

Листинг 16: Класс ListNode с deque (упрощенный)

from collections import deque
class ListNode:
    def __init__(self, data):
        "constructor class to initiate this object"
        
        # store data
        self.data = data
        
        return

Определение узлов не меняется и аналогично Листингу 2 . С учетом этих знаний мы создаем список узлов следующим образом:

Листинг 17: Создание списка с помощью deque

track = deque([node1, node2, node3])
print("three items (initial list):")
for item in track:
    print(item. data)

Добавление элемента в начало списка работает с помощью метода append_left() как показано в листинге 18 :

Листинг 18: Добавление элемента в начало списка

# add an item at the beginning
node4 = ListNode(15)
track.append_left(node4)
print("four items (added as the head):")
for item in track:
    print(item.data)

Аналогично, append() добавляет узел в конец списка, как показано в Листинге 19 :

Листинг 19: Добавление элемента в конец списка

# add an item at the end
node5 = ListNode("Moscow")
print("five items (added at the end):")
track. append(node5)
for item in track:
    print(item.data)

Вывод

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

Для дальнейшего чтения и альтернативных реализаций вы можете посмотреть здесь:

Признание

Автор хотел бы поблагодарить Герольда Рупрехта и Мэнди Ноймайер за их поддержку и комментарии при подготовке этой статьи.

что это такое и как его реализовать

Давайте поговорим немного о связных списках. Вероятно, вы о них слышали. Скажем, на лекциях по структурам данных. И возможно, вы думали, что это как-то сильно заумно. Почему бы не использовать массив? На самом деле связные списки имеют некоторые преимущества перед массивами и простыми списками. Поначалу эта тема может показаться сложной, но не волнуйтесь: мы все разберем.

Если вы представите себе фотографию людей, взявшихся за руки в хороводе, вы получите примерное представление о такой структуре, как связный список. Это некоторое количество отдельных узлов, связанных между собой ссылками, т. е. ссылками на другие узлы. Связные списки бывают двух видов: однонаправленные и двунаправленные (односвязные и двусвязные).

Источник: Medium.com

В односвязных списках каждый узел имеет одну стрелку, указывающую вперед, а в двусвязных узлы имеют еще и стрелки, указывающие назад. Но на собеседованиях в вопросах, касающихся связных списков, чаще всего имеются в виду односвязные. Почему? Их проще реализовать. Да, у вас не будет возможности перемещаться по списку в обратном направлении, но для отслеживания узлов хватит и однонаправленных связей.

Давайте попробуем реализовать связный список на Python. Возможно, вы бы начали с class Linked_list, а затем создали в нем узлы, но все можно сделать куда проще. Представьте цепочку из скрепок. Мы берем кучку скрепок и соединяем их, цепляя друг за дружку.

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

 class Node:

Далее, как обычно при создании класса на Python, мы создаем метод __init__. Этот метод инициализирует все поля, например, переменные в классе, при создании каждого нового объекта Node.

Мы будем принимать переменную data — это значение, которое мы хотим сохранять в узле. Также нам нужно определить ссылку, направленную вперед, она традиционно называется next. Сначала узел не связан ни с чем, поэтому мы устанавливаем next в None.

class Node:
    def __init__(self, data):
        self. data = data
        self.next = None

Это почти все, что нам нужно. Можно оставить, как есть, но в книге «Cracking the Coding Interview» также реализуется метод appendToTail(), который создает новый узел и добавляет его в конец списка, проходя его, так что нам не приходится делать это вручную.

Начнем с определения этого метода в классе Node. Метод будет принимать значение, которое мы хотим поместить в новый узел, и ключевое слово self (это специфично для Python).

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None
    def append(self, val):
        pass

Первое, что мы делаем, это создаем новый узел с заданным значением. Назовем его end.

def append(self, val):
    end = Node(val)

Затем мы создаем указатель (поинтер). Это может звучать слишком технически, но по сути мы создаем ссылку на головной элемент (head) нашего списка. Мы делаем так, потому что хотим проходить по списку, не переназначая в нем ничего. Итак, мы делаем ссылку на первый узел, self, и сохраняем его в переменной n.

def append(self, val):
    end = Node(val)
    n = self

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

def append(self, val):
    end = Node(val)
    n = self
    while (n. next):
        n = n.next

Наконец, мы указываем на последний узел, за которым нет следующего узла. Мы просто берем end — наш новый узел — и устанавливаем n.next = end.

def append(self, val):
    end = Node(val)
    n = self
    while (n.next):
        n = n.next
    n.next = end

Вот и все! Вот как выглядит наш класс полностью:

list_node.py

class Node:
    def __init__(self, data):
        self.next = None
        self.data = data
    def append(self, val):
        end = Node(val)
        n = self
        while (n.next):
            n = n.next
        n. next = end

Проверяем наш связный список

Давайте все это проверим. Начнем с создания нового объекта Node. Назовем его ll (две латинские буквы «l» в нижнем регистре как сокращение Linked List). Назначим ему значение 1.

 ll = Node(1)

Поскольку мы написали такой классный метод append(), мы можем вызывать его для добавления в наш список новых узлов.

 ll.append(2)
 ll.append(3)

Как нам увидеть, как выглядит наш список? Теоретически, выглядеть он должен следующим образом:

  [1] --> [2] --> [3]

Но нет способа вывести его именно в таком виде. Нам нужно пройти список, выводя каждое значение. Вы же помните, как проходить список? Мы только что это делали. Повторим:

  1. Создаем переменную, указывающую на head.
  2. Если есть следующий узел, перемещаемся к этому узлу.

И просто выводим data в каждом узле. Мы начинаем с шага № 1: создаем новую переменную и назначаем ее головным элементом списка.

 node = ll

Далее мы выводим первый узел. Почему мы не начали с цикла while? Цикл while проитерируется только дважды, потому что только у двух узлов есть next (у последнего узла его нет). В информатике это называется ошибкой на единицу (когда нужно сделать что-то Х раз плюс 1). Это можно представить в виде забора. Вы ставите столб, затем секцию забора, и чередуете пару столб + секция столько раз, сколько нужно по длине.

Но вы не можете оставить последнюю секцию забора висящей в воздухе. Ограда должна закончиться столбом, а не секцией. Поэтому вам приходится либо добавлять еще один столб в конце, либо (что в информатике более распространено) начать с постановки столба, а затем добавлять пары секция + столб. Это мы и сделаем.

Для начала мы выведем первый узел, а затем запустим цикл while для вывода всех последующих узлов.

node = ll
print(node. data)
while node.next:
    node = node.next
    print(node.data)

Запустив это для нашего предыдущего списка, мы получим в консоли:

 1
 2
 3

Ура! Наш связный список работает.

Зачем уметь создавать связный список на Python?

Зачем вообще может понадобиться создавать собственный связный список на Python? Это хороший вопрос. Использование связных списков имеет некоторые преимущества по сравнению с использованием просто списков Python.

Традиционно вопрос звучит как «чем использование связного списка лучше использования массива». Основная идея в том, что массивы в Java и других ООП-языках имеют фиксированный размер, поэтому для добавления элемента приходится создавать новый массив с размером N + 1 и помещать в него все значения из предыдущего массива. Пространственная и временная сложность этой операции — O(N). А вот добавление элемента в конец связного списка имеет постоянную временную сложность (O(1)).

Списки в Python это не настоящие массивы, а скорее реализация динамического массива, что имеет свои преимущества и недостатки. В Википедии есть таблица со сравнением производительности связных списков, массивов и динамических массивов.

Если вопрос производительности вас не тревожит, тогда да, проще реализовать обычный список Python. Но научиться реализовывать собственный связный список все равно полезно. Это как изучение математики: у нас есть калькуляторы, но основные концепции мы все-таки изучаем.

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

Связанные списки в деталях с примерами Python: одинарные связанные списки

Связанные списки — одна из наиболее часто используемых структур данных в любом языке программирования. В этой статье мы подробно изучим связанные списки. Мы увидим, какие существуют типы связанных списков, как перемещаться по связанному списку, как вставлять и удалять элементы из связанного списка, каковы различные методы сортировки связанного списка, как перевернуть связанный список и т. Д. .

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

Что такое связанный список?

Прежде чем мы изучим, что такое связанные списки, давайте сначала кратко рассмотрим, как массивы хранят данные. В массивах данные хранятся в непрерывных ячейках памяти. Например, если первый элемент в массиве хранится в индексе 10 памяти и имеет размер 15 байт, второй элемент будет сохранен в индексе 10 + 15 + 1 = 26-й индекс. Следовательно, можно легко пройти по массиву.

Чтобы найти третий элемент в массиве, вы можете просто использовать начальный индекс первого элемента плюс размер первого элемента плюс размер второго элемента плюс 1.

Как связанные списки хранят данные

Связанные списки, с другой стороны, разные. Связанные списки не хранят данные в непрерывных ячейках памяти. Для каждого элемента в ячейке памяти связанный список хранит значение элемента и ссылку или указатель на следующий элемент. Одна пара элемента связанного списка и ссылка на следующий элемент составляет узел.

Например, если узел состоит из 34 | 10, это означает, что значение узла равно 30, а следующий элемент сохраняется в ячейке памяти «10». Чтобы пройти по связанному списку, вам просто нужно знать местоположение в памяти или ссылку на первый узел, остальные узлы могут быть последовательно перемещены, используя ссылку на следующий элемент в каждом узле.

Ссылка на первый узел также известна как начальный узел.

Связанные списки и массивы:
  • Связанный список — это динамическая структура данных, что означает, что память, зарезервированная для списка ссылок, может быть увеличена или уменьшена во время выполнения. Память для структуры данных связанного списка заранее не выделяется. Всякий раз, когда требуется добавить новый элемент в связанный, память для нового узла создается во время выполнения. С другой стороны, в случае массива память должна быть выделена заранее для определенного количества элементов. В случаях, когда недостаточно элементов для заполнения всего индекса массива, память тратится впустую.
  • Поскольку для массивов требуются непрерывные ячейки памяти, очень сложно удалить или вставить элемент в массив, так как ячейки памяти большого количества элементов должны быть обновлены. С другой стороны, элементы связанного списка не хранятся в непрерывной области памяти, поэтому вы можете легко обновить связанные списки.
  • Благодаря своей гибкости связанный список больше подходит для реализации таких структур данных, как стеки, очереди и списки.

Однако у связанного списка есть и недостатки.

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

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

  • Единый связанный список
  • Двусвязный список
  • Циркулярный связанный список
  • Связанный список с заголовком
  • Отсортированный связанный список

В этой первой части статьи мы сосредоточимся на едином связном списке и его различных операциях.

Единый связанный список
Создание класса узла
Создание класса единого связанного списка
Обход элементов связанного списка
Вставка элементов
Подсчет элементов
Поиск элементов
Создание связного списка
Удаление элементов
Обращение связного списка

Единый связанный список

Единый связанный список — самый простой из всех вариантов связанных списков. Каждый узел в едином связанном списке содержит элемент и ссылку на следующий элемент, и все.

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

Создание класса узла

Первое, что вам нужно сделать, это создать класс для узлов. Объекты этого класса будут фактическими узлами, которые мы вставим в наш связанный список. Мы знаем, что узел для единственного связанного списка содержит элемент и ссылку на следующий узел. Следовательно, наш класс узла будет содержать две переменные-члены: item и ref . Значение item будет установлено значением, переданным через конструктор, в то время как ссылка будет изначально установлена на null.

Выполните следующий скрипт:

 class Node: 
 def __init__(self, data): 
 self.item = data 
 self.ref = None 
Создание класса единого связанного списка

Затем нам нужно создать класс для связанного списка. Этот класс будет содержать методы для вставки, удаления, обхода и сортировки списка. Первоначально класс будет содержать только один член start_node который будет указывать на начальный или первый узел списка. Значение start_node будет установлено равным null с помощью конструктора, поскольку связанный список будет пуст во время создания. Следующий сценарий создает класс для связанного списка.

 class LinkedList: 
 def __init__(self): 
 self.start_node = None 

Теперь мы создали класс для нашего единственного списка. Следующим шагом является добавление функции вставки для вставки элементов в связанный список. Но перед этим мы добавим функцию для просмотра связанного списка. Эта функция поможет нам прочитать данные в нашем списке.

Просмотр элементов связанного списка

Код Python для функции перемещения выглядит следующим образом. Добавьте приведенную ниже функцию в LinkedList который мы создали в предыдущем разделе.

 def traverse_list(self): 
 if self.start_node is None: 
 print("List has no element") 
 return 
 else: 
 n = self.start_node 
 while n is not None: 
 print(n.item , " ") 
 n = n.ref 

Посмотрим, что происходит в приведенной выше функции. Функция состоит из двух основных частей. Во-первых, он проверяет, пуст ли связанный список. Следующий код проверяет это:

 if self. start_node is None: 
 print("List has no element") 
 return 

Если связанный список пуст, это означает, что нет элемента для повторения. В таких случаях traverse_list() просто выводит утверждение о том, что в списке нет элементов.

В противном случае, если в списке есть элемент, будет выполнен следующий фрагмент кода:

 n = self.start_node 
 while n is not None: 
 print(n.item , " ") 
 n = n.ref 

Как мы уже говорили ранее, start будет содержать ссылку на первые узлы. Поэтому мы инициализируем переменную n с start . Затем мы выполняем цикл, который выполняется до тех пор, пока n станет равным нулю. Внутри цикла мы печатаем элемент, хранящийся в текущем узле, а затем устанавливаем значение переменной n n.ref , которое содержит ссылку на следующий узел. Ссылка на последний узел — None поскольку после этого узла нет. Следовательно, когда n становится None , цикл завершается.

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

Вставка предметов

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

Вставка элементов в начале

Самый простой способ вставить элемент в один связанный список — это добавить элемент в начало списка. Следующая функция вставляет элемент в начало списка. Добавьте эту функцию в созданный нами ранее класс LinkedList

 def insert_at_start(self, data): 
 new_node = Node(data) 
 new_node.ref = self.start_node 
 self.start_node= new_node 

В приведенном выше скрипте мы создаем метод insert_at_start() , метод принимает один параметр, который в основном является значением элемента, который мы хотим вставить. Внутри метода мы просто создаем объект Node и устанавливаем его ссылку на start_node поскольку start_node ранее хранил первый узел, который после вставки нового узла в начало станет вторым узлом.

Поэтому мы добавляем ссылку на start_node в ref нового узла. Теперь , так как new_node является первым узлом, мы устанавливаем значение start_node переменной в new_node .

Вставка элементов в конец

Следующая функция используется для добавления элемента в конец связанного списка.

 def insert_at_end(self, data): 
 new_node = Node(data) 
 if self.start_node is None: 
 self.start_node = new_node 
 return 
 n = self.start_node 
 while n.ref is not None: 
 n= n.ref 
 n.ref = new_node; 

В приведенном выше скрипте мы создаем функцию insert_at_end() , которая вставляет элемент в конец связанного списка. Значение элемента, который мы хотим вставить, передается в качестве аргумента функции. Функция состоит из двух частей. Сначала мы проверяем , если связанный список пуст или нет, если связанный список пуст, все , что мы должны сделать , это установить значение start_node переменной в new_node объекта.

С другой стороны, если список уже содержит какие-то узлы. Мы инициализируем переменную n с помощью начального узла. Затем мы перебираем все узлы в списке, используя цикл while, как мы это делали в случае функции traverse_list Цикл завершается, когда мы достигаем последнего узла. Затем мы устанавливаем ссылку последнего узла на вновь созданный new_node .

Добавьте insert_at_end() в класс LinkedList

Вставка элемента после другого элемента

Возможно, нам потребуется добавить элемент после другого элемента в один связанный список. Для этого мы можем использовать insert_after_item() как определено ниже:

 def insert_after_item(self, x, data): 
 
 n = self.start_node 
 print(n.ref) 
 while n is not None: 
 if n.item == x: 
 break 
 n = n.ref 
 if n is None: 
 print("item not in the list") 
 else: 
 new_node = Node(data) 
 new_node.ref = n.ref 
 n.ref = new_node 

Функция insert_after_item() принимает два параметра: x и data . Первый параметр — это элемент, после которого вы хотите вставить новый узел, а второй параметр содержит значение для нового узла.

Начнем с создания новой переменной n и присвоения start_node переменной start_node. Затем мы просматриваем связанный список, используя цикл while. Цикл while выполняется до тех пор, пока n станет None . Во время каждой итерации мы проверяем, равно ли значение, хранящееся в текущем узле, значению, переданному параметром x Если сравнение вернет истину, мы прервем цикл.

Затем, если элемент найден, n не будет None . Ссылка new_node устанавливается на ссылку, сохраненную n а ссылка n устанавливается на new_node . Добавьте insert_after_item() в класс LinkesList

Вставка элемента перед другим элементом
 def insert_before_item(self, x, data): 
 if self.start_node is None: 
 print("List has no element") 
 return 
 
 if x == self. start_node.item: 
 new_node = Node(data) 
 new_node.ref = self.start_node 
 self.start_node = new_node 
 return 
 
 n = self.start_node 
 print(n.ref) 
 while n.ref is not None: 
 if n.ref.item == x: 
 break 
 n = n.ref 
 if n.ref is None: 
 print("item not in the list") 
 else: 
 new_node = Node(data) 
 new_node.ref = n.ref 
 n.ref = new_node 

В приведенном выше скрипте мы определяем insert_before_item() . Функция состоит из трех частей. Давайте подробно рассмотрим каждую часть.

 if self.start_node is None: 
 print("List has no element") 
 return 

В приведенном выше скрипте мы проверяем, пуст ли список. Если он действительно пуст, мы просто печатаем, что в списке нет элемента, и возвращаемся из функции.

Затем мы проверяем, находится ли элемент по первому индексу. Взгляните на следующий сценарий:

 if x == self.start_node.item: 
 new_node = Node(data) 
 new_node.ref = self.start_node 
 self.start_node = new_node 
 return 

Если элемент, после которого мы хотим вставить новый узел, находится в первом index. Мы просто устанавливаем ссылку на вновь вставленный узел на start_node а затем устанавливаем значение start_node на new_node .

Наконец, если список не равен None и элемент не найден по первому индексу, мы создаем новую переменную n и присваиваем start_node переменную start_node. Затем мы просматриваем связанный список, используя цикл while. Цикл while выполняется до n.ref станет None . Во время каждой итерации мы проверяем, равно ли значение, хранящееся в ссылке текущего узла, значению, переданному параметром x Если сравнение вернет истину, мы прервем цикл.

Затем, если элемент найден, n.ref не будет None . Ссылка new_node устанавливается на ссылку n а ссылка n устанавливается на new_node . Взгляните на следующий сценарий:

 if n.ref is None: 
 print("item not in the list") 
 else: 
 new_node = Node(data) 
 new_node. ref = n.ref 
 n.ref = new_node 

Добавьте insert_before_item() в класс LinkedList

Вставка элемента по определенному индексу

Иногда нам нужно вставить элемент по определенному индексу, мы можем сделать это с помощью следующего скрипта:

 def insert_at_index (self, index, data): 
 if index == 1: 
 new_node = Node(data) 
 new_node.ref = self.start_node 
 self.start_node = new_node 
 i = 1 
 n = self.start_node 
 while i < index-1 and n is not None: 
 n = n.ref 
 i = i+1 
 if n is None: 
 print("Index out of bound") 
 else: 
 new_node = Node(data) 
 new_node.ref = n.ref 
 n.ref = new_node 

В скрипте мы сначала проверяем, равен ли индекс, в котором мы хотим сохранить элемент, 1, затем просто назначаем start_node ссылке на new_node а затем устанавливаем значение start_node на new_node .

Затем выполните цикл while, который выполняется до тех пор, пока счетчик i станет больше или равным index-1 . Например, если вы хотите добавить новый узел в третий index. Во время первой итерации цикла while i станет равным 2, а текущий итерируемый узел будет равен «2». Цикл не будет выполняться снова, поскольку i равно 2, что равно index-1 (3-1 = 2). Следовательно, петля разорвется. Затем мы добавляем новый узел после текущего итеративного узла (который является узлом 2), следовательно, новый узел добавляется по индексу.

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

Тестирование функций вставки

Теперь мы определили все наши функции вставки, давайте протестируем их.

Сначала создайте объект класса связанного списка следующим образом:

 new_linked_list = LinkedList() 

Затем давайте сначала insert_at_end() чтобы добавить три элемента в связанный список. Выполните следующий скрипт:

 new_linked_list.insert_at_end(5) 
 new_linked_list.insert_at_end(10) 
 new_linked_list.insert_at_end(15) 

Чтобы увидеть, действительно ли элементы были вставлены, давайте пройдемся по связанному списку, используя функцию обхода.

 new_linked_list.traverse_list() 

Вы должны увидеть следующий результат:

 5 
 10 
 15 

Затем давайте добавим элемент в начало:

 new_linked_list.insert_at_start(20) 

Теперь, если вы пройдете по списку, вы должны увидеть следующий вывод:

 20 
 5 
 10 
 15 

Добавим после пункта 10 новый элемент 17:

 new_linked_list.insert_after_item(10, 17) 

Теперь при обходе списка возвращается следующий результат:

 20 
 5 
 10 
 17 
 15 

Вы можете увидеть 17 вставленных после 10.

Давайте теперь вставим еще один элемент 25 перед элементом 17, используя insert_before_item() как показано ниже:

 new_linked_list. insert_before_item(17, 25) 

Теперь список будет содержать следующие элементы:

 20 
 5 
 10 
 25 
 17 
 15 

Наконец, давайте добавим элемент в третье место, которое в настоящее время занято 10. Вы увидите, что 10 переместит на одно место вперед, и новый элемент будет вставлен на его место. Для этого можно использовать функцию insert_at_index() Следующий скрипт вставляет элемент 8 в третий индекс списка.

 new_linked_list.insert_at_index(3,8) 

Теперь, если вы пройдете по списку, вы должны увидеть следующий вывод:

 20 
 5 
 8 
 10 
 25 
 17 
 15 

И с этим мы протестировали все наши функции вставки. В настоящее время в нашем списке 7 элементов. Напишем функцию, которая возвращает количество элементов в связанном списке.

Подсчет элементов

Следующая функция подсчитывает общее количество элементов.

 def get_count(self): 
 if self.start_node is None: 
 return 0; 
 n = self.start_node 
 count = 0; 
 while n is not None: 
 count = count + 1 
 n = n. ref 
 return count 

В приведенном выше скрипте мы создаем get_count() которая просто подсчитывает количество элементов в связанном списке. Функция просто проходит через все узлы в массиве и увеличивает счетчик, используя цикл while. В конце цикла счетчик содержит общее количество элементов в цикле.

LinkedList выше функцию в класс LinkedList, скомпилируйте LinkedList и затем вставьте некоторые элементы в LinkedList как мы это делали в предыдущем разделе. К концу последнего раздела у нас было 7 элементов в нашем связанном списке.

Давайте воспользуемся get_count() чтобы получить общее количество элементов в списке:

 new_linked_list.get_count() 

На выходе вы должны увидеть количество элементов в связанном списке.

В качестве альтернативы, другой способ получить «счетчик» списка — это отслеживать количество элементов, вставленных и удаленных из списка, в простой переменной счетчика, принадлежащей классу LinkedList Это работает хорошо и быстрее, чем get_count выше, если базовая структура данных списка не может управляться извне класса.

Поиск элементов

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

Скрипт для search_item() выглядит следующим образом:

 def search_item(self, x): 
 if self.start_node is None: 
 print("List has no elements") 
 return 
 n = self.start_node 
 while n is not None: 
 if n.item == x: 
 print("Item found") 
 return True 
 n = n.ref 
 print("item not found") 
 return False 

LinkedList выше функцию в класс LinkedList. Выполним поиск элемента в ранее созданном списке. Выполните следующий скрипт:

 new_linked_list.search_item(5) 

Поскольку мы вставили 5 в наш связанный список, вышеуказанная функция вернет true. Результат будет выглядеть так:

 Item found 
 True 
Создание связанного списка

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

 def make_new_list(self): 
 nums = int(input("How many nodes do you want to create: ")) 
 if nums == 0: 
 return 
 for i in range(nums): 
 value = int(input("Enter the value for the node:")) 
 self.insert_at_end(value) 

В приведенном выше сценарии make_new_list() сначала запрашивает у пользователя количество элементов в списке. Затем, используя цикл for, пользователю предлагается ввести значение для каждого узла, которое затем вставляется в связанный список с помощью функции insert_at_end() .

На следующем make_new_list() в действии.

{.ezlazyload .img-responsive}

Удаление элементов

В этом разделе мы увидим различные способы удаления элемента из единого связанного списка.

Удаление с самого начала

Удалить элемент или элемент из начала связанного списка очень просто. Мы должны установить ссылку start_node на второй узел, что мы можем сделать, просто присвоив значение ссылки начального узла (который указывает на второй узел) начальному узлу, как показано ниже:

 def delete_at_start(self): 
 if self.start_node is None: 
 print("The list has no element to delete") 
 return 
 self.start_node = self.start_node.ref 

В приведенном выше сценарии мы сначала проверяем, пуст список или нет. Если список пуст, мы отображаем сообщение о том, что в списке нет элемента для удаления. В противном случае мы присваиваем значение start_node.ref start_node . start_node теперь будет указывать на второй элемент. Добавьте delete_at_start() в класс LinkedList

Удаление в конце

Чтобы удалить элемент из конца списка, нам просто нужно перебрать связанный список до второго последнего элемента, а затем нам нужно установить ссылку на второй последний элемент на none, что преобразует второй последний элемент в последний элемент.

Скрипт для функции delete_at_end выглядит следующим образом:

 def delete_at_end(self): 
 if self.start_node is None: 
 print("The list has no element to delete") 
 return 
 
 n = self.start_node 
 while n.ref.ref is not None: 
 n = n.ref 
 n.ref = None 

Добавьте приведенный выше сценарий в класс LinkedList()

Удаление по значению элемента

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

 def delete_element_by_value(self, x): 
 if self.start_node is None: 
 print("The list has no element to delete") 
 return 
 
 # Deleting first node 
 if self.start_node.item == x: 
 self.start_node = self.start_node. ref 
 return 
 
 n = self.start_node 
 while n.ref is not None: 
 if n.ref.item == x: 
 break 
 n = n.ref 
 
 if n.ref is None: 
 print("item not found in the list") 
 else: 
 n.ref = n.ref.ref 

В приведенном выше сценарии мы сначала проверяем, пуст ли список. Затем мы проверяем, находится ли удаляемый элемент в начале связанного списка. Если элемент найден в начале, мы удаляем его, устанавливая первый узел на ссылку первого узла (который в основном относится ко второму узлу).

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

Тестирование функций удаления

Давайте протестируем функции удаления, которые мы только что создали. Но перед этим добавьте фиктивные данные в наш связанный список, используя следующий скрипт:

 new_linked_list. insert_at_end(10) 
 new_linked_list.insert_at_end(20) 
 new_linked_list.insert_at_end(30) 
 new_linked_list.insert_at_end(40) 
 new_linked_list.insert_at_end(50) 

Приведенный выше сценарий вставляет 5 элементов в связанный список. Если вы пройдете по списку, вы должны увидеть следующие элементы:

 10 
 20 
 30 
 40 
 50 

Давайте сначала удалим элемент с самого начала:

 new_linked_list.delete_at_start() 

Теперь, если вы пройдете по списку, вы должны увидеть следующий вывод:

 20 
 30 
 40 
 50 

Теперь удалим элемент с конца:

 new_linked_list.delete_at_end() 

Список теперь содержит следующие элементы:

 20 
 30 
 40 

Наконец, давайте удалим элемент по значению, скажем 30.

 new_linked_list.delete_element_by_value(30) 

Теперь, если вы пройдете по списку, вы не должны увидеть элемент 30.

Переворачивание связного списка

Чтобы перевернуть связанный список, вам нужно иметь три переменные: prev , n и next . prev отследит предыдущий узел, то next будет отслеживать следующий узел будет ли n будет соответствовать текущему узлу.

Мы запускаем цикл while, присваивая начальный узел переменной n а prev не инициализируется значением none. Цикл выполняется до тех пор, пока n станет равным нулю. Внутри цикла while вам необходимо выполнить следующие функции.

  • Присвойте значение ссылки текущего узла next .
  • Установите значение ссылки текущего узла n на prev
  • Установите prev на текущий узел n .
  • Установите текущий узел n на значение next узла.

В конце цикла prev будет указывать на последний узел, нам нужно сделать его первым узлом, поэтому мы устанавливаем значение переменной self.start_node prev . Цикл while заставит каждый узел указывать на его предыдущий узел, в результате чего будет получен обратный связанный список. Сценарий выглядит следующим образом:

 def reverse_linkedlist(self): 
 prev = None 
 n = self.start_node 
 while n is not None: 
 next = n.ref 
 n.ref = prev 
 prev = n 
 n = next 
 self.start_node = prev 

LinkedList выше функцию в класс LinkedList. Создайте связанный список случайных чисел, а затем посмотрите, можно ли его отменить, используя функцию reverse_linkedlist()

Заключение

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

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

Глава 3. Связные списки, стеки и очереди. Фундаментальные алгоритмы и структуры данных в Delphi

Глава 3. Связные списки, стеки и очереди. Фундаментальные алгоритмы и структуры данных в Delphi

ВикиЧтение

Фундаментальные алгоритмы и структуры данных в Delphi
Бакнелл Джулиан М.

Содержание

Глава 3. Связные списки, стеки и очереди

Как и массивы, связные списки представляют собой универсальную структуру данных, широко используемую многими программистами. Однако, в отличие от массивов, связные списки не входят в состав стандартного языка Object Pascal. Тем не менее, в Object Pascal создать связный список достаточно просто. Все что для этого нужно — наличие в составе языка указателя, хотя фактически могут использоваться и классы или объекты.

На основе связных списков можно легко организовать стеки и очереди — еще две простые, но эффективные структуры данных. Несмотря на то что они, на первый взгляд, не имеют ничего общего со связными списками, их можно написать на базе односвязных списков. И, как мы увидим чуть позже, иногда удобнее реализовать стеки и очереди на базе массивов, а не связных списков.

Начнем наше рассмотрение со связного списка и операций, которые такой список должен поддерживать.

Глава 3 Альтернативные стеки протоколов

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

I.3.1 Протоколы, элементы, стеки и наборы

I.3.1 Протоколы, элементы, стеки и наборы Протоколом (protocol) называется набор правил для одной из коммуникационных функций. Например, протокол IP представляет собой набор правил для маршрутизации данных, a TCP определяет правила для надежной и последовательной доставки

ГЛАВА 5 Очереди сообщений Posix

ГЛАВА 5 Очереди сообщений Posix 5.1. Введение Очередь сообщений можно рассматривать как связный список сообщений. Программные потоки с соответствующими разрешениями могут помещать сообщения в очередь, а потоки с другими соответствующими разрешениями могут извлекать их

ГЛАВА 6 Очереди сообщений System V

ГЛАВА 6 Очереди сообщений System V 6.1. Введениеы Каждой очереди сообщений System V сопоставляется свой идентификатор очереди сообщений. Любой процесс с соответствующими привилегиями (раздел 3.5) может поместить сообщение в очередь, и любой процесс с другими соответствующими

9.

2. Стеки и очереди

9.2. Стеки и очереди Стеки и очереди — это первые из встретившихся нам структур, которые, строго говоря, не встроены в Ruby. Иными словами, в Ruby нет классов Stack и Queue, в отличие от Array и Hash (впрочем, класс Queue есть в библиотеке thread.rb, которую мы еще будем рассматривать).И все же в

Связные списки

Связные списки В связных списках последовательный поиск выполняется точно так же, как и в массивах. Тем не менее, элементы проходятся не по индексу, а по указателю Next. Для класса TtdSingleLinkList, описанного в главе 3, можно разработать две следующих функции: первая — для

Связные списки

Связные списки Изучая код листинга 4.9, можно придти к выводу, что маловероятно, чтобы бинарный поиск использовался для связных списков, если, конечно, не воспользоваться индексным доступом к элементам списка, который, как уже упоминалось в главе 3, приводит к снижению

Глава 9.

Очереди по приоритету и пирамидальная сортировка.

Глава 9. Очереди по приоритету и пирамидальная сортировка. В главе 3 мы рассмотрели несколько очень простых структур данных. Одной из них была очередь. В эту структуру можно было добавлять элементы, а затем извлекать их в порядке поступления. При этом сохранение даты и

Глава 24. Списки команд

Глава 24. Списки команд Средством обработки последовательности из нескольких команд служат списки: «И-списки» и «ИЛИ-списки». Они эффективно могут заменить сложную последовательность вложенных if/then или даже case.Объединение команд в цепочкиИ-списокcommand-1 &amp;&amp; command-2 &amp;&amp;

Глава 3 Списки, операторы, арифметика

Глава 3 Списки, операторы, арифметика В этой главе мы будем изучать специальные способы представления списков. Список — один из самых простых и полезных типов структур. Мы рассмотрим также некоторые программы для выполнения типовых операций над списками и, кроме того,

У6.9 Ограниченные стеки

У6.9 Ограниченные стеки Измените приведенную в этой лекции спецификацию стеков так, чтобы она описывала стеки ограниченной емкости. (Указание: введите емкость как явную функцию-запрос и сделайте функцию put

У15.5 Связанные стеки

У15.5 Связанные стеки Основываясь на классах STACK и LINKED_LIST, постройте класс LINKED_STACK, описывающий реализацию стека как связного

Типы связанных списков в структурах данных

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

Существует четыре основных типа связанных списков:

  • Односвязные списки
  • Двусвязные списки
  • Круговые связанные списки
  • Циклические двусвязные списки

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

Структура односвязного списка

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

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

Создание и обход односвязного списка

Связный список — это структура данных, в которой хранится последовательность элементов. Каждый элемент в списке называется узлом, и каждый узел имеет ссылку на следующий узел в списке. Первый узел в списке называется головным, а последний узел в списке — хвостовым.

Чтобы создать односвязный список, нам сначала нужно создать класс узлов. Каждый узел будет иметь два члена данных: целочисленное значение и ссылку на следующий узел в списке. Далее нам нужно создать класс LinkedList. Этот класс будет иметь два члена данных: головной узел и хвостовой узел. Головной узел будет хранить первый элемент в списке, а хвостовой узел будет хранить последний элемент в списке.

Чтобы добавить элемент в список, нам нужно создать новый узел и установить следующую ссылку предыдущего хвостового узла, чтобы она указывала на новый узел. Затем мы можем установить новый узел в качестве нового хвоста списка.

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

Двунаправленный связанный список — это двунаправленный связанный список. Таким образом, вы можете пройти его в обоих направлениях. В отличие от односвязных списков, его узлы содержат один дополнительный указатель, называемый предыдущим указателем. Этот указатель указывает на предыдущий узел.

Структура двусвязного списка

Двусвязный список односвязных списков — это структура данных, состоящая из набора односвязных списков (SLL), каждый из которых является двусвязным. Он используется для хранения данных таким образом, чтобы можно было быстро вставлять и удалять элементы.

Каждый SLL состоит из двух частей: головы и хвоста. В начале каждого SLL находится указатель на первый элемент в списке, а в конце — указатель на последний элемент.

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

Создание и обход двусвязного списка

Двусвязный список — это тип структуры данных, который позволяет хранить данные линейным образом, подобно односвязному списку. Однако, в отличие от односвязного списка, двусвязный список допускает как прямой, так и обратный обход данных, хранящихся в нем. Это делает ее идеальной структурой данных для приложений, которым требуется возможность перемещаться как вперед, так и назад по списку данных.

Чтобы создать двусвязный список, нам сначала нужно создать класс Node, который будет использоваться для хранения наших данных. Этот класс Node будет иметь два атрибута: data и next. Атрибут data будет использоваться для хранения фактических данных, которые мы хотим сохранить в нашем списке, а атрибут next будет использоваться для хранения ссылки на следующий узел в списке.

Когда у нас есть класс Node, мы можем создать двусвязный список. Для этого нам нужно создать класс, который будет представлять наш список. У этого класса будет два атрибута: голова и хвост. Атрибут head будет использоваться для хранения ссылки на первый узел в нашем списке, а атрибут tail будет использоваться для хранения ссылки на последний узел в нашем списке.

После создания класса списка мы можем начать добавлять в него данные. Чтобы добавить данные в наш список, нам нужно создать новый узел и установить для его атрибута данных данные, которые мы хотим добавить. Затем нам нужно установить следующий атрибут нового узла, чтобы он указывал на головной узел нашего списка. Наконец, нам нужно установить головной узел нашего списка так, чтобы он указывал на новый узел.

Теперь, когда мы знаем, как добавлять данные в наш список, мы можем написать функцию для обхода нашего списка. Для этого нам нужно создать переменную, которая будет отслеживать текущий узел, который мы проходим. Мы можем установить эту переменную в головной узел нашего списка, чтобы начать. Затем нам нужно создать цикл while, который будет продолжаться до тех пор, пока текущий узел не станет None, что указывает на то, что мы достигли конца нашего списка.

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

Круговой связанный список — это однонаправленный связанный список. Таким образом, вы можете пройти его только в одном направлении. Но у этого типа связанного списка последний узел указывает на головной узел. Поэтому во время обхода вам нужно быть осторожным и прекращать обход при повторном посещении головного узла.

Структура кругового связанного списка

Циклический связанный список — это тип структуры данных, в которой используется технология связанных списков для хранения данных линейным последовательным образом. Однако, в отличие от традиционного связанного списка, круговой связанный список не имеет ни начала, ни конца — по сути, это кольцо узлов. Это делает круговые связанные списки идеальными для приложений, в которых данные должны обрабатываться в непрерывном цикле, например, в приложениях реального времени или симуляциях.

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

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

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

Во-вторых, поскольку данные хранятся в кольцевой структуре, к ним можно обращаться в непрерывном цикле. Это делает круговые связанные списки идеальными для приложений, в которых данные должны обрабатываться в непрерывном цикле, например, в приложении реального времени или моделировании.

В-третьих, поскольку у списка нет определенного начала или конца, круговые связанные списки обычно более эффективны, чем традиционные связанные списки, когда речь идет об использовании памяти. Это связано с тем, что традиционные связанные списки часто требуют дополнительной памяти для указателей, указывающих на начало и конец списка. Циклические связанные списки, с другой стороны, требуют только одного указателя для хранения в памяти — указателя на начало.

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

Создание и обход кругового связанного списка 

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

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

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

Существует два способа создания кругового связанного списка:

  1. Создайте односвязный список и сделайте так, чтобы последний узел указывал на первый узел.
  2. Создайте двусвязный список и сделайте так, чтобы последний узел указывал на первый узел, а первый узел указывал на последний узел.

Чтобы создать односвязный циклический связанный список, нам сначала нужно создать односвязный список. Мы можем сделать это, создав класс Node и класс LinkedList. Класс Node будет представлять каждый узел в списке, а класс LinkedList будет представлять сам список.

Чтобы создать двусвязный круговой связанный список, нам сначала нужно создать двусвязный список. Мы можем сделать это, создав класс Node и класс LinkedList. Класс Node будет представлять каждый узел в списке, а класс LinkedList будет представлять сам список.

Создав классы Node и LinkedList, мы можем создать круговой связанный список, создав несколько узлов и связав их вместе. Для этого нам сначала нужно создать несколько узлов. Затем нам нужно связать узлы вместе, чтобы сформировать круговой связанный список. Мы также можем связать узлы вместе в двусвязном списке.

Существует два способа обхода кругового связанного списка:

  1. Перемещайтесь по списку, пока снова не достигнете головного узла.
  2. Следите за узлами, которые вы посетили.

Чтобы просмотреть список, пока вы снова не достигнете головного узла, вы можете использовать цикл while. Если вы хотите отслеживать узлы, которые вы посетили, вы можете использовать список или набор.

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

Структура дважды циклически связанного списка

Дважды циклический связанный список (DCL) — это разновидность стандартного двусвязного списка. В DCL первый и последний узлы связаны вместе, образуя круг. Это позволяет быстро и легко перемещаться по всему списку без необходимости специальной обработки регистра в начале или в конце списка. DCL часто используются в приложениях, где данные должны обрабатываться последовательно, например, в видео- или аудиоплеере. Циклический характер списка позволяет быстро и легко перемещаться от одного узла к другому без необходимости обработки особых случаев. DCL также иногда используются в приложениях, где к данным требуется произвольный доступ, например, в базе данных. В этом случае круговой характер списка позволяет быстро и легко перемещаться к любому узлу в списке без необходимости обработки особых случаев.

Создание и обход дважды кругового связанного списка 

Чтобы создать дважды круговой связанный список, нам сначала нужно создать структуру узлов, которая будет содержать наши данные и указатели. Затем мы можем создать указатель на голову и инициализировать его значением null. Далее нам нужно создать функцию для вставки новых узлов в список. Эта функция должна принимать в качестве входных данных данные, которые мы хотим вставить, и указатель на заголовок. Затем он должен создать новый узел с этими данными и вставить его в список. Наконец, нам нужно написать функцию для обхода списка и вывода данных, содержащихся в каждом узле. Эта функция должна принимать в качестве входных данных указатель головы. Затем он должен перебирать каждый узел в списке, распечатывая данные, содержащиеся в этом узле.

Далее вы познакомитесь с некоторыми приложениями этих связанных списков.

  • Связанный список используется для реализации стека и очередей
  • Связный список используется для представления разреженных матриц
  • Вы можете реализовать средство просмотра изображений, используя круговой связанный список
  • Вы можете использовать концепцию связанного списка для навигации по веб-страницам
  • Вы можете использовать круговой двусвязный список для реализации кучи Фибоначчи
Если вы хотите получить навыки, необходимые для работы в сложной, полезной и динамичной роли в сфере ИТ, мы вас поддержим! Откройте для себя бесконечные возможности с помощью этой инновационной программы для аспирантов по курсу Full Stack Web Development, разработанному нашими партнерами из Caltech CTME. Зарегистрируйтесь сегодня!

Следующие шаги

Погружение в особенности «Односвязных списков» может стать вашей следующей остановкой. Связанные списки состоят из узлов, которые указывают на следующий узел. Связанные списки являются динамическими и имеют более быстрое время вставки/удаления.

Ищете онлайн-обучение и сертификацию в области разработки программного обеспечения? Simplilearn предлагает множество программ, в том числе программу для аспирантов по веб-разработке полного стека в сотрудничестве с Caltech CTME.

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

Как реализовать связанный список в JavaScript

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

В этой статье мы обсудим, что такое связанный список, чем он отличается от массива и как его реализовать в JavaScript. Давайте начнем.


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

Каждый элемент (обычно называемый узлами) содержит два элемента: сохраненные данные и ссылку на следующий узел. Данные могут быть любого допустимого типа данных. Вы можете увидеть это на диаграмме ниже.

Точка входа в связанный список называется головой. Голова — это ссылка на первый узел в связанном списке. Последний узел в списке указывает на ноль. Если список пуст, заголовок является нулевой ссылкой.

В JavaScript связанный список выглядит так:

 const list = {
    глава: {
        значение: 6
        следующий: {
            значение: 10
            следующий: {
                значение: 12
                следующий: {
                    значение: 3
                    следующий: ноль
                    }
                }
            }
        }
    }
}; 

РЕКЛАМА

  • Узлы можно легко удалять или добавлять из связанного списка без реорганизации всей структуры данных. Это одно из его преимуществ перед массивами.
  • Операции поиска в связанных списках выполняются медленно. В отличие от массивов, произвольный доступ к элементам данных не допускается. Доступ к узлам осуществляется последовательно, начиная с первого узла.
  • Использует больше памяти, чем массивы, из-за хранения указателей.

Существует три типа связанных списков:

  • Односвязные списки : Каждый узел содержит только один указатель на следующий узел. Это то, о чем мы говорили до сих пор.
  • Двусвязные списки : Каждый узел содержит два указателя, указатель на следующий узел и указатель на предыдущий узел.
  • Циклические связанные списки : Циклические связанные списки представляют собой вариант связанного списка, в котором последний узел указывает на первый узел или любой другой узел перед ним, тем самым образуя петлю.

РЕКЛАМА

Реализация узла списка в JavaScript

Как было сказано ранее, узел списка содержит два элемента: данные и указатель на следующий узел. Мы можем реализовать узел списка в JavaScript следующим образом:

 class ListNode {
    конструктор (данные) {
        это.данные = данные
        это.следующее = ноль
    }
} 

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

 класс LinkedList {
    конструктор (голова = ноль) {
        это.голова = голова
    }
} 

Собираем все вместе

Давайте создадим связанный список с только что созданным классом. Сначала мы создаем два узла списка, node1 и node2 , а также указатель с узла 1 на узел 2.

 let node1 = new ListNode(2)
пусть node2 = новый ListNode (5)
node1.next = node2 

Далее мы создадим связанный список с node1 .

 пусть список = новый LinkedList (узел 1) 

Давайте попробуем получить доступ к узлам в списке, который мы только что создали.

 console.log(list.head.next.data) // возвращает 5 

РЕКЛАМА

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

  1. size()
  2. clear()
  3. getLast()
  4. getFirst()

1. size()

Этот метод возвращает количество узлов, присутствующих в связанном списке.

 размер() {
    пусть счет = 0;
    пусть узел = this.head;
    в то время как (узел) {
        количество++;
        узел = узел.следующий
    }
    количество возвратов;
}
 

2. clear()

Этот метод очищает список.

 очистить () {
    this.head = ноль;
} 

РЕКЛАМА

3. getLast()

Этот метод возвращает последний узел связанного списка.

 получитьПоследнее() {
    пусть lastNode = this.head;
    если (последний узел) {
        в то время как (последний узел. следующий) {
            последнийУзел = последнийУзел.следующий
        }
    }
    вернуть последний узел
} 

4.

getFirst()

Этот метод возвращает первый узел связанного списка.

 получитьПервый() {
    вернуть this.head;
} 

Резюме

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

Надеюсь, вам понравилось читать.

Хотите получать уведомления, когда я публикую новую статью? Кликните сюда.



Научитесь программировать бесплатно. Учебная программа freeCodeCamp с открытым исходным кодом помогла более чем 40 000 человек получить работу в качестве разработчиков. Начать

РЕКЛАМА

С#. Каковы реальные примеры использования связанных списков?

спросил

Изменено 13 дней назад

Просмотрено 25 тысяч раз

Другой программист упомянул, что за всю его карьеру они не нашли варианта использования структуры данных связанного списка ни в одном профессиональном программном обеспечении. Я не мог придумать ни одного хорошего примера из головы. Он в основном разработчик C# и Java

Может ли кто-нибудь привести несколько примеров, когда это была правильная структура данных для решения конкретной реальной проблемы?

Связанный: Каков практический, реальный пример связанного списка?

Связанный список структур данных Java в С#

1

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

  1. LinkedLists не требует непрерывных блоков памяти, поэтому может помочь уменьшить фрагментацию памяти
  2. LinkedLists поддерживают эффективное удаление элементов (динамические массивы обычно вызывают сдвиг всех элементов).
  3. LinkedLists поддерживают эффективное добавление элементов (динамические массивы могут вызвать перераспределение + копирование, если конкретное добавление превышает текущую емкость)

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

9

Реальным примером может быть очередь FIFO. Простой список на основе массива довольно плох для этого, потому что вам нужно добавить на одном конце и удалить на другом конце, и одна из этих операций будет O (n) со списком на основе массива (если вы не добавите дополнительную логику к работать с начальным И конечным индексом), в то время как оба являются O (1) со связанным списком без дополнительных усилий.

5

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

класс Renderable /* или класс Object, что угодно */
{
  // ...
  Рендеринг * m_pNext;
  Рендеринг * m_pPrev; // или нет, если односвязный
  // ...
}
 

Когда Renderables появляются и исчезают, они могут регистрироваться в этом списке, не вызывая выделения памяти. Если их глубина рендеринга или приоритет изменены, они могут удалить и снова вставить себя и т. д.

Когда придет время рендеринга, все, что вам нужно сделать, это найти начало списка и прокрутить его, вызвав соответствующий метод!

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

1

Связанные списки (в паре с хеш-таблицей) действительно полезны для кэшей LRU.

Каждому Get необходимо поместить узел в начало списка, что очень дешево для связанных списков.

Пример односвязного списка.

  1. Кнопка отмены любого приложения, такого как Microsoft Word, Paint и т. д.: связанный список состояний.
  2. GPS-навигация: связанный список картографических данных. Путешествие от источника к месту назначения является примером обхода всех узлов. Перенаправление с помощью GPS является примером операций добавления и удаления картографических данных.

Пример двусвязного списка.

  1. Кнопки браузера «Далее» и «Назад»: связанный список URL-адресов.
  2. Кнопки «Далее» и «Назад» средства просмотра изображений: связанный список изображений
  3. Кнопка «Отменить» и «Повторить» в Photoshop, связанный список состояний.

Стеки и очереди — очень наглядные примеры связанных списков, но, поскольку другие уже упоминали о них, я хотел бы добавить еще несколько примеров:

DOM хранит узлы в виде связанных списков. Простой пример javascript, который действительно одинаков на любом языке:

 for (var node = parent.firstChild; node != null; node = node.nextSibling) {
    // ..
}
 

Я полагаю, что разработчик Java в какой-то момент столкнулся с XML.

Деревья — еще один хороший пример связанных списков, хотя они и не являются простыми одномерными списками. Кто-то, кто занимался разработкой Java, вероятно, сталкивался с TreeMaps и TreeSets.

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

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

Неизменяемый связанный список является очень ценной структурой, так как вы можете «делить структуру» с другими списками с тем же хвостом. Большинство функциональных языков включают тип неизменяемого связанного списка в качестве одной из своих основных структур данных, и эти типы используются повсеместно.

1

Я написал расширение BIOS (по сути, драйвер устройства для BIOS, написанный на ассемблере) для хост-контроллера USB. — Реализация высокоуровневой, казалось бы, абстрактной структуры данных, такой как связанный список в ассемблере, не так сложна, как кажется. — Контроллер обслуживал запросы ввода-вывода для клавиатуры и диска, используя связанный список пакетов в основной памяти. Я поддерживал пул бесплатных пакетов в другом связанном списке. Выполнение ввода-вывода в основном включало захват свободного пакета из начала списка свободных, настройку пакета, добавление пакета в список устройств и повторное добавление пакета в начало свободного пула после завершения ввода-вывода. Связанные списки позволяют молниеносно перемещаться по таким объектам, особенно когда объекты большие, поскольку на самом деле им не нужно двигаться. Только их указатели должны быть обновлены.

Очередь на основе массива потребовала бы:

  • с использованием указателя индекса начала/конца, который работает быстро, но требует фиксирования размера очереди, чтобы производителю приходилось ждать, когда очередь потребителя заполнится, и блокировать очередь при этом заполняется весь пакет, если производителей более одного
  • сдвигает все элементы в очереди всякий раз, когда вы вставляете/удаляете их, что замедляет работу с большими объектами

Таким образом, связанные списки — хороший способ реализовать произвольно длинную очередь больших объектов в порядке поступления.

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

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

В качестве, возможно, лучшего реального примера в .Net рассмотрим MultiCastDelegate.

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

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

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

CPython реализует что-то похожее с массивным связанным списком почти каждый раз, когда он построен с отладкой.

Отвечая тегу «Структуры данных», на низком уровне, таком как сборка, связанные списки являются идеальным способом хранения списков переменной длины других структур данных. Нет дополнительных затрат на поддержание длины или конца списка, и нет необходимости в элементах списка фиксированного размера. Последняя причина также относится к языкам более высокого уровня.

Связный список — это основная структура данных для Dancing Links, метода, используемого для эффективной реализации алгоритма X, который представляет собой алгоритм поиска с возвратом для поиска всех решений NP-полной задачи точного покрытия, к которой естественным образом сводятся многие сложные задачи. к.

Стеки и очереди являются примерами связанных списков.

2

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

Если я могу ответить на этот вопрос немного иначе, чем другие, то в последнее время я использую связанные списки для организации музыкальных списков. Списки обеспечивают отличный способ хранения и сортировки музыки по исполнителю, песне, альбому, даже по рейтингу или продолжительности песни. У меня двусвязный список, но я вижу, что он применим и к односвязному списку. Списки покупок тоже подойдут, а если у вас ОКР, как у моей мамы, вы можете легко организовать его между рядами и замороженными продуктами в последнюю очередь!

Если мы включим стеки и очереди, очевидным для очередей будет очередь печати или музыкальный плейлист (списки в любом смысле, очевидно, хороши).

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

Реализация связанного списка обнаружена в дикой природе:

Кэш Python LRU:

 ...
...
    cache_get = cache.get # связанный метод для поиска ключа или возврата None
    cache_len = cache.__len__ # получить размер кеша без вызова len()
    lock = RLock() # потому что обновления связанного списка не являются потокобезопасными
    root = [] # корень кругового двусвязного списка
...
...
 

Однако обратите внимание, что функция _lru_cache_wrapper на самом деле импортирована из реализации C-модуля :

 ...
...
структура typedef lru_list_elem {
    PyObject_HEAD
    структура lru_list_elem *пред., *след.; /* заимствованные ссылки */
    хэш Py_hash_t;
    PyObject *ключ, *результат;
} lru_list_elem;
. ..
...
 

Плюсы связанного списка:

  • Динамическое хранилище сводит на нет фрагментацию
  • Быстрая установка/удаление
  • Быстрый доступ к первому элементу (и конец, если реализован двунаправленный)
  • Эффективное использование памяти

Минусы связанного списка:

  • Медленное перемещение

Внезапно я бы реализовал стеки, очереди и насосы сообщений, используя связанные списки. Я бы также реализовал дерево данных, используя связанные списки с несколькими ссылками для быстрой вставки/удаления.

1

Твой ответ

Зарегистрируйтесь или войдите в систему

Зарегистрируйтесь с помощью Google

Зарегистрироваться через Facebook

Зарегистрируйтесь, используя адрес электронной почты и пароль

Опубликовать как гость

Электронная почта

Обязательно, но не отображается

Опубликовать как гость

Электронная почта

Требуется, но не отображается

Введение — Real Python