Редактирование: Дерево поиска, наивная реализация

Перейти к: навигация, поиск

Внимание! Вы не авторизовались на сайте. Ваш IP-адрес будет публично видимым, если вы будете вносить любые правки. Если вы войдёте или создадите учётную запись, правки вместо этого будут связаны с вашим именем пользователя, а также у вас появятся другие преимущества.

Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия Ваш текст
Строка 65: Строка 65:
 
=== Поиск следующего и предыдущего элемента ===
 
=== Поиск следующего и предыдущего элемента ===
 
====Реализация с использованием информации о родителе====
 
====Реализация с использованием информации о родителе====
Если у узла есть правое поддерево, то следующий за ним элемент будет минимальным элементом в этом поддереве. Если у него нет правого поддерева, то нужно следовать вверх, пока не встретим узел, который является левым дочерним узлом своего родителя. Поиск предыдущего выполнятся аналогично. Если у узла есть левое поддерево, то предыдущий ему элемент будет максимальным элементом в этом поддереве. Если у него нет левого поддерева, то нужно следовать вверх, пока не встретим узел, который является правым дочерним узлом своего родителя.  
+
Если у узла есть правое поддерево, то следующий за ним элемент будет минимальным элементом в этом поддереве. Если у него нет правого поддерева, то нужно следовать вверх, пока не встретим узел, который является левым дочерним узлом своего родителя. Поиск предыдущего выполнятся аналогично. Если у узла есть левое поддерево, то следующий за ним элемент будет максимальным элементом в этом поддереве. Если у него нет левого поддерева, то нужно следовать вверх, пока не встретим узел, который является правым дочерним узлом своего родителя.  
 
  '''Node''' next(x : '''Node'''):
 
  '''Node''' next(x : '''Node'''):
 
     '''if''' x.right != ''null''
 
     '''if''' x.right != ''null''
Строка 84: Строка 84:
 
     '''return''' y
 
     '''return''' y
 
Обе операции выполняются за время <tex>O(h)</tex>.
 
Обе операции выполняются за время <tex>O(h)</tex>.
 
 
====Реализация без использования информации о родителе====
 
====Реализация без использования информации о родителе====
 
Рассмотрим поиск следующего элемента для некоторого ключа <tex>x</tex>. Поиск будем начинать с корня дерева, храня текущий узел <tex>current</tex> и узел <tex>successor</tex>, последний посещенный узел, ключ которого больше <tex>x</tex>. <br>
 
Рассмотрим поиск следующего элемента для некоторого ключа <tex>x</tex>. Поиск будем начинать с корня дерева, храня текущий узел <tex>current</tex> и узел <tex>successor</tex>, последний посещенный узел, ключ которого больше <tex>x</tex>. <br>
Строка 173: Строка 172:
 
           successor.right.parent = successor.parent
 
           successor.right.parent = successor.parent
 
       '''else'''
 
       '''else'''
         successor.parent.right = successor.left
+
         successor.parent.right = successor.right
         '''if''' successor.left != ''null''
+
         '''if''' successor.right != ''null''
 
           successor.right.parent = successor.parent
 
           successor.right.parent = successor.parent
  
 
====Рекурсивная реализация====
 
====Рекурсивная реализация====
При рекурсивном удалении узла из бинарного дерева нужно рассмотреть три случая: удаляемый элемент находится в левом поддереве текущего поддерева, удаляемый элемент находится в правом поддереве или удаляемый элемент находится в корне. В двух первых случаях нужно рекурсивно удалить элемент из нужного поддерева. Если удаляемый элемент находится в корне текущего поддерева и имеет два дочерних узла, то нужно заменить его минимальным элементом из правого поддерева и рекурсивно удалить '''этот''' минимальный элемент из правого поддерева. Иначе, если удаляемый элемент имеет один дочерний узел, нужно заменить его потомком. Время работы алгоритма {{---}} <tex>O(h)</tex>.
+
При рекурсивном удалении узла из бинарного дерева нужно рассмотреть три случая: удаляемый элемент находится в левом поддереве текущего поддерева, удаляемый элемент находится в правом поддереве или удаляемый элемент находится в корне. В двух первых случаях нужно рекурсивно удалить элемент из нужного поддерева. Если удаляемый элемент находится в корне текущего поддерева и имеет два дочерних узла, то нужно заменить его минимальным элементом из правого поддерева и рекурсивно удалить минимальный элемент из правого поддерева. Иначе, если удаляемый элемент имеет один дочерний узел, нужно заменить его потомком. Время работы алгоритма {{---}} <tex>O(h)</tex>.
 
