<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=213.222.232.86&amp;*</id>
		<title>Викиконспекты - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=213.222.232.86&amp;*"/>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/213.222.232.86"/>
		<updated>2026-05-19T18:04:08Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA&amp;diff=49666</id>
		<title>Список</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA&amp;diff=49666"/>
				<updated>2015-10-21T18:06:35Z</updated>
		
		<summary type="html">&lt;p&gt;213.222.232.86: /* Поиск цикла в списке */ repeat until, в отличие от do while продолжает работу при ложности условия&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''Связный список''' (англ. ''List'') {{---}} структура данных, состоящая из элементов, содержащих помимо собственных данных ссылки на следующий и/или предыдущий элемент списка. С помощью списков можно реализовать такие структуры данных как [[стек]] и [[очередь]]. &lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
==Односвязный список ==&lt;br /&gt;
Простейшая реализация списка. В узлах хранятся данные и указатель на следующий элемент в списке.&lt;br /&gt;
[[Файл:simpleSpisok.png|center|400px]]&lt;br /&gt;
==Двусвязный список ==&lt;br /&gt;
Также хранится указатель на предыдущий элемент списка, благодаря чему становится проще удалять и переставлять элементы.&lt;br /&gt;
[[Файл:twiceSpisok.png|center|400px]]&lt;br /&gt;
===XOR-связный список ===&lt;br /&gt;
В некоторых случаях использование двусвязного списка в явном виде является нецелесообразным. В целях экономии памяти можно хранить только результат выполнения операции Xor над адресами предыдущего и следующего элементов списка. Таким образом, зная адрес предыдущего элемента, мы можем вычислить адрес следующего элемента.&lt;br /&gt;
==Циклический список==&lt;br /&gt;
Первый элемент является следующим для последнего элемента списка.&lt;br /&gt;
[[Файл:circleSpisok.png|center|450px]]&lt;br /&gt;
==Операции на списке==&lt;br /&gt;
Рассмотрим базовые операции на примере односвязного списка.&lt;br /&gt;
===Вставка===&lt;br /&gt;
Очевиден случай, когда необходимо добавить элемент (&amp;lt;tex&amp;gt;newHead&amp;lt;/tex&amp;gt;) в голову списка. Установим в этом элементе ссылку на старую голову, и обновим указатель на голову.&lt;br /&gt;
&lt;br /&gt;
  '''function''' insert(Node thatElement):&lt;br /&gt;
    thatElement.next = thisElement.next &lt;br /&gt;
    thisElement.next = thatElement&lt;br /&gt;
[[Файл:insertAfter.png|center|490px]]&lt;br /&gt;
&lt;br /&gt;
===Поиск===&lt;br /&gt;
Для того, чтобы найти элемент по значению (&amp;lt;tex&amp;gt;value&amp;lt;/tex&amp;gt;), будем двигаться по списку от головы до конца и сравнивать значение в элементах с искомым. Если элемента в списке нет, то возвращаем &amp;lt;tex&amp;gt;NULL&amp;lt;/tex&amp;gt;. &lt;br /&gt;
  Node search('''int''' value):&lt;br /&gt;
    node = head&lt;br /&gt;
    '''while''' node != ''NULL'' '''and''' value != node.value&lt;br /&gt;
        node = node.next&lt;br /&gt;
    '''return''' node&lt;br /&gt;
&lt;br /&gt;
===Удаление===&lt;br /&gt;
Для того, чтобы удалить голову списка, переназначим указатель на голову на второй элемент списка, а голову удалим.&lt;br /&gt;
  '''function''' removeHead():&lt;br /&gt;
    '''if''' head != ''NULL''&lt;br /&gt;
        tmp = head&lt;br /&gt;
        head = head.next&lt;br /&gt;
        '''delete''' tmp&lt;br /&gt;
[[Файл:removeHead.png|center|430px]]&lt;br /&gt;
Удаление элемента после заданного (&amp;lt;tex&amp;gt;thisElement&amp;lt;/tex&amp;gt;) происходит следующим образом: изменим ссылку на следующий элемент на следующий за удаляемым, затем удалим нужный объект.&lt;br /&gt;
  '''function''' removeAfter(Node thisElement):&lt;br /&gt;
    '''if''' thisElement.next != ''NULL''&lt;br /&gt;
        tmp = thisElement.next&lt;br /&gt;
        thisElement.next = thisElement.next.next&lt;br /&gt;
        '''delete''' tmp&lt;br /&gt;
[[Файл:removeAfter.png|center|550px]]&lt;br /&gt;
&lt;br /&gt;
==Поиск цикла в списке==&lt;br /&gt;
Для начала необходимо уметь определять {{---}} список циклический или нет. Воспользуемся алгоритмом Флойда &amp;quot;Черепаха и заяц&amp;quot;. Пусть за одну итерацию первый указатель (черепаха) переходит к следующему элементу списка, а второй указатель (заяц) на два элемента вперед. Тогда, если эти два указателя встретятся, то цикл найден, если дошли до конца списка, то цикла нет.&lt;br /&gt;
  '''boolean''' hasCycle(Node head):&lt;br /&gt;
    tortoise = head&lt;br /&gt;
    hare = head&lt;br /&gt;
    '''repeat'''&lt;br /&gt;
      '''if''' hare == ''NULL'' '''or''' hare.next == ''NULL'' &lt;br /&gt;
        '''return''' ''false''&lt;br /&gt;
      tortoise = tortoise.next&lt;br /&gt;
      hare = hare.next.next&lt;br /&gt;
    '''until''' tortoise == hare&lt;br /&gt;
    '''return''' ''true''&lt;br /&gt;
Если цикла не существует, то заяц первым дойдет до конца и функция возвратит &amp;lt;tex&amp;gt;false&amp;lt;/tex&amp;gt;. В другом случае, в тот момент, когда и черепаха и заяц находятся в цикле, расстояние между ними будет сокращаться на &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;, что гарантирует их встречу за конечное время.&lt;br /&gt;
&lt;br /&gt;
==Поиск длины хвоста в списке с циклом==&lt;br /&gt;
Так как для поиска хвоста мы должны знать, что цикл существует, воспользуемся предыдущей функцией и при выходе из неё запомним &amp;quot;момент встречи&amp;quot; зайца и черепахи. Назовем её &amp;lt;tex&amp;gt;pointMeeting&amp;lt;/tex&amp;gt;.&lt;br /&gt;
===Наивные реализации===&lt;br /&gt;
====Реализация за &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt;====&lt;br /&gt;
Будем последовательно идти от начала цикла и проверять, лежит ли этот элемент на цикле. На каждой итерации запустим от &amp;lt;tex&amp;gt;pointMeeting&amp;lt;/tex&amp;gt; вперёд указатель. Если он окажется в текущем элементе, прежде чем посетит &amp;lt;tex&amp;gt;pointMeeting&amp;lt;/tex&amp;gt; снова, то точку окончания (начала) хвоста нашли.&lt;br /&gt;
&lt;br /&gt;
====Реализация за &amp;lt;tex&amp;gt;O(n \log n)&amp;lt;/tex&amp;gt;====&lt;br /&gt;
Реализацию, приведенную выше можно улучшить. Для этого воспользуемся [[Целочисленный_двоичный_поиск | бинарным поиском]]. Сначала проверим голову списка, потом сделаем &amp;lt;tex&amp;gt; 2 &amp;lt;/tex&amp;gt; шага вперёд, потом &amp;lt;tex&amp;gt; 4 &amp;lt;/tex&amp;gt;, потом  &amp;lt;tex&amp;gt; 8 &amp;lt;/tex&amp;gt; и так далее, пока не окажемся на цикле. Теперь у нас есть две позиции {{---}} на левой границе, где мы в хвосте, и на правой {{---}} в цикле. Сделаем бинарный поиск уже по этому отрезку и таким образом найдём цикл за &amp;lt;tex&amp;gt;O(n \log n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===Эффективная реализация===&lt;br /&gt;
Возможны два варианта цикла в списке. Первый вариант {{---}} сам список циклический (указатель &amp;lt;tex&amp;gt;next&amp;lt;/tex&amp;gt; последнего элемента равен первому), а второй вариант {{---}} цикл внутри списка (указатель &amp;lt;tex&amp;gt;next&amp;lt;/tex&amp;gt; последнего элемента равен любому другому (не первому). В первом случае найти длину цикла тривиально, во второй случай сводится к первому, если найти указатель на начало цикла. Достаточно запустить один указатель из &amp;lt;tex&amp;gt;pointMeeting&amp;lt;/tex&amp;gt;, а другой из головы с одной скоростью. Элемент, где оба указателя встретятся, будет началом цикла. Сложность алгоритма {{---}} &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Ниже приведена функция, которая находит эту точку, а возвращает длину хвоста списка. &lt;br /&gt;
  '''int''' getTail(Node head, Node pointMeeting):&lt;br /&gt;
    firstElement = head.next&lt;br /&gt;
    secondElement = pointMeeting.next&lt;br /&gt;
    lengthTail = 1&lt;br /&gt;
    '''while''' firstElement != secondElement&lt;br /&gt;
      firstElement = firstElement.next&lt;br /&gt;
      secondElement = secondElement.next&lt;br /&gt;
      lengthTail = lenghtTail + 1&lt;br /&gt;
    '''return''' lengthTail&lt;br /&gt;
====Доказательство корректности алгоритма====&lt;br /&gt;
Рассмотрим цикл длиной &amp;lt;tex&amp;gt;N&amp;lt;/tex&amp;gt; с хвостом длины &amp;lt;tex&amp;gt;L&amp;lt;/tex&amp;gt;. Напишем функции для обоих указателей в зависимости от шага &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt;. Очевидно, что встреча не может произойти при &amp;lt;tex&amp;gt;n \leqslant L&amp;lt;/tex&amp;gt;, так как в этом случае &amp;lt;tex&amp;gt;2n&amp;gt;n&amp;lt;/tex&amp;gt; для любого &amp;lt;tex&amp;gt;n&amp;gt;0&amp;lt;/tex&amp;gt;. Тогда положения указателей зададутся следующими функциями (при &amp;lt;tex&amp;gt;n&amp;gt;L&amp;lt;/tex&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;f_1(n) = L + (n-L) \bmod N&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;f_2(n) = L + (2n-L) \bmod N&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Приравнивая, получим &amp;lt;tex&amp;gt;n \bmod N = 0&amp;lt;/tex&amp;gt;, или &amp;lt;tex&amp;gt;n = k N, n &amp;gt; L&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;H&amp;lt;/tex&amp;gt; {{---}} голова списка, &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; {{---}} точка встречи, &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt; {{---}} первый элемент цикла, &amp;lt;tex&amp;gt;Q&amp;lt;/tex&amp;gt; {{---}} расстояние от &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; до &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt;. Тогда в точку &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt; можно прийти двумя путями: из &amp;lt;tex&amp;gt;H&amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt; длиной &amp;lt;tex&amp;gt;L&amp;lt;/tex&amp;gt; и из &amp;lt;tex&amp;gt;H&amp;lt;/tex&amp;gt; через &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; в &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt; длиной &amp;lt;tex&amp;gt;L + N = X + Q&amp;lt;/tex&amp;gt;, то есть:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;Q = L + N - X&amp;lt;/tex&amp;gt;, но так как &amp;lt;tex&amp;gt;X = kN&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;Q = L + (1-k) N&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;L = p N + M, 0 \leqslant M &amp;lt; N&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Известно, что&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;L &amp;lt; k N \leqslant L + N&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tex&amp;gt;pN + M &amp;lt; kN \leqslant (p+1)N + M&amp;lt;/tex&amp;gt; откуда &amp;lt;tex&amp;gt;k = p + 1&amp;lt;/tex&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Подставив полученные значения, получим:&lt;br /&gt;
&amp;lt;tex&amp;gt;Q = pN + M + (1 - p - 1)N = M = L \bmod N&amp;lt;/tex&amp;gt;, откуда следует, что если запустить указатели с одной скоростью из &amp;lt;tex&amp;gt;H&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt;, то они встретятся через &amp;lt;tex&amp;gt;L&amp;lt;/tex&amp;gt; шагов в точке &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt;. К этому времени вышедший из &amp;lt;tex&amp;gt;H&amp;lt;/tex&amp;gt; пройдёт ровно &amp;lt;tex&amp;gt;L&amp;lt;/tex&amp;gt; шагов и остановится в &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt;, вышедший из &amp;lt;tex&amp;gt;X&amp;lt;/tex&amp;gt; накрутит по циклу &amp;lt;tex&amp;gt;[L/N]&amp;lt;/tex&amp;gt; шагов и пройдёт ещё &amp;lt;tex&amp;gt;Q = L \bmod N&amp;lt;/tex&amp;gt; шагов. Поскольку &amp;lt;tex&amp;gt;L = [L/N] + L \bmod N&amp;lt;/tex&amp;gt;, то они встретятся как раз в точке &amp;lt;tex&amp;gt;A&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Задача про обращение списка==&lt;br /&gt;
Для того, чтобы обратить список, необходимо пройти по всем элементам этого списка, и все указатели на следующий элемент заменить на предыдущий.&lt;br /&gt;
Эта рекурсивная функция принимает указатель на голову списка и предыдущий элемент (при запуске указывать &amp;lt;tex&amp;gt;NULL&amp;lt;/tex&amp;gt;), а возвращает указатель на новую голову списка.&lt;br /&gt;
   &lt;br /&gt;
  Node reverse(Node current, Node prev):&lt;br /&gt;
    '''if''' current == ''NULL''&lt;br /&gt;
      '''return''' prev&lt;br /&gt;
    next = current.next&lt;br /&gt;
    current.next = prev&lt;br /&gt;
    '''return''' reverse(next, current)&lt;br /&gt;
&lt;br /&gt;
Алгоритм корректен, поскольку значения элементов в списке не изменяются, а все указатели &amp;lt;tex&amp;gt;next&amp;lt;/tex&amp;gt; изменят свое направление, не нарушив связности самого списка. &lt;br /&gt;
&lt;br /&gt;
==См.также==&lt;br /&gt;
* [[Динамический массив]]&lt;br /&gt;
&lt;br /&gt;
==Источники информации==&lt;br /&gt;
* [[wikipedia:Linked_list | Wikipedia {{---}} Linked list]]&lt;br /&gt;
* [[wikipedia:ru:Список_(информатика) | Википедия {{---}} Список]]&lt;br /&gt;
* Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн Алгоритмы: построение и анализ {{---}} 2-е изд. {{---}} М.: «Вильямс», 2007. {{---}} Глава 11.2. {{---}} ISBN 5-8489-0857-4&lt;br /&gt;
* Дональд Э. Кнут Искусство программирования. Том 1. Основные алгоритмы {{---}} 2-е изд. {{---}} М.: «Вильямс», 2012. {{---}} Глава 2.2. {{---}} ISBN 0-201-89685-0&lt;br /&gt;
&lt;br /&gt;
[[Категория: Дискретная математика и алгоритмы]]&lt;br /&gt;
[[Категория: Амортизационный анализ]]&lt;br /&gt;
[[Категория: Структуры данных]]&lt;/div&gt;</summary>
		<author><name>213.222.232.86</name></author>	</entry>

	</feed>