Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
МОДЕЛИРОВАНИЕ ИЕРАРХИЧЕСКИХ ОБЪЕКТОВ СРЕДСТВАМИ...doc
Скачиваний:
1
Добавлен:
01.05.2025
Размер:
9.06 Mб
Скачать

Задачи повышенной сложности

  1. Определить уровень в иерархии одного заданного элемента относительно другого заданного элемента.

  2. Определить ближайшего общего предка для двух наперёд заданных узлов.

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

  4. Написать хранимую процедуру, извлекающую всё поддерево, начиная с заданного узла.

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

  6. Написать хранимую процедуру, проверяющую структуру на отсутствие петель.

  7. Проверить все узлы иерархии на наличие нескольких предков.

Способ правого и левого коэффициентов

Метод, предложенный Джо Селко [4] ещё называемый методом вложенных множеств, основан на полном обходе дерева (рис. 43). При полном обходе дерева каждому узлу назначается пара значений – левый и правый коэффициенты. Левые коэффициенты присваиваются во время движения от предка к потомку. Правые коэффициенты назначаются при движении от потомка к предку.

Рис. 43 Назначение коэффициентов при выполнении полного обхода

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

Таблица, соответствующая иерархии нашего примера, выглядит следующим образом (Табл. 4).

Табл. 4 Модель иерархии на основе метода правого и левого коэффициентов

Значения левого коэффициента

Содержательная часть данных узла

Значения правого коэффициента

1

A

26

2

B

13

3

D

12

4

F

7

5

J

6

8

G

11

9

K

10

14

C

25

15

E

23

16

H

19

17

L

18

20

I

23

21

M

22

Организуемая таким образом структура данных может быть описана отношением, создаваемым следующим оператором SQL (рис. 44):

create table T (L int not null primary key, Node varchar, R int not null);

Рис. 44 Реализация модели иерархии методом правого и левого коэффициентов

Здесь L – это столбец левых коэффициентов, R – правых коэффициентов, а Node – содержательная информация об узле (здесь может располагаться несколько столбцов хранящих содержательную часть узла). Роль уникального идентификатора узла исполняет левый коэффициент, объявленный первичным ключом. Также в качестве уникального идентификатора может быть выбран правый коэффициент или пара – левый и правый коэффициенты взятые вместе.

Заполним созданную таблицу данными, соответствующими иерархии представленной на рис. 8 (рис. 45).

INSERT INTO T (L, Node, R) VALUES ( 1, 'A', 26);

INSERT INTO T (L, Node, R) VALUES ( 2, 'B', 13);

INSERT INTO T (L, Node, R) VALUES ( 3, 'D', 12);

INSERT INTO T (L, Node, R) VALUES ( 4, 'F', 7);

INSERT INTO T (L, Node, R) VALUES ( 5, 'J', 6);

INSERT INTO T (L, Node, R) VALUES ( 8, 'G', 11);

INSERT INTO T (L, Node, R) VALUES ( 9, 'K', 10);

INSERT INTO T (L, Node, R) VALUES (14, 'C', 25);

INSERT INTO T (L, Node, R) VALUES (15, 'E', 23);

INSERT INTO T (L, Node, R) VALUES (16, 'H', 19);

INSERT INTO T (L, Node, R) VALUES (17, 'L', 18);

INSERT INTO T (L, Node, R) VALUES (20, 'I', 23);

INSERT INTO T (L, Node, R) VALUES (21, 'M', 22);

Рис. 45 Модель иерархии, представленной на рис. 8

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

Выбираем всех потомков для узла D (рис. 46):

SELECT * FROM T WHERE

L>(SELECT L FROM T WHERE Node='D') AND

R<(SELECT R FROM T WHERE Node='D');

Рис. 46 Все потомки узла D

Получим всех предков узла D (рис. 47):

SELECT * FROM T WHERE

L<(SELECT L FROM T WHERE Node='D') AND

R>(SELECT R FROM T WHERE Node='D');

Рис. 47 Все предки узла D

Теперь воспользуемся предикатами BETWEEN, чтобы определить всех предков определенного узла. Для этого необходимо написать следующее предложение SQL (рис. 48):

SELECT 'D' AS Base, B1.Node AS Parent, (B1.R-B1.L) AS height

FROM T AS B1, T AS E1

WHERE E1.L BETWEEN B1.L AND B1.R AND

E1.R BETWEEN B1.L AND B1.R AND

E1.Node='D';

Чем больше значение height, тем дальше по иерархии узлы друг от друга.

Рис. 48 Использование BETWEEN для нахождения всех предков узла D

Уровень в иерархии заданного элемента можно узнать, получив количество его предков и добавив 1. Получим уровень в иерархии для узла D (рис. 49)

SELECT COUNT(Node)+1 AS Level FROM T WHERE

L<(SELECT L FROM T WHERE Node='D') AND

R>(SELECT R FROM T WHERE Node='D');

Рис. 49 Уровень узла D в иерархии

Уровень узла в иерархии, можно получить, используя предикат BETWEEN следующим образом (рис. 50):

SELECT COUNT(*) AS level FROM T AS B1, T AS E1 WHERE

E1.L BETWEEN B1.L AND B1.R AND

E1.R BETWEEN B1.L AND B1.R AND

E1.Node='D';

Рис. 50 Использование BETWEEN для определения уровня в иерархии

Для всех узлов в иерархии вычислим их уровни при помощи следующего запроса (рис. 51):

SELECT P2.Node, COUNT(*) AS level FROM T AS P1, T AS P2

WHERE P2.L BETWEEN P1.L AND P1.R GROUP BY P2.Node;

Рис. 51 Определяем уровни всех узлов иерархии