Рекурсивная функция, возвращающая дерево с удаленным элементом <tex>z</tex>:
 
Рекурсивная функция, возвращающая дерево с удаленным элементом <tex>z</tex>:
 
  '''Node''' delete(root : '''Node''', z : '''T'''):              <font color="green">// корень поддерева, удаляемый ключ</font>
 
  '''Node''' delete(root : '''Node''', z : '''T'''):              <font color="green">// корень поддерева, удаляемый ключ</font>
Строка 189: Строка 188:
 
   '''else if''' root.left != ''null'' '''and''' root.right != ''null''
 
   '''else if''' root.left != ''null'' '''and''' root.right != ''null''
 
     root.key = minimum(root.right).key
 
     root.key = minimum(root.right).key
     root.right = delete(root.right, root.key)
+
     root.right = delete(root.right, root.right.key)
 
   '''else'''
 
   '''else'''
 
     '''if''' root.left != ''null''
 
     '''if''' root.left != ''null''
 
       root = root.left
 
       root = root.left
     '''else if''' root.right != ''null''
+
     '''else'''
 
       root = root.right
 
       root = root.right
    '''else'''
 
      root = ''null''
 
 
   '''return''' root
 
   '''return''' root
  
Строка 211: Строка 208:
 
Функция принимает на вход исследуемую вершину, а также два значения: <tex>\mathtt{min}</tex> и <tex>\mathtt{max}</tex>, которые до вызова функции равнялись <tex> \infty </tex> и <tex> -\infty </tex> соответственно, где <tex> \infty </tex> — очень большое число, т.е. ни один ключ дерева не превосходит его по модулю. Казалось бы, два последних параметра не нужны. Но без них программа может выдать неверный ответ, так как сравнения только вершины и её детей недостаточно. Необходимо также помнить, в каком поддереве для более старших предков мы находимся. Например, в этом дереве вершина с номером <tex>8</tex> находится левее вершины, в которой лежит <tex>5</tex>, чего не должно быть в дереве поиска, однако после проверки функция бы вернула <tex>\mathtt{true}</tex>.
 
Функция принимает на вход исследуемую вершину, а также два значения: <tex>\mathtt{min}</tex> и <tex>\mathtt{max}</tex>, которые до вызова функции равнялись <tex> \infty </tex> и <tex> -\infty </tex> соответственно, где <tex> \infty </tex> — очень большое число, т.е. ни один ключ дерева не превосходит его по модулю. Казалось бы, два последних параметра не нужны. Но без них программа может выдать неверный ответ, так как сравнения только вершины и её детей недостаточно. Необходимо также помнить, в каком поддереве для более старших предков мы находимся. Например, в этом дереве вершина с номером <tex>8</tex> находится левее вершины, в которой лежит <tex>5</tex>, чего не должно быть в дереве поиска, однако после проверки функция бы вернула <tex>\mathtt{true}</tex>.
  
  '''bool''' isBinarySearchTree(root: '''Node'''):                    <font color="green">// Здесь root — корень заданного двоичного дерева.</font>
+
  '''function''' look()
 
   
 
   
   '''bool''' check(v : '''Node''', min: '''T''', max: '''T'''):                 <font color="green">// min и max — минимально и максимально допустимые значения в вершинах поддерева.</font>
+
   '''bool''' check(v : '''Node''', min: '''int''', max: '''int'''):       <font color="green">// min и max — минимально и максимально допустимые значения в вершинах поддерева.</font>
     '''if''' v == ''null''                   '''return''' ''true''
+
     '''if''' v.left != ''null''
     '''if''' v.key <= min '''or''' max <= v.key '''return''' ''false''
+
      '''if''' v.left.key > v.key '''or''' v.left.key < min
    '''return''' check(v.left, min, v.key) '''and''' check(v.right, v.key, max)
