Опубликован: 05.01.2015 | Доступ: свободный | Студентов: 2178 / 0 | Длительность: 63:16:00
Лекция 15:

Поразрядный поиск

Patricia-деревья

Основанный на trie-деревьях поиск, который описан в разделе 15.2, обладает двумя недостатками. Во-первых, однонаправленные ветвления приводят к созданию дополнительных, но по сути необязательных, узлов в trie-дереве. Во-вторых, trie-деревья содержат два различных типа узлов, что усложняет алгоритмы (см. упражнения 15.20 и 15.21). В 1968 г. Моррисон (Morrison) нашел способ устранить обе эти проблемы с помощью применения метода, который он назвал patricia ( " practical algorithm to retrieve information coded in alphanumeric " — " практический алгоритм получения информации, закодированной алфавитно-цифровыми символами " ). Моррисон разработал свой алгоритм для приложений, индексирующих строки, наподобие рассмотренных в разделе 15.5, но он также эффективен и для реализации таблицы символов. Подобно DST-деревьям, patricia-деревья позволяют выполнять поиск N ключей в дереве, содержащем всего N узлов; подобно trie-деревьям, они требуют для одного поиска выполнения всего лишь около lgN сравнений разрядов и одного сравнения полного ключа, а также поддерживают другие операции АТД. Более того, эти характеристики производительности не зависят от длины ключей, и структура данных пригодна для ключей переменой длины.

Взяв структуру данных стандартного trie-дерева, мы устраняем однонаправленные пути с помощью простого приема: в каждый узел помещается индекс разряда, который должен проверяться для выбора пути из этого узла. Таким образом, мы сразу переходим к разряду, в котором должно приниматься важное решение, пропуская сравнения разрядов в узлах, в которых все ключи в поддереве имеют одинаковые разряды. А внешние узлы исключаются при помощи еще одного простого приема: данные хранятся во внутренних узлах, а ссылки на внешние узлы заменяются ссылками, которые указывают в обратном направлении вверх на нужный внутренний узел в trie-дереве. Эти два изменения позволяют представлять trie-деревья как бинарные деревья, состоящие из узлов с ключом и двумя ссылками (а также дополнительным полем под индекс); такие деревья называются patricia-деревьями (patricia trie). В patricia-деревьях ключи хранятся в узлах, как в DST-деревьях, а обход дерева выполняется в соответствии с разрядами искомого ключа, но ключи в узлах не используются для управления поиском при спуске вниз по дереву. Они хранятся там просто для возможного обращения к ним впоследствии, при достижении нижней части дерева.

Как было отмечено в предыдущем абзаце, понять работу алгоритма проще, если сначала заметить, что стандартные trie-деревья и patricia-деревья можно считать различными представлениями одной и той же абстрактной структуры trie-дерева. Например, trie-деревья, показанные на рис. 15.10 и вверху на рис. 15.11, где показаны поиск и вставка в patricia-деревьях, представляют ту же абстрактную структуру, что и trie-деревья на рис. 15.6. В алгоритмах поиска и вставки для patricia-деревьев используется, создается и поддерживается конкретное представление абстрактной структуры данных trie-дерева, которое отличается от используемого в алгоритмах поиска и вставки из раздела 15.2; но лежащая в их основе абстракция остается той же самой.

Программа 15.4 является реализацией алгоритма поиска в patricia-дереве. Используемый в ней метод отличается от поиска в trie-дереве тремя аспектами: нет явных пустых ссылок, в ключе проверяется не следующий разряд, а указанный, и поиск завершается сравнением ключа в точке, где происходит переход вверх по дереву. Указывает ли ссылка вверх, проверить легко, т.к. индексы разрядов в узлах (по определению) увеличиваются по мере перемещения вниз по дереву. Поиск начинается с корня и проходит вниз по дереву, используя в каждом узле индекс разряда для определения проверяемого разряда в искомом ключе — если этот разряд равен 1, выполняется переход вправо, а если 0 — влево.

 Поиск в patricia-дереве

Рис. 15.10. Поиск в patricia-дереве

При успешном поиске ключа R = 10010 в этом patricia-дереве выполняется переход вправо (поскольку нулевой бит равен 1), затем влево (поскольку бит 4 равен 0), что приводит к ключу R (единственному ключу в дереве, начинающемуся с последовательности 1***0).

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

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

При неудачном поиске ключа I = 01001 выполняется переход влево от корня (поскольку нулевой бит равен 0), затем по правой (направленной вверх) ссылке (поскольку первый бит равен 1), и выясняется, что ключ H (единственный ключ в дереве, начинающийся с последовательности 01) не равен I .

Программа 15.4. Поиск в patricia-дереве

