Уханов - лекции
.pdf
5*5 |
3*3 |
– |
25 |
2*5 |
– |
3*8 |
24 |
– |
2*13 |
1*8 |
26 |
1*21 |
1*13 |
– |
21 |
– |
– |
1*34 |
34 |
Элемент таблицы А*В означает А отрезков условной длины В.
Копирования исключаются за счет точного распределения отрезков по файлам. На примере использовали двухпутевое слияние. Если использовать многопутевое слияние, то будет работать быстрее. Поэтому рассмотрим слияние на шести файлах для 129 начальных отрезков.
фаз |
f1 |
f2 |
f3 |
f4 |
f5 |
f6 |
обработано отрезков |
а |
|
|
|
|
|
|
129 |
1 |
31*1 |
30*1 |
28*1 |
24*1 |
16*1 |
– |
|
2 |
15*1 |
14*1 |
12*1 |
8*1 |
– |
16*5 |
80 |
3 |
7*1 |
6*1 |
4*1 |
– |
8*9 |
8*5 |
72 |
4 |
3*1 |
2*1 |
– |
4*17 |
4*9 |
4*5 |
68 |
5 |
1*1 |
– |
2*33 |
2*17 |
2*9 |
2*5 |
66 |
6 |
– |
1*65 |
1*33 |
1*17 |
1*9 |
1*5 |
65 |
7 |
1*129 |
– |
– |
– |
– |
– |
129 |
Для того, чтобы заставить многопутевое слияние работать так, как показано на примере, необходимо иметь точное фибоначчиево распределение отрезков по файлам. Его можно получить, если прокручивать рассмотренную схему в обратном порядке. Рассмотрим таблицу в обратной последовательности, игнорируя выходной файл.
уровень |
f1 |
f2 |
f3 |
f4 |
f5 |
сумма |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
5 |
2 |
2 |
2 |
2 |
2 |
1 |
9 |
3 |
4 |
4 |
4 |
3 |
2 |
17 |
4 |
8 |
8 |
7 |
6 |
4 |
33 |
5 |
16 |
15 |
14 |
12 |
8 |
65 |
6 |
31 |
30 |
28 |
24 |
16 |
129 |
... |
... |
... |
... |
... |
... |
... |
n |
an |
bn |
cn |
dn |
en |
tn |
n+1 |
an + bn |
an + cn |
an + dn |
an + en |
an |
tn + 4an |
Из таблицы следует, что
en = an-1;
dn = an-1 + en-1 = an-1 + an-2;
cn = an-1 + dn-1 = an-1 + an-2 + an-3;
bn = an-1 + cn-1 = an-1 + an-2 + an-3 + an-4;
an = an-1 + bn-1 = an-1 + an-2 + an-3 + an-4 + an-5,
где a0 = 1 и an = 0, при n < 0.
Числа Фибоначчи порядка p определяются правилами:
Fn(p) = Fn-1(p) + Fn-2(p) + ... + Fn-p(p) при n ≥ p;
Fn(p) = 0 при 0 ≤ n ≤ p-2;
Fp-1(p) = 1.
Другими словами, начинаем с p-1 нулей, затем пишем 1. Каждое следующее число является суммой p предыдущих. При p = 2 получаем обычную последовательность Фибоначчи.
Выведенная формула для an показывает, что число отрезков в f1 в процессе слияния на шести файлах есть число Фибоначчи пятого порядка: an = Fn+4(5).
Числа Фибоначчи пятого порядка образуют следующую последовательность: 0 0 0 0 1 1 2 4 8 16 31 ...
Итак, будем вычислять an и tn+1. Как только будет получено tn+1 ≥ N, где N – общее число отрезков, то получим нужное нам точное распределение отрезков. Если получено tn+1 > N, то значит N не является точным числом Фибоначчи. Поэтому следует дополнить N фиктивными нулевыми отрезками.
В общем случае, для M сливаемых файлов число отрезков в файле f1 будет числом Фибоначчи порядке p = M-1: an = Fn+p-1(p). А общее число отрезков будет вычисляться по формуле: tn+1 = tn + (p-1)an.
Каскадное слияние
Каскадное слияние подобно многофазному начинается с точного распределения отрезков по лентам, хотя правила распределения другие. В качестве примера рассмотрим слияние, использующее шесть файлов.
№ |
f1 |
f2 |
f3 |
f4 |
f5 |
f6 |
обработано отрезков |
1 |
55*1 |
50*1 |
41*1 |
29*1 |
15*1 |
– |
190 |
2 |
– |
5*1 |
9*2 |
12*3 |
14*4 |
15*5 |
190 |
3 |
5*15 |
4*14 |
12*3 |
2*9 |
1*5 |
– |
190 |
4 |
– |
1*15 |
1*29 |
1*41 |
1*50 |
1*55 |
190 |
5 |
1*190 |
– |
– |
– |
– |
– |
190 |
Каждая строка представляет полный проход по всем данным. Проход 2, например, получен из 1 выполнением 5-путевого слияния с f1, ..., f5, на f6, пока не опустеет f5, затем 4-путевого
слияния с f1, ..., f4 на f5, 3-путевого – с f1, f2, f3 на f4, 2-путевого – с f1, f2 на f3 и, наконец, однопутевого (копирования) с f1 на f2. Подробно получение второго прохода можно представить в
таблице.
f1 |
f2 |
f3 |
f4 |
f5 |
f6 |
55*1 |
50*1 |
41*1 |
29*1 |
15*1 |
– |
40*1 |
35*1 |
26*1 |
14*1 |
– |
15*5 |
26*1 |
21*1 |
12*1 |
– |
14*4 |
– |
14*1 |
9*1 |
– |
12*3 |
– |
– |
5*1 |
– |
9*2 |
– |
– |
– |
– |
5*1 |
– |
– |
– |
– |
Ясно, что операции копирования излишни, и их можно было бы опустить. Фактически копирование занимает лишь небольшой процент времени. Большая часть времени тратится на пяти и четырехпутевые слияния.
Рассматривая процесс в обратном порядке и игнорируя выходной файл, можно вывести точные распределения отрезков по лентам на любом этапе. Числа в распределении носят название каскадных.
уровень |
f1 |
f2 |
f3 |
f4 |
f5 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
2 |
5 |
4 |
3 |
2 |
1 |
3 |
15 |
14 |
12 |
9 |
5 |
4 |
55 |
50 |
41 |
29 |
15 |
5 |
190 |
175 |
146 |
105 |
55 |
... |
... |
... |
... |
... |
... |
n |
an |
bn |
cn |
dn |
en |
n+1 |
an + bn + |
an + bn + |
an + bn + |
an + bn |
an |
|
+ cn + |
+ cn + dn |
+ cn |
|
|
|
+ dn + en |
|
|
|
|
Вычисляем an до тех пор, пока не получим an ≥ N. В этом случае будет получена требуемое точное распределение отрезков по файлам. Если получено an > N, то требуется дополнить N фиктивными нулевыми отрезками.
Древовидные таблицы
Явное использование структуры бинарного дерева позволяет быстро вставлять и удалять записи и производить эффективный поиск, и особенно целесообразно для организации динамических таблиц, подверженных частым вставкам и удалениям.
Простым, но не лучшим методом построения является метод, когда первая запись с ключом k1 помещается в корень, а остальные записи обрабатываются по следующему принципу: если меньше, то поместить запись в левое поддерево; больше – в правое.
Дана последовательность 5 3 7 2 4 8 6 1. Строим дерево:
5
3 7
2 4 6 8
1
В этом случае возможно построение дерева следующего вида:
Такое дерево только усложняет поиск данных в таблице.
Оценим трудоемкость поиска в случайном дереве
Пусть дано n ключей 1, 2, ..., n, следующих в порядке возрастания. Допустим, что вероятность того, что в корень попадет i ключ равна 1/n. При этом, в левое поддерево попадет i-1 ключ, а в правое – n-i ключей.
Обозначим через an среднюю длину пути до узла дерева. Тогда an(i) – средняя длина пути до узла дерева, если в корне ключ i.
a n (i) = i −n1 (ai−1 +1) +1 1n + nn−i (a n−i +1) .
Первое слагаемое: искомый ключ с вероятностью i −n1 находится в левом поддереве и длина
пути до него ai-1 + 1.
Второе слагаемое: искомый ключ с вероятностью 1/n находится в корне и длина пути до него
1.
Третье слагаемое: искомый ключ с вероятностью nn−i находится в правом поддереве и длина пути до него an-i + 1.
Далее вычислим an как сумму an(i) по всем i:
|
1 |
|
|
n |
1 |
n |
−1 |
|
|
|
1 |
|
n −i |
|
1 |
n |
|||||
an = |
|
∑an (i) = |
∑( |
i |
(ai−1 +1) +1 |
+ |
(an−i +1)) =1+ |
∑((i −1)ai−1 +(n −i)an−i ) = |
|||||||||||||
|
|
n |
|
|
|
|
n |
n |
2 |
||||||||||||
|
n |
|
i=1 |
i=1 |
n |
|
|
|
|
|
|
|
n |
i=1 |
|||||||
|
|
2 |
|
n |
|
|
|
|
2 |
|
n−1 |
|
|
|
|
|
|
||||
=1+ |
|
|
∑(i −1)ai−1 =1 |
+ |
|
∑ja j . |
|
|
|
|
|
(1) |
|||||||||
2 |
2 |
|
|
|
|
|
|||||||||||||||
|
|
n |
|
i=1 |
|
|
|
|
n |
j=1 |
|
|
|
|
|
|
|||||
Попытаемся получить рекуррентную формулу для вычисления an. Для этого в формуле (1) |
|||||||||||||||||||||
выделим отдельно слагаемое с j = n-1: |
|
|
|
|
|
|
|||||||||||||||
|
|
|
|
|
2 |
|
|
|
|
2 |
|
|
n−2 |
|
|
|
|
|
|
||
an =1+ |
|
(n −1)a n−1 + |
|
|
∑iai . |
|
|
|
|
|
|
||||||||||
|
2 |
2 |
|
|
|
|
|
|
|
|
|||||||||||
|
|
|
|
|
n |
|
|
|
n |
|
|
i=1 |
|
|
|
|
|
|
|
||
Распишем an-1 по формуле (1):
|
2 |
|
|
n−2 |
an−1 =1+ |
|
|
∑iai . |
|
(n −1) |
2 |
|||
|
|
i=1 |
||
Далее выделим сумму: |
||||
n−2 |
|
2 |
|
|
∑iai = (n -1) |
(an−1 −1). |
|||
i=1 |
2 |
|
|
|
Подставим полученное значение (2) в формулу (1): a n = n12 ((n2 −1)a n−1 +2n −1).
Получено рекуррентное соотношение для an. При больших n получаем
|
2 |
n |
1 |
|
|
|
an ≈an−1 + |
, следовательно, a n ≈ 2∑ |
|
≈ 2lnn = 2ln2log2 n ≈1,39log2 n . |
|||
n |
i |
|||||
|
i=1 |
|
||||
Получили среднюю длину случайного дерева.
(2)
(3)
Сбалансированные деревья
Дерево называется сбалансированным, если разность высот левого и правого поддеревьев в каждом узле по модулю не превышает 1.
Определение сбалансированности представляет собой компромисс между оптимальными бинарными деревьями, все внешние узлы которых расположены на двух смежных уровнях, и произвольными бинарными деревьями.
При вставке или удаление узлов из дерева возможно нарушение балансировки. Следует отметить, что требование сохранности балансировки может повлечь за собой необходимость реорганизации всего дерева. Однако, разработаны алгоритмы, позволяющие выполнять операции вставки, удаления, поиска с сохранением балансировки за логарифмическое время.
Определим высоту сбалансированного дерева как максимальную длину пути от корня до листа. Обозначим через N(l) минимальное число вершин в сбалансированном дереве высоты l. В таком дереве с минимальным числом узлов одна из ветвей, исходящих от корня, должна содержать N(l-1), а другая N(l-2) вершин. Таким образом,
N(l) = N(l-1) + N(l-2) + 1. (1)
Предположим, что:
|
N(l) > αl+1, |
(2) |
|
|
|
5 +1 |
2 |
где |
б = |
2 |
– положительный корень уравнения x -x-1 = 0, которое является |
характеристическим для разностного уравнения (1). Подставим в правую часть (1) нижние границы для N(l-1) и N(l-2), которые следуют из неравенства (2):
N(l) > αl + αl-1 + 1. |
(3) |
Теперь подставим в (2) нижнюю грань N(l), определяемую (3): |
|
αl + αl-1 + 1 > αl+1. |
(4) |
Очевидно, что если докажем справедливость (4), то этим будет доказана справедливость (2). Преобразуем (4):
1 > αl+1 - αl - αl-1 = αl-1( α2 - α - 1) = 0.
Трехчлен в скобках равен нулю, поскольку α – его корень. Получили, что 1 > 0. Таким
образом, неравенство (2) доказано. Следовательно, число вершин сбалансированного дерева
n > N(l) > αl+1. (5)
Найдем высоту сбалансированного дерева l. Для этого прологарифмируем неравенство (5). log2n > (l+1)log2α,
l < −1+ log2 n = −1+1,44log2 n . log2б
Таким образом, высота сбалансированного дерева не больше, чем 1,44log2n, без корня. Когда производится вставка или удаление – возможно нарушение балансировки. В каждом
узле храним показатель сбалансированности – разность уровней поддеревьев. Обозначим «+» тот факт, что правое поддерево выше левого на единицу; «–» если левое выше правого; «•» если высоты поддеревьев равны. Можно выделить два основных случая:
1 .
A
+ +
B
+
h α
h β |
γ |
h+1
2.
|
|
A |
|
|
+ + |
|
|
B |
|
α |
– |
|
|
|
h+1 |
|
X |
|
– |
|
|
|
|
|
|
σ |
h+1
βh γ
h+1
Остальные случаю получаются вращением этих двух вокруг вертикальных осей. Балансировку можно восстановить следующими преобразованиями:
1.
|
B |
|
• |
|
A |
|
• |
|
γ |
|
h+1 |
h α |
h β |
сохраняется обратный обход
2.
X
•
A |
B |
+ |
• |
|
|
α |
β |
h γ |
σ |
h+1 |
h+1 |
|
|
h+1 |
тоже сохраняется обратный обход В обоих случаях в дереве нужно изменить лишь несколько связей. Преобразованные деревья
имеют высоту h+2, как и до включения нового узла, следовательно, часть дерева, расположенная над А, не изменится. Для работы алгоритма необходимо хранить в узлах показатели сбалансированности, которые могут иметь значения +1, -1, 0. Для этого достаточно двух битов.
Восстановление балансировки требует логарифм числа операций, следовательно, время тратится логарифмическое.
Идея сбалансированных деревьев принадлежит: Адельсон-Вельский и Ландис. Поэтому эти деревья еще называют АВЛ-деревьями.
В-деревья
Рассмотрим большое бинарное дерево и предположим, что оно хранится на диске. Если буквально следовать рассмотренным алгоритмам работы с деревом, то потребуется около log2N обращений к диску. При N = 106 потребуется до 20 обращений к диску. Но, если разделить дерево на страницы по 7 узлов и считать, что в каждый момент доступна лишь одна страница, то потребуется примерно в три раза меньше обращений. Группировка узлов в страницы, по существу, преобразует бинарные деревья в октарные, разветвляющиеся в каждой странице на 8 путей. Если увеличить число узлов в странице, то потребуется еще меньше обращений к диску.
Изобразим на рисунке группировку узлов в странице по 3 узла.
страницы
Если узлы бинарного дерева объединить в страницы, которые читаются в один проход, тогда дерево будет иметь высоту log4N.
Можно объявить размер страницы и записать в нее столько узлов, сколько поместится. Но может возникнуть проблема мощного перемещения узлов снизу вверх.
Каждый узел В-дерева содержит не менее m/2 ключей и не более чем m, кроме корня, который может содержать от 1 до m ключей. Тогда m – это порядок В-дерева. Если m четно, то удобно. Все листья В-дерева располагаются на одном уровне.
Будем считать, что узел имеет следующую структуру:
k0 |
k1 |
k2 |
... |
|
kn |
p0 |
p1 |
p2 |
... |
pn |
pn+1 |
Здесь ki |
– ключи, pi |
– указатели |
на |
поддеревья, содержащие ключи из диапазона |
||||||
ki-1 < k < ki. |
|
|
|
|
|
|
|
|
|
|
Изобразим В-дерево порядка 4. |
|
|
|
|
||||||
|
|
|
|
|
|
100 |
200 |
|
|
|
|
|
30 |
40 |
50 |
|
|
|
|
140 |
180 |
15 |
20 |
35 |
38 |
|
46 |
47 |
60 |
70 |
80 |
|
Рассмотрим основные операции над В-деревьями. Поиск.
Начинаем поиск от корня. Либо найдем в очередном узле ключ, либо найдем пару ключей ki и ki+1, таких, что искомый ключ находится между ними: ki < k < ki+1. В этом случае нужно спуститься вниз по дереву по указателю pi+1.
Для данного алгоритма предполагаем, что ключи в узле упорядочены.
Вставка.
Вставка всегда происходит в лист.
Если в листе ключей меньше, чем m, то производим вставку. Требуемый лист находим, используя операцию поиска.
Пример: m = 4. Имеем следующее дерево:
100 200
20 |
30 |
40 |
110 |
130 |
150 |
210 |
270 |
Требуется вставить ключ 140. С помощью операции поиска находим нужный лист и производим вставку. Получим дерево:
100 200
20 |
30 |
40 |
110 |
130 |
140 |
150 |
210 |
270 |
Если теперь будем производить вставку ключа 160, то возникнет переполнение узла, поскольку этот ключ будет пятым, а порядок дерева 4. В этом случае нужно поискать соседа слева или справа, у которого число ключей меньше m. Если такой сосед найден, то рассмотрим множество ключей, состоящее из ключей переполнившегося узла, ключей соседа и разделяющего их ключа отца. Для рассматриваемого примера, это будет множество 20 30 40 100 110 130 140 150 160. В этом множестве найдем средний ключ. Его поставим на место разделяющего ключа в отце, меньшие ключи уйдут к соседу, оставшиеся составят переполнившийся узел. Т.е. получим:
110 200
20 |
30 |
40 |
100 |
130 |
140 |
150 |
160 |
210 |
270 |
Рассмотрим вставку ключа 50. В данном случае возникает переполнение узла, и нет подходящего соседа, с которым можно поделиться ключами. Тогда средний ключ переполнившегося узла поднимаем к отцу, а оставшиеся узлы поделим поровну между переполнившимся и вновь созданным узлом. Получим следующее:
40 110 200
20 |
30 |
50 |
100 |
130 |
140 |
150 |
160 |
210 |
270 |
Поднятый к отцу ключ может вызвать расщепление отца. Таким образом, процесс расщепления может подняться до корня и вызвать его расщепление. В этом случае появляется новый корень.
Следовательно, В-дерево растет от листьев к корню. Удаление.
Удаление всегда производится из листа.
Если удаляемый ключ находится в листе и если после его удаления в узле остается не менее m/2 ключей, то все в порядке.
Рассмотрим, например, удаление ключа 210. После его удаления в узле остается меньше, чем m/2 ключей. Получили дефицит ключей или антипереполнение. В этом случае поищем богатого соседа и рассмотрим множество, состоящее из ключей узла, из которого произошло удаление, ключей богатого соседа и разделяющего их ключа отца. Для рассматриваемого примера это будет множество 130 140 150 160 200 270. Средний ключ этого множества поместим в качестве разделяющего в отца, а остальные поделим поровну между узлом, из которого удаляли и соседом. Получим следующее:
40 110 150
20 |
30 |
50 |
100 |
130 |
140 |
150 |
200 |
270 |
Теперь удалим ключ 20. В данном случае нет богатого соседа. Тогда рассмотрим множество, состоящее из ключей узла, из которого произошло удаление, разделяющего ключа в отце и соседа с m/2 ключами. Все это множество поместим в узел, из которого удаляли. Сосед уничтожается, а в отце становится на один ключ меньше. Дерево примет следующий вид:
110 150
30 |
40 |
50 |
100 |
130 |
140 |
160 |
200 |
270 |
Поскольку число ключей отца уменьшается на единицу, то это может вызвать его антипереполнение. Причем процесс антипереполнения может подняться до корня и уничтожить его. Таким образом, появится новый корень.
Если удаляемый ключ находится не в листе.
Порядок следования ключей при обратном обходе сортированный. Если удаляемый ключ находится не в листе, то вместо него удалим его предшественника в обратном порядке, а затем вернем его на место ключа, который нужно было удалить. Следовательно, не нарушится сортировка. Поскольку предшественник и последователь находятся в листе, следовательно, все сводится к предыдущему случаю.
Например, удалим узел 110. Получим следующее:
100 150
30 |
40 |
50 |
130 |
140 |
160 |
200 |
270 |
Оценим время поиска в В-дереве.
Рассмотрим, к скольким узлам будет происходить обращение в наихудшем случае при поиске в В-дереве порядка m. Предположим, что имеется N ключей, а на уровне l расположено
|
|
m |
|
m |
2 |
|||
N+1 листьев. Тогда число узлов на уровнях 1, 2, 3, ... не менее 2, |
2 |
2 |
, |
2 |
|
|
, ... Следовательно, |
|
2 |
||||||||
|
|
|
|
|
|
|||
m l−1 |
|
|
|
N +1 |
||||
N +1 ≥ 2 |
|
|
или l ≤1+logm |
|
|
|
. Так как при поиске происходит обращение не более чем |
|
|
|
2 |
||||||
|
2 |
|
2 |
|
|
|||
к l узлам, то |
эффективность поиска высока. Например, высота дерева, построенного на 106 |
|||||||
записей, всего |
3. |
|
|
|
|
|
||