+
        '''return''' ''false''
 +
      '''else''' '''return''' check(v.left, min, v.key)
 +
     '''if''' v.right != ''null''
 +
      '''if''' v.right.key < v.key '''or''' v.right.key > max
 +
        '''return''' ''false''
 +
      '''else''' '''return''' check(v.right, v.key, max)
 +
    '''return''' ''true''
 
   
 
   
   '''return''' check(root, <tex> -\infty </tex>, <tex> \infty </tex>)
+
   check(root, <tex> \infty </tex>, <tex> -\infty </tex>)                 <font color="green">// root - корень дерева.</font>
  
 
Время работы алгоритма {{---}} <tex>O(n)</tex>, где <tex>n</tex> {{---}} количество вершин в дереве.
 
Время работы алгоритма {{---}} <tex>O(n)</tex>, где <tex>n</tex> {{---}} количество вершин в дереве.
Строка 225: Строка 228:
  
 
{{Задача
 
{{Задача
|definition = Найти в данном дереве такую вершину, что она будет корнем поддерева поиска с наибольшим количеством вершин.
+
|definition = Найти в данном дереве такую вершину, что поддерево, для которого она является корнем, является деревом поиска и оно максимально.
 +
}}
 +
 
 +
{{Задача
 +
|definition = Выделить в данном дереве наибольшее возможное количество соседних вершин, образующих дерево поиска.
 
}}
 
}}
Если мы будем приведённым выше способом проверять каждую вершину, мы справимся с задачей за <tex>O(n^2)</tex>. Но её можно решить за <tex>O(n)</tex>, идя от корня и проверяя все вершины по одному разу, основываясь на следующих фактах:
+
Рассмотрим каждую вершину дерева, предполагая, что она может быть корнем максимального поддерева поиска. Найдём для каждой из них количество всех вершин, которые могут находиться в таком поддереве. Максимальный из результатов, получаемых на каждом шаге, будем запоминать. Вместе с максимумом будем запоминать и соответствующую ему вершину. После того, как мы обошли всё дерево и нашли корень дерева поиска с наибольшим количеством вершин, при помощи обхода <tex>\mathrm{preorderTraversal}</tex> выводим все вершины на экран.
* Значение в вершине больше максимума в её левом поддереве;
+
 
* Значение в вершине меньше минимума в её правом поддереве;
+
'''Node''' root()
* Левое и правое поддерево являются деревьями поиска.
+
  maxdp = -1
 +
  maxroot = ''null''
 +
  '''for''' u '''in''' Tree          <font color="green">// Здесь Tree — заданное двоичное дерево.</font>
 +
    dp = dfs(u, <tex> -\infty </tex>, <tex> \infty </tex>)
 +
    '''if''' dp > maxdp
 +
      maxdp = dp
 +
      maxroot = u
 +
  '''return''' maxroot
 +
 
 +
Функция <tex>\mathtt{dfs}</tex> позволяет найти для каждой вершин максимально возможное количество узлов поддерева. На вход функции подаются сама анализируемая вершина и левая и правая границы интервала, в которой могут находиться значения в её поддереве. Начальные значения двух последних аргументов равны <tex> -\infty </tex> и <tex> \infty </tex> соответственно.
  
Введём <tex>\mathtt{v.min}</tex> и <tex>\mathtt{v.max}</tex>, которые будут хранить минимум в левом поддереве вершины и максимум в правом. Тогда мы должны будем проверить, являются ли эти поддеревья деревьями поиска и, если да, лежит ли ключ вершины <tex>\mathtt{v}</tex> между этими значениями <tex>\mathtt{v.min}</tex> и <tex>\mathtt{v.max}</tex>. Если вершина является листом, она автоматически становится деревом поиска, а её ключ {{---}} минимумом или максимумом для её родителя (в зависимости от расположения вершины). Функция <tex>\mathtt{cnt}</tex> записывает в <tex>\mathtt{v.kol}</tex> количество вершин в дереве, если оно является деревом поиска или <tex>\mathtt{-1}</tex> в противном случае. После выполнения функции ищем за линейное время вершину с наибольшим значением <tex>\mathtt{v.kol}</tex>.
+
В основе функции также лежит [[Обход в глубину, цвета вершин|обход в глубину]]. Рекурсивная функция обходит всех существующих детей вершины, поданной на вход, и, если ребёнок не нарушает условия дерева поиска, она добавляет его в поддерево и анализирует его потомков. В этом случае роль <tex>v</tex> будет разыгрывать ребёнок, удовлетворяющий условию дерева поиска. Если он был левым сыном, то максимально возможному значению присваивается число, стоящее в его родителе, а минимальное возможное значение не изменяется. Наоборот, если он был правым сыном, увеличиваем минимум, а максимум оставляем тем же. В случае, когда левый или правый сын не удовлетворяет условию дерева поиска, этот узел не включается в искомое поддерево и дальше не рассматривается.
  