Рекурсивная функция searchR возвращает уникальный узел, который может содержать запись с ключом v. Она спускается вниз по trie-дереву, используя биты дерева для управления поиском, но в каждом встреченном узле проверяет только один бит — указанный в поле bit. Функция прерывает поиск, встретив внешнюю ссылку, указывающую вверх. Функция поиска search вызывает функцию searchR, а затем проверяет ключ в этом узле для определения того, был ли поиск успешным или неудачным.

  private:
    Item searchR(link h, Key v, int d)
      { if (h->bit <= d) return h->item;
        if (digit(v, h->bit) == 0)
          return searchR(h->l, v, h->bit);
        else
        return searchR(h->r, v, h->bit);
      }
  public:
    Item search(Key v)
      { Item t = searchR(head, v, -1);
        return (v == t.key()) ? t : nullItem;
      }
       

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

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

Реализация вставки в patricia-деревья отражает два случая, возникающих при вставке в trie-деревья (см. рис. 15.11). Как обычно, информация о месте вставки нового ключа определяется в результате неудачного поиска. В trie-деревьях поиск может быть неудачным либо из-за пустой ссылки, либо из-за несовпадения ключа в листе. При использовании patricia-деревьев для определения требуемого типа вставки приходится выполнять больше действий, т.к. во время поиска пропускаются разряды, соответствующие однонаправленным путям.

 Вставка в patricia-дереве

Рис. 15.11. Вставка в patricia-дереве

Чтобы вставить ключ I в приведенное на рис. 15.10patricia-дерево, мы добавляем новый узел для проверки бита 4, поскольку ключи H = 01000 и I = 01001 отличаются только этим разрядом (вверху). В последующих поисках в trie-дереве, которые дойдут до нового узла, необходимо проверить ключ H (левая ссылка), если 4 разряд ключа поиска равен 0; а если этот разряд равен 1 (правая ссылка), то следует проверить ключ I. Для вставки ключа N = 01110 (внизу) между ключами H и I добавляется новый узел для проверки бита 2, поскольку именно этот бит отличает N от H и I.

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

 Построение patricia-дерева

Рис. 15.12. Построение patricia-дерева

Эта последовательность рисунков отражает результат вставки ключей A S E R C H в первоначально пустое patricia-дерево. На рис. 15.11 показан результат вставки ключей I и N в дерево, показанное на нижнем рисунке.

Программа 15.5 является реализацией алгоритма вставки в patricia-дерево. Ее код вытекает непосредственно из описания, приведенного в предыдущем абзаце, с одним дополнением: мы считаем, что ссылки на узлы, содержащие индексы разрядов, не большие, чем индекс текущего разряда — это ссылки на внешние узлы. Код вставки просто проверяет это свойство ссылок, но он не должен перемещать ключи или ссылки. На первый взгляд направленные вверх ссылки в patricia-деревьях выглядят загадочно, но выбор ссылок, которые должны использоваться при вставке каждого узла, удивительно прост. А использование одного типа узла вместо двух существенно упрощает код.

По построению все внешние узлы, расположенные ниже узла с индексом к, начинаются с тех же самых к разрядов (иначе для различения этих узлов понадобился бы узел с индексом, меньшим к). Следовательно, patricia-дерево можно преобразовать в стандартное trie-дерево, создав соответствующие внутренние узлы между узлами, в которых были пропущены разряды, и заменив ссылки вверх на ссылки, указывающие на внешние узлы (см. упражнение 15.48). Однако свойство 15.2 выполняется для patricia-деревьев не полностью, т.к. присваивание ключей внутренним узлам зависит от порядка вставки ключей. Структура внутренних узлов зависит от порядка вставки ключей, а внешние ссылки и размещение значений ключей — нет.

Программа 15.5. Вставка в patricia-дерево

Процесс вставки ключа в patricia-дерево начинается с поиска. Функция searchR из программы 15.5 приводит к уникальному ключу в дереве, который должен отличаться от вставляемого. Мы находим самый левый бит, которым отличаются этот и искомый ключи, а затем при помощи рекурсивной функции insertR спускаемся вниз по дереву и вставляем новый узел, содержащий v в этой позиции.

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

private:
  link insertR(link h, Item x, int d, link p)
    { Key v = x.key();
      if ((h->bit >= d) || (h->bit <= p->bit))
        { link t = new node(x); t->bit = d;
          t->l = (digit(v, t->bit) ? h : t);
          t->r = (digit(v, t->bit) ? t : h);
          return t;
        }
      if (digit(v, h->bit) == 0)
        h->l = insertR(h->l, x, d, h);
      else
      h->r = insertR(h->r, x, d, h);
      return h;
    }
