Сторожевой узел - Sentinel node

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

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

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

  • Незначительно повышенная скорость работы
  • Увеличенная структура данных надежность (возможно)

Недостатки

  • Незначительно увеличенная алгоритмическая сложность и размер кода.
  • Если к структуре данных осуществляется доступ одновременно (что означает, что все узлы, к которым осуществляется доступ, должны быть защищены как минимум на «Только для чтения» ), для реализации на основе дозорного устройства дозорный узел должен быть защищен от «чтения-записи» с помощью мьютекс. Этот дополнительный мьютекс во многих сценариях использования может вызвать серьезное снижение производительности.[1]. Один из способов избежать этого - защитить структуру списка в целом от «чтения-записи», тогда как в версии с НОЛЬ достаточно защитить структуру данных в целом «только для чтения» (если не последует операция обновления).
  • Концепция дозорного устройства бесполезна для записи структуры данных на диск.

Примеры

Поиск

Ниже представлены две версии подпрограммы (реализованной в Язык программирования C ) для поиска заданного ключа поиска в односвязный список. Первый использует контрольное значение НОЛЬ, а второй (указатель на) сторожевой узел Часовой, как индикатор конца списка. Объявления структуры данных односвязного списка и результаты обеих подпрограмм одинаковы.

структура sll_node {                          // один узел односвязного списка   int ключ;   структура sll_node *следующий;                  // индикатор конца списка или -> следующий узел} sll, *первый;

Первая версия с использованием NULL в качестве индикатора конца списка

 1 // глобальная инициализация 2 первый = НОЛЬ;                              // перед первой вставкой (не показано) 3  4 структура sll_node *Поиск(структура sll_node *первый, int search_key) { 5     структура sll_node *узел; 6     за (узел = первый;  7         узел != НОЛЬ; 8         узел = узел->следующий) 9     {10         если (узел->ключ == search_key)11             возвращаться узел;                   // найденный12     }13     // не найден14     возвращаться НОЛЬ;15 }

В за-loop содержит два теста (желтые линии) на итерацию:

  • узел! = NULL;
  • если (узел-> ключ == search_key).

Вторая версия с использованием дозорного узла

Глобально доступный указатель часовой в специально подготовленную структуру данных Часовой используется как индикатор конца списка.

 1 // глобальная переменная 2 sll_node Часовой, *часовой = &Часовой; 3 // глобальная инициализация 4 часовой->следующий = часовой; 5 первый = часовой;                          // перед первой вставкой (не показано) 6 // Обратите внимание, что указатель всегда должен находиться в конце списка. 7  8 структура sll_node *SearchWithSentinelnode(структура sll_node *первый, int search_key) { 9     структура sll_node *узел;10     часовой->ключ = search_key;11     за (узел = первый; 12         узел->ключ != search_key;13         узел = узел->следующий)14     {15     }16     если (узел != часовой)17         возвращаться узел;                       // найденный18     // не найден19     возвращаться НОЛЬ;20 }

В за-loop содержит только один тест (желтая линия) на итерацию:

  • узел-> ключ! = ключ_поиска;.

Реализация связанного списка

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

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

Ниже представлена ​​реализация кругового двусвязного списка на Python:

учебный класс Узел:    def __в этом__(себя, данные, следующий=Никто, предыдущий=Никто) -> Никто:        себя.данные = данные        себя.следующий = следующий        себя.предыдущий = предыдущий    def __repr__(себя) -> ул:        возвращаться 'Узел (данные ={})'.формат(себя.данные)учебный класс LinkedList:    def __в этом__(себя) -> Никто:        себя._sentinel = Узел(данные=Никто)        себя._sentinel.следующий = себя._sentinel        себя._sentinel.предыдущий = себя._sentinel    def pop_left(себя):        возвращаться себя.remove_by_ref(себя._sentinel.следующий)    def поп(себя):        возвращаться себя.remove_by_ref(себя._sentinel.предыдущий)    def append_nodeleft(себя, узел) -> Никто:        себя.add_node(себя._sentinel, узел)    def append_node(себя, узел -> Никто):        себя.add_node(себя._sentinel.предыдущий, узел)    def append_left(себя, данные) -> Никто:        узел = Узел(данные=данные)        себя.append_nodeleft(узел)    def добавить(себя, данные) -> Никто:        узел = Узел(данные=данные)        себя.append_node(узел)    def remove_by_ref(себя, узел):        если узел является себя._sentinel:            поднимать Исключение(«Никогда не убрать часового».)        узел.предыдущий.следующий = узел.следующий        узел.следующий.предыдущий = узел.предыдущий        узел.предыдущий = Никто        узел.следующий = Никто        возвращаться узел    def add_node(себя, курнод, новый узел) -> Никто:        новый узел.следующий = курнод.следующий        новый узел.предыдущий = курнод        курнод.следующий.предыдущий = новый узел        курнод.следующий = новый узел    def поиск(себя, ценить):        себя._sentinel.данные = ценить        узел = себя._sentinel.следующий        пока узел.данные != ценить:            узел = узел.следующий        себя._sentinel.данные = Никто        если узел является себя._sentinel:            возвращаться Никто        возвращаться узел    def __iter__(себя):        узел = себя._sentinel.следующий        пока узел является нет себя._sentinel:            урожай узел.данные            узел = узел.следующий    def ревитер(себя):        узел = себя._sentinel.предыдущий        пока узел является нет себя._sentinel:            урожай узел.данные            узел = узел.предыдущий

Обратите внимание, как add_node () метод принимает узел, который будет перемещен новым узлом в параметре курнод. Для добавления слева это заголовок непустого списка, а для добавления справа - хвост. Но из-за того, как настроена связь для обратной ссылки на дозорного, код работает и для пустых списков, где курнод будет дозорным узлом.

Смотрите также

Рекомендации

  1. ^ Игнатченко, Сергей (1998), "Реализации STL и безопасность потоков", Отчет C ++