'''int''' count(root: '''Node'''):                <font color="green">// root — корень заданного двоичного дерева.</font>
+
Функция возвращает значение переменной <tex>\mathtt{res}</tex>, где записано количество вершин поддерева.
 
  '''int''' cnt(v: '''Node'''):
 
    '''if''' v == ''null''
 
      v.kol = 0
 
      '''return''' = 0
 
    '''if''' cnt(v.left) != -1 '''and''' cnt(v.right) != -1
 
      '''if''' v.left == ''null'' '''and''' v.right == ''null''
 
        v.min = v.key
 
        v.max = v.key
 
        v.kol = 1
 
        '''return''' 1
 
      '''if''' v.left == ''null''
 
        '''if''' v.right.max > v.key
 
          v.min = v.key
 
          v.kol = cnt(v.right) + 1
 
          '''return''' v.kol
 
      '''if''' v.right == ''null''
 
        '''if''' v.left.min < v.key
 
          v.max = v.key
 
          v.kol = cnt(v.left) + 1
 
          '''return''' v.kol
 
      '''if''' v.left.min < v.key '''and''' v.right.max > v.key
 
        v.min = v.left.min
 
        v.max = v.right.max
 
        v.kol = v.left.kol + v.right.kol + 1
 
        v.kol = cnt(v.left) + cnt(v.right) + 1
 
        '''return''' v.kol
 
    '''return''' -1
 
 
  '''return''' cnt(root)
 
  
Алгоритм работает за <tex>O(n)</tex>, так как мы прошлись по дереву два раза за время, равное количеству вершин.
+
'''int''' dfs(v: '''Node''', max: '''T''', min: '''T''')
 +
  res = 1
 +
  '''if''' v.left != ''null''
 +
    '''if''' v.left.key < v.key '''and''' v.left.key > max
 +
      res += dfs(v.left, v.left.key, min)
 +
  '''if''' v.right != ''null''
 +
    '''if''' v.right.key > v.key '''and''' v.right.key < min
 +
      res += dfs(v.left, max, v.left.key)
 +
  '''return''' res
  
 
===Восстановление дерева по результату обхода preorderTraversal===
 
===Восстановление дерева по результату обхода preorderTraversal===
Строка 273: Строка 267:
 
|definition = Восстановить дерево по последовательности, выведенной после выполнения процедуры <tex>\mathrm{preorderTraversal}</tex>.
 
|definition = Восстановить дерево по последовательности, выведенной после выполнения процедуры <tex>\mathrm{preorderTraversal}</tex>.
 
}}
 
}}
[[Файл:BST_from_seq.gif|right|thumb|257px|Восстановление дерева поиска по последовательности ключей]]
+
[[Файл:BST_from_seq.gif|right|thumb|260px|Восстановление дерева поиска по последовательности ключей]]
  