public:
  void insert(Item x)
    { Key v = x.key(); int i;
      Key w = searchR(head->l, v, -1).key();
      if (v == w) return;
      for (i = 0; digit(v, i) == digit(w, i); i++) ;
      head->l = insertR(head->l, x, i, head);
    }
  ST(int maxN)
    { head = new node(nullItem);
      head->l = head->r = head;
    }
      

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

Patricia-деревья — наиболее показательный вариант метода поразрядного поиска: он позволяет находить разряды, которые различают ключи поиска, и встраивать их в структуру данных (без лишних узлов), быстро приводящую от любого искомого ключа к единственному ключу в структуре, который может быть равен искомому. На рис. 15.13 показано patricia-дерево, образованное теми же ключами, что и для построения trie-дерева на рис. 15.9: patricia-дерево не только содержит на 44% узлов меньше по сравнению со стандартным trie-деревом, но и почти идеально сбалансировано.

 Пример patriciu-дерева

Рис. 15.13. Пример patriciu-дерева

Это patricia-дерево, построенное в результате вставки примерно 200 случайных ключей, эквивалентно trie-дереву, приведенному на рис. 15.9, в котором удалены однонаправленные пути. Результирующее дерево почти идеально сбалансировано.

Программа 15.6. Сортировка в patricia-дереве

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

  private:
    void showR(link h, ostream& os, int d)
      { if (h->bit <= d) { h->item.show(os); return; }
        showR(h->l, os, h->bit);
        showR(h->r, os, h->bit);
      }
  public:
    void show(ostream& os)
      { showR(head->l, os, -1); }
      

Лемма 15.5. Вставка или поиск случайного ключа в patricia-дереве, построенном из N случайных битовых строк, требует приблизительно lgN битовых сравнений в среднем и приблизительно 2 lgN битовых сравнений в худшем случае. Количество битовых сравнений никогда не превышает длины ключа.

Эта лемма непосредственно следует из леммы 15.3, поскольку длина путей в patricia-деревьях не превышает длину путей в соответствующих trie-деревьях. Точный анализ среднего случая в patricia-дереве сложен; из него следует, что в среднем в patricia-дереве требуется на одно сравнение меньше, чем в стандартном trie-дереве (см. раздел ссылок). $\blacksquare$