Количество потомков можно определить как половину разности правого и левого коэффициентов (R-L)/2 (рис. 52)

SELECT (R-L)/2 AS Child FROM T WHERE Node='D';

Рис. 52 Определяем количество потомков узла D

Для элементов, не имеющих потомков разность между значениями левого и правого коэффициентов всегда равна 1. Поэтому все листья можно найти следующим простым запросом (рис. 53):

SELECT * FROM T WHERE R-L=1;

Рис. 53 Список листьев

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

CREATE UNIQUE INDEX L1 ON T(L ASC);

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

SELECT * FROM T WHERE L=(R-1);

SQL сервер может использовать индекс по левому столбцу только в том случае, когда он не используется в выражении, поэтому запись условия (R-L)=1 не может использоваться.

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

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

SELECT COUNT(*)+1 AS Level FROM T AS B3, T AS E3 WHERE

E3.L BETWEEN B3.L AND B3.R AND E3.Node='D';

Результат выполнения этого предложения SQL в среде SQL Server Management Studio Express представлен на рис. 54.

Рис. 54 значение уровня в дереве для непосредственных потомков узла D

  1. выбрать узлы уровня, вычисленного на предыдущем шаге:

SELECT DISTINCT B1.Node FROM T AS B1, T AS E1 WHERE

(SELECT COUNT(*) FROM T AS B2, T AS E2 WHERE

E2.L BETWEEN B2.L AND B2.R AND E2.Node=B1.Node)=

(SELECT COUNT(*)+1 FROM T AS B3, T AS E3 WHERE

E3.L BETWEEN B3.L AND B3.R AND E3.Node='D');

Результат выполнения этого предложения SQL в среде SQL Server Management Studio Express представлен на рис. 55.

Рис. 55 выбираем узлы 4 уровня

  1. Окончательно, из полученного на предыдущем шаге списка узлов, выбрать те узлы, которые имеют предком заданный узел.

SELECT B1.Node FROM T AS B1, T AS E1

WHERE (SELECT COUNT(*) FROM T AS B2, T AS E2 WHERE

E2.L BETWEEN B2.L AND B2.R AND E2.Node=B1.Node)=

(SELECT COUNT(*)+1 FROM T AS B3, T AS E3 WHERE

E3.L BETWEEN B3.L AND B3.R AND E3.Node='D')

AND B1.L BETWEEN E1.L AND E1.R AND E1.Node='D';

Результат выполнения этого предложения SQL в среде SQL Server Management Studio Express представлен на рис. 56.

Рис. 56 Непосредственные потомки узла D

Аналогичным образом определяем непосредственного предка для заданного узла (в примере – для узла D):

FROM T AS B1, T AS E1 WHERE (SELECT COUNT(*) FROM T AS B2, T AS E2 WHERE

E2.L BETWEEN B2.L AND B2.R AND E2.Node=B1.Node)=

(SELECT COUNT(*)-1 FROM T AS B3, T AS E3 WHERE

E3.L BETWEEN B3.L AND B3.R AND E3.Node='D') AND

E1.L BETWEEN B1.L AND B1.R AND E1.Node='D';

Результат выполнения этого предложения SQL в среде SQL Server Management Studio Express представлен на рис. 57.

Рис. 57 Определение непосредственного предка узла D

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

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

ALTER TABLE T ADD Parent int references T(L);

или представляют собой уровень в иерархии:

ALTER TABLE T ADD [Level] int;

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

Теперь рассмотрим задачу поиска ближайшего общего предка для двух заданных узлов. Пусть это будут узлы J и K. Как видно на рис. 43, их ближайший общий предок узел D.

Список всех общих для узлов J и K предков можно получить, например, следующим SQL-предложением (рис. 58):

select L, [Node], R from T where

L<=(select MIN(L) from T where Node='J' OR Node='K') AND

R>=(select MAX(R) from T where Node='K' OR Node='K');

Рис. 58 Список всех общих предков узлов J и K

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

select MAX(L) from T where

L<=(select MIN(L) from T where Node='J' OR Node='K') AND

R>=(select MAX(R) from T where Node='K' OR Node='K');

Рис. 59 Узел, с максимальным значением левого коэффициента

Окончательно, находим требуемое значение (рис. 60):

select L, [Node], R from T where

L=(select MAX(L) from T where (

L<=(select MIN(L) from T where Node='J' OR Node='K') AND

R>=(select MAX(R) from T where Node='K' OR Node='K')));

Рис. 60 Ближайший общий предок узлов J и K

Отдельно рассмотрим задачи удаления и добавления узлов в дерево. Если требуется удалить из иерархии поддерево можно выполнить следующее предложение SQL:

DELETE FROM T WHERE L BETWEEN (SELECT L FROM T WHERE Node='D')

AND (SELECT R FROM T WHERE Node='D');

Результат выполнения этого предложения SQL в среде SQL Server Management Studio Express представлен на рис. 61.

Рис. 61 Удаление узлов

После выполнения этого запроса появляются промежутки в последовательности правого и левого коэффициентов (рис. 62). Это не мешает выполнять большинство запросов к дереву, т.к. свойство вложенности сохраняется. Можно, например, использовать предикат BETWEEN в запросах, но другие операции, зависящие от «плотности» номеров коэффициентов, не будут работать в дереве с промежутками. Например, теперь нельзя выбрать все листья, используя свойство (R-L)=1, невозможно определить число узлов в поддереве, используя значения левого и правого коэффициентов его корня. Также, «благодаря» данному запросу, утрачена информация, которая была бы очень полезна в закрытии образовавшихся промежутков – правильные и левые номера корня поддерева. Поэтому,