Как мы помним, процедура <tex>\mathrm{preorderTraversal}</tex> выводит значения в узлах поддерева следующим образом: сначала идёт до упора влево, затем на каком-то моменте делает шаг вправо и снова движется влево. Это продолжается до тех пор, пока не будут выведены все вершины. Полученная последовательность позволит нам однозначно определить расположение всех узлов поддерева. Первая вершина всегда будет в корне. Затем, пока не будут использованы все значения, будем последовательно подвешивать левых сыновей к последней добавленной вершине, пока не найдём номер, нарушающий убывающую последовательность, а для каждого такого номера будем искать вершину без правого потомка, хранящую наибольшее значение, не превосходящее того, которое хотим поставить, и подвешиваем к ней элемент с таким номером в качестве правого сына. Когда мы, желая найти такую вершину, встречаем какую-нибудь другую, уже имеющую правого сына, проходим по ветке вправо. Мы имеем на это право, так как если такая вершина стоит, то процедура обхода в ней уже побывала и поворачивала вправо, поэтому спускаться в другую сторону смысла не имеет. Вершину с максимальным ключом, с которой будем начинать поиск, будем запоминать. Она будет обновляться каждый раз, когда появится новый максимум.
+
Как мы помним, процедура <tex>\mathrm{preorderTraversal}</tex> выводит значения в узлах поддерева следующим образом: сначала идёт до упора влево, затем на каком-то моменте делает шаг вправо и снова движется влево. Это продолжается до тех пор, пока не будут выведены все вершины. Полученная последовательность позволит нам однозначно определить расположение всех узлов поддерева. Первая вершина всегда будет в корне. Затем, пока не будут использованы все значения, будем последовательно подвешивать левых сыновей к последней добавленной вершине, пока не найдём номер, нарушающий убывающую последовательность, а для каждого такого номера будем искать вершину без правого потомка, хранящую наибольшее значение, не превосходящее того, которое хотим поставить, и подвешиваем к ней элемент с таким номером в качестве правого сына.
  
Процедура восстановления дерева работает за <tex>O(n)</tex>.
+
Разберём алгоритм на примере последовательности для приведённого выше дерева. Она выглядит так: ''8 2 1 4 3 5''. Сначала в корень записывается ''8''. Затем его левым сыном становится вершина с номером ''2'', а её левым сыном — ''1''. Следующее значение — ''4'' — уже нарушает убывающую подпоследовательность. Подберём для него вершину, где лежит значение, меньшее его, причём такая вершина максимальна. В противном случае он будет превосходить и прародителя, находясь в его левом поддереве, а это противоречит определению дерева поиска. Очевидно, что нельзя также подвесить его и к вершине, которая хранить большее значение. Для вершины ''4'' родителем будет узел с числом ''2''. Сделаем его правым сыном рассматриваемую вершину. Затем снова дадим левых потомков последней добавленной вершине, опять же, пока не найдём ключ, нарушающий порядок убывания. В нашем случае в дерево дописывается ''3''. Для следующего значения снова ищем родителя, для которого он станет правым сыном. Это значение равно ''4''. Добавляем ''5'' как правого сына для вершины ''4''. Вершины закончились, мы построили дерево.
  
 
+
  '''func''' seq2bst()
Разберём алгоритм на примере последовательности <tex>\mathtt{8}</tex> <tex>\mathtt{2}</tex> <tex>\mathtt{1}</tex> <tex>\mathtt{4}</tex> <tex>\mathtt{3}</tex> <tex>\mathtt{5}</tex>.
 
 
 
Будем выделять красным цветом вершины, рассматриваемые на каждом шаге, чёрным жирным {{---}} их родителей, курсивом {{---}} убывающие подпоследовательности (в случаях, когда мы их рассматриваем) или претендентов на добавление к ним правого ребёнка (когда рассматривается вершина, нарушающая убывающую последовательность).
 