В таблица 15.1 приведены экспериментальные данные, подтверждающие вывод, что в случае целочисленных ключей DST-деревья, стандартные trie-деревья и patricia-деревья имеют сравнимую производительность (а также обеспечивают время поиска, которое сравнимо или меньше времени поиска методами на основе сбалансированных деревьев из "Сбалансированные деревья" . Поэтому данные методы, несомненно, следует рассматривать в качестве возможных реализаций таблиц символов даже при использовании ключей, которые представимы в виде коротких битовых строк, с учетом ряда упомянутых очевидных компромиссов.

Таблица 15.1. Экспериментальное сравнение реализаций trie-деревьев
N Создание Успешный поиск
B D T P B D T P
1250 1 1 1 1 0 1 1 0
2500 2 2 4 3 1 1 2 1
5000 4 5 7 7 3 2 3 2
12500 18 15 20 18 8 7 9 7
25000 40 36 44 41 20 17 20 17
50000 81 80 99 90 43 41 47 36
100000 176 167 269 242 103 85 101 92
200000 411 360 544 448 228 179 211 182
Обозначения:
B RB-дерево бинарного поиска (программы 12.8 и 13.6)
D DST-дерево (программа 15.1)
T trie-дерево (программы 15.2 и 15.3)
P patricia-дерево (программы 15.4 и 15.5)

Эти сравнительные значения времени построения и поиска в таблицах символов, содержащих случайные последовательности 32-разрядных целых чисел, подтверждают, что поразрядные методы могут конкурировать с методами, использующими сбалансированные деревья, даже в случае ключей, состоящих из случайных битов. Различия в производительности более заметны, если ключи являются длинными и не обязательно случайными (см. таблица 15.2), или когда возможен эффективный доступ к разрядам ключа (см. упражнение 15.22).

Обратите внимание, что затраты на поиск, приведенные в лемме 15.5, не возрастают с увеличением длины ключа. И напротив, затраты на поиск в стандартном trie-дереве, как правило, зависят от длины ключей: позиция первого разряда, которым различаются два заданных ключа, может находиться сколь угодно далеко. Все рассмотренные ранее методы поиска, основанные на сравнениях, также зависят от длины ключа: даже если два ключа различаются только самым правым разрядом, для их сравнения требуется время, пропорциональное длине ключей. А в методах хеширования всегда требуется время, пропорциональное длине ключа — на вычисление хеш-функции. Однако patricia-деревья сразу обращаются к значимым разрядам и обычно проверяют менее lgN из них. В связи с этим patricia-метод (или поиск по trie-дереву со свернутыми однонаправленными путями) является рекомендуемым методом поиска при наличии длинных ключей.

Например, предположим, что используется компьютер, обеспечивающий эффективный доступ к 8-разрядным байтам данных, и требуется выполнять поиск среди миллионов 1000-разрядных ключей. В этом случае patricia-методу для выполнения поиска будет нужен доступ лишь приблизительно к 20 байтам искомого ключа плюс одна 125-байтовая операция проверки на равенство. А при использовании хеширования потребовался бы доступ ко всем 125 байтам искомого ключа для вычисления хеш-функции (плюс несколько проверок на равенство), а методы, основанные на сравнениях, потребовали бы от 20 до 30 полных сравнений ключей. Конечно, сравнения ключей, особенно на ранних этапах поиска, требуют проверки всего нескольких байтов, но на последующих этапах, как правило, необходимо сравнение значительно большего количества байтов. В разделе 15.5 мы снова сравним производительность различных методов поиска для длинных ключей.

Patricia-ялгоритм не накладывает какие-либо ограничения на длину искомых ключей. Этот алгоритм особенно эффективен в приложениях с потенциально очень длинными ключами переменной длины, наподобие рассматриваемых в разделе 15.5. При использовании patricia-деревьев обычно можно надеяться, что количество проверок разрядов, необходимых для поиска среди N записей, даже с очень длинными ключами, будет приблизительно пропорционально lgN.

Упражнения

15.33. Что происходит при использовании программы 15.5 для вставки записи, ключ которой равен какому-либо ключу, уже присутствующему в patricia-дереве?

15.34. Нарисуйте patricia-дерево, образованное вставками ключей E A S Y Q U T I O N в указанном порядке в первоначально пустое дерево.

15.35. Нарисуйте patricia-дерево, образованное вставками ключей 01010011 00000111 00100001 01010001 11101100 00100001 10010101 01001010 в указанном порядке в первоначально пустое дерево.

15.36. Нарисуйте patricia-дерево, образованное вставками ключей 01001010 10010101 00100001 11101100 01010001 00100001 00000111 01010011 в указанном порядке в первоначально пустое дерево.

15.37. Экспериментально сравните высоту и длину внутреннего пути patricia-дерева, построенного вставками N случайных 32-разрядных ключей в первоначально пустое дерево, с этими же характеристиками стандартного BST-дерева и RB-дерева ( "Сбалансированные деревья" ), построенных из этих же ключей, для N = 103, 104, 105 и 106 (см. упражнения 15.6 и 15.14).

15.38. Приведите полную характеристику длины внутреннего пути для худшего случая patricia-дерева, содержащего N различных w-разрядных ключей.

15.39. Реализуйте операцию выбрать для таблицы символов на основе patricia-дерева.

15.40. Реализуйте операцию удалить для таблицы символов на основе patricia-дерева.

15.41. Реализуйте операцию объединить для таблицы символов на основе patricia-дерева. о 15.42. Напишите программу, которая выводит все ключи patricia-дерева, имеющие те же начальные t разрядов, что и заданный ключ.

15.43. Измените стандартные поиск и вставку в trie-дереве (программы 15.2 и 15.3), чтобы исключить однонаправленные пути, как в patricia-деревьях. Если вы выполнили упражнение 15.20, начните с полученной в нем программы.

15.44. Измените поиск и вставку в patricia-дереве (программы 15.4 и 15.5), чтобы использовать таблицу, содержащую 2r trie-деревьев, как описано в упражнении 15.23.

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

15.46. Измените программу поиска в patricia-дереве (программа 15.4), чтобы она сравнивала ключи при спуске вниз по дереву, для повышения производительности успешного поиска. Экспериментально оцените эффективность этого изменения (см. упражнение 15.45).

15.47. Воспользуйтесь patricia-деревом для построения структуры данных, которая может поддерживать АТД таблицы существования для w-разрядных двоичных целых чисел (см. упражнение 15.32).

15.48. Напишите программу, которая преобразует patricia-дерево в стандартное trie-дерево с теми же ключами, и наоборот.

Бактыгуль Асаинова
Бактыгуль Асаинова

Здравствуйте прошла курсы на тему Алгоритмы С++. Но не пришел сертификат и не доступен.Где и как можно его скаачат?

Александра Боброва
Александра Боброва

Я прошла все лекции на 100%.

Но в https://www.intuit.ru/intuituser/study/diplomas ничего нет.

Что делать? Как получить сертификат?