{| style="background-color:#CCC;margin:0.5px"
 
!style="background-color:#EEE"| Состояние
 
последовательности
 
!style="background-color:#EEE"| Действие
 
!style="background-color:#EEE"| Пояснение
 
|-
 
|style="background-color:#FFF;padding:2px 10px"| <span style="color:red">'''8'''</span> 2 1 4 3 5
 
|style="background-color:#FFF;padding:2px 10px"| Делаем вершину корнем.
 
|style="background-color:#FFF;padding:2px 10px"| ''Первая вершина всегда будет корнем, так как вывод начинался с него.''
 
|-
 
|style="background-color:#FFF;padding:2px 10px"| '''''8''''' <span style="color:red">'''''2'''''</span> ''1'' 4 3 5
 
|rowspan=2 style="background-color:#FFF;padding:2px 10px"|Находим убывающую подпоследовательность. Каждую вершину подвешиваем к последней из взятых ранее в качестве левого сына.
 
|rowspan=2 style="background-color:#FFF;padding:2px 10px"| ''Каждая последующая вершина становится левым сыном предыдущей, так как выводя ключи, мы двигались по дереву поиска влево, пока есть вершины.''
 
|-
 
| style="background-color:#FFF;padding:2px 10px"| ''8 '''2''''' <span style="color:red">'''''1'''''</span> 4 3 5
 
|-
 
|style="background-color:#FFF;padding:2px 10px"| ''8 '''2''''' 1 <span style="color:red">'''4'''</span> 3 5
 
|style="background-color:#FFF;padding:2px 10px"| Для вершины, нарушившей убывающую последовательность, ищем максимальное значение, меньшее его. В данном случае оно равно <tex>\mathtt{2}</tex>. Затем добавляем вершину.
 
|style="background-color:#FFF;padding:2px 10px"| ''На моменте вывода следующего номера процедура обратилась уже к какому-то из правых поддеревьев, так как влево идти уже некуда. Значит, нам необходимо найти узел, для которого данная вершина являлась бы правым сыном. Очевидно, что в её родителе не может лежать значение, которое больше её ключа. Но эту вершину нельзя подвесить и к меньшим, иначе нашёлся бы более старший предок, также хранящий какое-то значение, которое меньше, чем в исследуемой. Для этого предка вершина бы попала в левое поддерево. И тогда возникает противоречие с определением дерева поиска. Отсюда следует, что родитель определяется единственным образом {{---}} он хранит максимум среди ключей, не превосходящих значения в подвешиваемой вершине, что и требовалось доказать.''
 
|-
 
|style="background-color:#FFF;padding:2px 10px"| 8 2 1 '''''4''''' <span style="color:red">'''''3'''''</span> 5
 
|style="background-color:#FFF;padding:2px 10px"| Находим убывающую подпоследовательность. Каждую вершину подвешиваем к последней из взятых ранее в качестве левого сына.
 
|style="background-color:#FFF;padding:2px 10px"| ''Зайдя в правое поддерево, процедура обхода снова до упора начала двигаться влево, поэтому действуем аналогичным образом.''
 
|-
 
|style="background-color:#FFF;padding:2px 10px"| ''8'' 2 1 '''''4''''' 3 <span style="color:red">'''5'''</span>
 
|style="background-color:#FFF;padding:2px 10px"| Для этой вершины ищем максимальное значение, меньшее его. Затем добавляем вершину.
 
|style="background-color:#FFF;padding:2px 10px"| ''Здесь процедура снова обратилась к правому поддереву. Рассуждения аналогичны. Ключ родителя этой вершины равен <tex>\mathtt{4}</tex>.''
 
|}
 
  
 
==См. также==
 
==См. также==
Строка 316: Строка 279:
 
* [[Рандомизированное бинарное дерево поиска]]
 
* [[Рандомизированное бинарное дерево поиска]]
 
* [[Красно-черное дерево]]
 
* [[Красно-черное дерево]]
* [[АВЛ-дерево]]
+
     
 
 
 
==Источники информации==
 
==Источники информации==
 
* [https://ru.wikipedia.org/wiki/%D0%94%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0 Википедия {{---}} Двоичное дерево поиска]
 
* [https://ru.wikipedia.org/wiki/%D0%94%D0%B2%D0%BE%D0%B8%D1%87%D0%BD%D0%BE%D0%B5_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0 Википедия {{---}} Двоичное дерево поиска]

Пожалуйста, учтите, что любой ваш вклад в проект «Викиконспекты» может быть отредактирован или удалён другими участниками. Если вы не хотите, чтобы кто-либо изменял ваши тексты, не помещайте их сюда.
Вы также подтверждаете, что являетесь автором вносимых дополнений, или скопировали их из источника, допускающего свободное распространение и изменение своего содержимого (см. Викиконспекты:Авторские права). НЕ РАЗМЕЩАЙТЕ БЕЗ РАЗРЕШЕНИЯ ОХРАНЯЕМЫЕ АВТОРСКИМ ПРАВОМ МАТЕРИАЛЫ!

Чтобы изменить эту страницу, пожалуйста, ответьте на приведённый ниже вопрос (подробнее):

Отменить | Справка по редактированию (в новом окне)

Шаблоны, используемые на этой странице: