Академический Документы
Профессиональный Документы
Культура Документы
ХЭШ-ТАБЛИЦЫ. ХЭШ-ФУНКЦИИ........................................................................................................ 6
КОСЫЕ ДЕРЕВЬЯ (SPLAY TREE). ОСНОВНЫЕ ОПЕРАЦИИ НАД КОСЫМИ ДЕРЕВЬЯМИ. ОСНОВНЫЕ
ТЕОРЕМЫ О КОСЫХ ДЕРЕВЬЯХ.........................................................................................................17
АМОРТИЗАЦИОННЫЙ АНАЛИЗ........................................................................................................ 29
ДИНАМИЧЕСКОЕ ПРОГРАММИРОВАНИЕ.........................................................................................31
ЖАДНЫЕ АЛГОРИТМЫ..................................................................................................................... 32
1
РАССТОЯНИЕ ЛЕВЕНШТЕЙНА. ЕГО СВОЙСТВА И АЛГОРИТМ ВЫЧИСЛЕНИЯ....................................33
2
Алгоритмическая модель – это разновидность информационной модели, где
содержится описание последовательности действий (план), строгое исполнение
которых приводит к решению поставленной задачи за конечное число шагов.
Примером алгоритмической модели может являться RAM.
Машина с произвольным доступом к памяти (Random Access Machine, RAM,
Равновероятная адресная машина) может рассматриваться как компьютер,
работающий следующим образом:
• алгоритм состоит из конечного числа команд;
• для исполнения простой операции {+, ∗, −, =, if, . . . } требуется один
шаг;
• каждое обращение к памяти занимает один шаг;
• объем памяти неограничен;
• имеются циклы, условия, безусловные переходы;
• один шаг выполняется за одну единицу времени;
• любой алгоритм может быть представлен в виде слова над некоторым
алфавитом. Мы будем считать, что алгоритм записан на некотором
языке программирования.
Сложность в среднем:
3
1
T m ( n )=
¿ { x ∈ X :||x||=n }∨¿ ∑ τ (x)¿
x ∈ X :||x||=n
Асимптотические обозначения
Рассмотрим две функции f, g: N → N. Обозначение f(n) = O(g(n)) означает,
что существуют такие c ∈ R и n0 ∈ N, что при любом n > n0 выполняется f(n) ≤ c ·
g(n).
Запись вида f(n) = O(g(n)) означает, что ф-ия f(n) возрастает медленнее
чем ф-ия g(n) при с = с1 и n = N, где c1 и N могут быть сколь угодно большими
числами, т.е. При c = c1 и n >= N, c*g(n) >=f(n). O – верхнее ограничение
сложности алгоритма.
Обозначение f(n) = Ω(g(n)) означает, что g(n) = O(f(n)).
Обозначение f(n) = Θ(g(n)) означает, что f(n) = O(g(n)) и, при этом, g(n) =
O(f(n)).
Назовем функцию f: N → N полиномиальной, если существует такое k ∈ R,
что f(n) = O(nk).
Назовем функцию f: N → N экспоненциальной, если существует такое a ∈ R,
что f(n) = Ω(an).
Свойства асимптотических обозначений:
Сложение:
f(n) + g(n) = O(max(f(n), g(n)))
f(n) + g(n) = Ω(max(f(n), g(n)))
f(n) + g(n) = Θ(max(f(n), g(n)))
Умножение:
f(n) · g(n) = O(f(n) · g(n))
f(n) · g(n) = Ω(f(n) · g(n))
f(n) · g(n) = Θ(f(n) · g(n))
7
t = node.next;
node.next=t.next;
удалить t из памяти;
}
Хэш-таблицы. Хэш-функции.
Хэш-таблица – это структура данных, реализующая интерфейс
ассоциативного массива. Она позволяет хранить пары ключ-значение и
выполнять три операции: добавление, удаление, поиск.
Хэш-функция – это функция h: U Zm, где U – множество всех
возможных ключей, m << |U|. Хэш-функция позволяет выполнять алгоритм
преобразования массива входных данных произвольной длины в выходную
битовую строку установленной длины.
Случай, при котором хэш-функция преобразует несколько разных ключей
в одинаковые хэш-коды, называется коллизией ((x1, x2): x1≠ x2, но h(x1) = h(x2)).
Методы борьбы с коллизиями в хэш-таблицах:
• метод цепочек (метод прямого связывания);
• метод открытой адресации.
При использовании метода цепочек в хеш-таблице хранятся пары
«связный список ключей» — «хэш-код». Для каждого ключа хеш-функцией
вычисляется хэш-код; если хэш-код был получен ранее (для другого ключа),
ключ добавляется в существующий список ключей, парный хэш-коду; иначе
создаётся новая пара «список ключей» — «хэш-код», и ключ добавляется в
8
созданный список. В общем случае, если имеется N ключей и M списков,
средний размер хеш-таблицы составит N/M. В этом случае при поиске по
таблице по сравнению со случаем, в котором поиск выполняется
последовательно, средний объём работ уменьшится примерно в M раз.
Второй распространенный метод — открытая индексация. Это значит, что
пары ключ-значение хранятся непосредственно в хэш-таблице. А алгоритм
вставки проверяет ячейки в некотором порядке, пока не будет найдена пустая
ячейка. Порядок вычисляется на лету.
Алгоритмы открытой адресации:
9
Двоичные деревья поиска. Основные операции над двоичными
деревьями поиска.
func InorderTreeWalk(root){
if (root != null){
InorderTreeWalk(root.left);
print root.key;
InorderTreeWalk(root.right);
}
}
10
while(( x != null) and (key != x.key)){
if (key < x.key)
x = x.left;
else
x = x.right;
}
return x;
}
//Добавление элемента
func TreeInsert(T, z){
y = null;
x = T.root;
while( x != null)
{
y = x;
if (z.key < x.key)
x = x.left;
else
x = x.right;
}
z.p = y;
if (y == null)
T.root = z;
else if (z.key < y.key)
y.left = z;
else
y.right = z;
}
Если вершина х имеет правого ребенка, то вершина, следующая за х не имеет левого ребенка
//Удаление
11
else
x=y.right;
if (y.p == null)
T.root = x;
else if (y == y.p.left)
y.p.left = x;
else
y.p.right = x;
if(y != z)
z.key = y.key;
return y;
}
Удаление элемента:
13
Замечание: при удалении вершины из двоичного дерева поиска, мы всегда
удаляем вершину с не более чем одним нелистовым ребенком.
14
одинаковое количество чёрных узлов, но теперь у x есть чёрный брат с
красным правым потомком, и мы переходим к следующему случаю. Ни x, ни
его отец не влияют на эту трансформацию.
//Вставка
func RBInsert(T, z){
y = null;
x = T.root;
while (x != null){
y = x;
if (z.key < x.key)
x = x.left;
15
else
x = x.right;
}
z.p = y;
if (y == null)
T.root = z;
else if (z.key < y.key)
y.left = z;
else
y.right = z;
z.left = null;
z.color = red;
RBInsertFixup(T, z);
}
16
∷ Пусть Nh – минимальное число вершин в AVL-дереве с высотой h.
h 0 1 2 3 4
Nh 1 2 4 7 12
Nh = Nh-1 + Nh-2 + 1
( ) ( )
k k
1+ √ 5 1− √ 5
−
2 2
F ФИБ=
√5
Nh = Fh+3 – 1.
( ) ( )
h+3 h+3
1+ √ 5 1−√ 5
−
2 2
N h= −1
√5
( ) ( )
h+3
1 1−√ 5 1
N h= −1+ 1+
√5 2 √5
h≤
(
log n+1+
1
√5 )
+log ( √ 5 )
−3 ≈ 1 , 44 log n−3 ∎
log(1+ √ 5
2 )
Вставка узла:
а) Спуск вниз по пути поиска, пока не убедимся, что такого узла в дереве
нет.
Удаление узла:
1. Поиск элемента(узла).
2. В случае нахождения элемента запоминаем его корни правого r и левого l
поддеревьев:
17
б) Если у найденный узел p не имеет правого поддерева, то по
свойству АВЛ-дерева слева у этого узла может быть только один
единственный дочерний узел (дерево высоты 1). Необходимо удалить
узел p и вернуть в качестве результата указатель на левый дочерний узел
узла p. Иначе в правом поддереве находим узел min с наименьшим
ключом и заменяем удаляемый узел p на найденный узел min. По
свойству двоичного дерева поиска этот ключ (минимальный в правом
поддереве от удаляемого узла) находится в конце левой ветки, начиная от
корня поддерева. По свойству АВЛ-дерева у минимального элемента
справа либо есть единственный узел, либо он отсутствует. Cлева к min
подвешиваем l, справа — r. При выходе из рекурсии производим
балансировку каждого пройденного узла.
Процедура балансировки
19
3. zig-zag: если p — не корень дерева и x — левый ребенок, а p —
правый, или наоборот, то делаем поворот вокруг ребра (x,p), а затем
поворот нового ребра (x,g), где g — бывший родитель p.
Поиск:
20
Добавление вершины
Анализ SplayTree
21
Случай Zig-Zig. Два вращения. Изменяются ранги только трех вершин: x, y, z.
Учетное время: 2 + r'(x) + r'(y) + r'(z) − r(x) − r(y) − r(z) = 2 + r'(y) + r'(z) − r(x) −
r(y) ≤ 2 + r'(x) + r'(z) − 2r(x), поскольку r'(x) = r(z), r'(x) r'(y) и r(y) r(x).
Теперь покажем, что 2 + r'(x) + r'(z) − 2r(x) ≤ 3(r’(x) − r(x)). Действительно, это
эквивалентно тому, что 2r'(x) − r(x) – r'(z) 2. Легко видеть, что log x + log y при
x + y ≤ 1 имеет максимум, равный −2 при x = y = 1/2 . Следовательно, учитывая,
что s(x) + s'(z) ≤ s'(x), выполняется −(2r'(x) − r(x) – r'(z)) = log(s(x)/s'(x) ) +
log(s'(z)/s'(x)) ≤ −2.
Случай Zig-Zag. Учетное время: 2 + r’(x) + r'(y) + r'(z) − r(x) − r(y) − r(z) ≤ 2 +
r’(y) + r'(z) − 2r(x), поскольку r’(x) = r(z) и r(x) ≤ r(y).
Теперь покажем, что 2 + r'(y) + r'(z) − 2r(x) ≤ 2(r'(x) − r(x)). Это эквивалентно
тому, что 2r'(x) – r'(y) – r'(z) 2. Это доказывается аналогично предыдущему
случаю, если учесть, что s'(y) + s'(z) ≤ s'(x). Итак, учетное время в этом случае
составляет 2(r'(x) − r(x)) ≤ 3(r'(x) − r(x)). Если теперь просуммировать
полученные оценки учетного времени по всем шагам алгоритма перекоса,
получим оценку 3(r'(t) − r(x)) + 1 = 3(r(t) − r(x)) + 1, ЧТД.
22
Двоичные кучи. Очереди с приоритетами.
Двоичная куча – это, по сути, бинарное дерево поиска, хранящееся в
массиве A[1],…,A[heapsize], где корню соответствует индекс 1, левый ребенок
имеет индекс 2i, а правый – 2i+1.
∑ 2t−h O ( h )=O ¿ ¿.
h=0
23
Очередь с приоритетами (priority queue) — это множество S, элементами
которого являются пары <key, α>, где key — число, определяющее приоритет
элемента, а α — связанная с ним информация. Для простоты изложения, мы
будем считать, что элементами множества являются только ключи. При
извлечении элемента из очереди, каждый раз извлекается элемент с наибольшим
приоритетом.
Реализация основана на куче. Для добавления элемента используется
алгоритм HeapInsert. Для извлечения элемента — алгоритм HeapExtractMax. Обе
эти операции требуют времени O(log n).
func Heapify(A, i){
l = left(i);
r = right(i);
if (l < heapsize and A[l]> A[i])
largest = i;
else largest = i;
if (r < heapsize and A[r]>A[largest])
largest = r;
if (largest != i){
swap(A[i], A[largest]);
Heapify(A, largest);
}
}
func BuildHeap(A){
heapsize = length(A);
for (i = heapsize / 2; i >= 0; i--)
Heapify(A, i);
}
24
Построение дерева требует времени O(n). В цикле n раз производится
обмен корня с последним элементом и вызов Heapify, что занимает время O(log
n). Таким образом, сложность пирамидальной сортировки составляет О(n log n).
(6) Группируя в этой сумме равные члены и учитывая, что 1 + 1/2 + 1/3 +
…+ 1/k = O(log k), получаем оценку O(n log n) для математического ожидания
времени работы быстрой сортировки.
26
1. Создадим вспомогательный массив C с индексами от 0 до k. Пройдемся
по исходному массиву А и запишем в C[i] количество элементов, равных
i.
2. Массив C превратим в массив индексов (P=(1,0,3,3) P=(1,1,4,7))
3. Создадим массив B, в котором будет храниться отсортированный массив.
4. Заполним массив В.
Алгоритм имеет сложность не больше, чем O(n+k).
Данная сортировка является устойчивой, т.к. при записи одинаковых
элементов будет учитываться порядок, который был в изначальном массиве.
procedure CountingSort(A, B, k)
for i := 0 to k − 1 do C[i] := 0
for j := 0 to n − 1 do C[A[j]] := C[A[j]] + 1
for i := 1 to k − 1 do C[i] := C[i] + C[i − 1]
for j := n − 1 downto 0 do
B[C[A[j]]] := A[j]
C[A[j]] := C[A[j]] – 1
Начало.
Шаг 1. Входной массив разделяется на подмассивы фиксированной длины,
вычисляемой определённым образом.
Шаг 2. Каждый подмассив сортируется сортировкой вставками, сортировкой
пузырьком или любой другой устойчивой сортировкой.
Шаг 3. Отсортированные подмассивы объединяются в один массив с
помощью модифицированной сортировки слиянием.
Конец.
Алгоритм
Используемые понятия
Итак, на данном этапе у нас есть входной массив, его размер N и вычисленное
число minrun. Алгоритм работы этого шага:
28
упорядочен по убыванию — переставляем элементы так, чтобы они шли по
возрастанию (это простой линейный алгоритм, просто идём с обоих концов к
середине, меняя элементы местами).
3. Если размер текущего run'а меньше чем minrun — берём следующие за
найденным run-ом элементы в количестве minrun — size(run). Таким образом, на
выходе у нас получается подмассив размером minrun или больше, часть которого (а в
идеале — он весь) упорядочена.
4. Применяем к данному подмассиву сортировку вставками. Так как размер подмассива
невелик и часть его уже упорядочена — сортировка работает быстро и эффективно.
5. Ставим указатель текущего элемента на следующий за подмассивом элемент.
6. Если конец входного массива не достигнут — переход к пункту 2, иначе — конец
данного шага.
Шаг 2. Слияние.
30
Пример дерева для алгоритма сортировки трех элементов.
Количество листьев: n!
Имеет место формула Стирлинга:
n! √ 2 πn ( n/ e )n
Докажем ее усеченную версию:
Теорема: n! (n/e)n.
n n n n
n (
Док-во: ln n !=∑ ln k ≥ ∫ lnxdx=n ln n−n+1, т.к. ∫ lnxdx=x ln x−x+C . n ! ≥ e
n
n
≥ n /e )
k=1 1 1 e
Амортизационный анализ.
будет
∑ ti n ∙ O(f ( n , m ))
α = i=1 = =O ( f ( n ,m ) ) .
n n
# При выполнении операции push будем использовать две монеты —
одну для самой операции, а вторую — в качестве резерва. Тогда для
операций pop и multipop учётную стоимость можно принять равной
нулю и использовать для удаления элемента монету, оставшуюся
после операции push.
Таким образом, для каждой операции требуется O(1) монет, значит,
средняя амортизационная стоимость операций a=O(1).
Динамическое программирование.
Динамическое программирование — метод решения задачи путём её
разбиения на несколько одинаковых подзадач, рекуррентно связанных между
собой. Самым простым примером будут числа Фибоначчи — чтобы вычислить
некоторое число в этой последовательности, нам нужно сперва вычислить
третье число, сложив первые два, затем четвёртое таким же образом на основе
второго и третьего, и так далее.
Жадные алгоритмы
#Задача о рюкзаке
Vi Wi V i /W i
1 30 10 3
2 40 20 2
3 50 30 5/3
{1 ,если a[i]≠ b [ j]
tab(i, j) =
0 ,если a [ i ] =b [ j]
Dab(0,i) = i
Dab(i,0) = i
# D”САРУМАН”,”САУРОН”:
С А У Р О Н
0 1 2 3 4 5 6
С 1 0 1 2 3 4 5
А 2 1 0 1 2 3 4
Р 3 2 1 1 1 2 3
У 4 3 2 1 2 2 3
М 5 4 3 2 2 3 3
А 6 5 3 3 3 3 4
Н 7 6 4 4 4 4 3
φ( w ,x , y ,z)
w=0: (x ⋁ y ⋁ z )( x)(x ⋁ y)( y ⋁ z) w=1:( x ⋁ y ) ( y ⋁ z ) ( z ) ( z )=0( х)
x=0:( y ⋁ z )( y )( y ⋁ z) x=1:…=0 (х )
y=0: z ⋁ z=0 y=1:…
0
# Задача о рюкзаке
5 x 1+ 8 x 2 +3 x 3 ≤ 10
x 1 , x 2 , x 3 ϵ {0 ,1 }
V i (стоим W i (вес) V i /W i
) (удельн
.
стоим.
)
1 45 5 9
2 48 8 6
37
3 35 3 11 2/3
х1 = 1, х2 = ¼, x3 = 1; U = 92 – оценка сверху
39
n
K=∑ xi v i −стоимость решения оригинальной задачи
i=1
( )
n n n n n
vi n vi
∑ x i v^i=¿ ∑ x i ⌊ nε ∙ v ⌋ ≥ ∑ xi ∙
ε v max
−1 =¿
n
∑
ε v max i=1
x i v i−∑ x i
n
ε v max
K−n ¿
i=1 i =1 max i=1 i=1
n
∑ x^i v^i ≥ ε vn K −n
i=1 max
( )
n n
ε v max n ε v max
∑ x^i v i ≥ ∑ x^i v^i
n
≥
ε v max
K−n
n
= K−¿ ε v max ≥ K (1−ε) ¿
i=1 i =1
1
Коэффициент аппроксимации p(n) = 1−ε ≈ 1+ ε ⟹ этот алгоритм является
полиномиальным в системе аппроксимации.
Генетический алгоритм:
1. генерируем начальную популяцию;
2. отбор;
3. применение генетических операторов (мутация и скрещивание).
40
a b a d
c d c b
Полный перебор
Задачу о рюкзаке можно решить, полностью перебрав все возможные
решения. Допустим, имеется N предметов, которые можно укладывать в рюкзак.
Нужно определить максимальную стоимость груза, вес которого не
превышает W. Для каждого предмета существует 2 варианта: предмет либо
кладётся в рюкзак, либо нет. Тогда перебор всех возможных вариантов
имеет временную сложность O(2^N), что позволяет его использовать лишь для
небольшого количества предметов. С ростом числа предметов задача становится
неразрешимой данным методом за приемлемое время.
После составления дерева необходимо найти лист с максимальной
ценностью среди тех, вес которых не превышает W.
Метод динамического программирования
При дополнительном ограничении на веса предметов, задачу о рюкзаке
можно решить за псевдополиномиальное время методами динамического
программирования.
Пусть M(i,w) – максимальная стоимость предмета из множества с
номерами 1, 2, …, i и ограничением по w.
Для решения задачи необходимо вычислить оптимальные решения для
всех w ϵ Z: 0 ≤ w ≤ W, где W – заданная грузоподъемность.
41
M(i, W) = { M ( i−1 , W ) , если wi >W
max ( M (i−1 ) , W ) , V i + M (i−1 , W −wi )¿
¿
У нас есть узел, это наше текущее состояние, т.е. количество предметов, которое
лежит в данный момент в рюкзаке. Также у нас есть ветви, это соединительные пути к
другим узлам (состояниям), т.е. как будто мы кладём одну вещь в рюкзак.
Допустим у нас есть пустой рюкзак грузоподьёмностью (5кг) и нам надо положить:
ноутбук (2кг), спальник (3кг), молот (5кг).
В этом дереве мы можем заметить, что у нас есть узлы, которые превышают вес
рюкзака и при этом у них есть ещё и потомки, а зачем нам продолжать рассматривать
следующие варианты, если дальше будет только хуже? Поэтому мы ограничиваем
весом 5 и получаем такой граф:
42
На выходе получаем, что есть 2 варианта, либо мы кладём в рюкзак: ноутбук и
спальник, либо молот.
то есть метод ветвей и границ предполагает исключение как раз тех узлов,
которые превышают вес рюкзака? – mtrfnv 2 ноя '17 в 17:43
В данном, конкретном случае, да – Komdosh 2 ноя '17 в 17:44
а что будет ценностью решения для каждого узла в данном случае? в тому
же, в задаче о рюкзаке присутствует еще и цена, как все это
связать? – mtrfnv 2 ноя '17 в 17:47
В качестве цены тут выступает вес, а ценности решения его сумма, вы
можете задать по другому.
43
Вероятностные алгоритмы. Проверка на простоту на основе малой
теоремы Ферма.
Вероятностные алгоритмы — это алгоритмы, использующие в своей
работе случайные числа. При этом, результат работы таких алгоритмов может
быть не всегда правильным. Тем не менее обычно вероятность ошибки можно
уменьшить до необходимых значений.
Тест Ферма — вероятностный тест на простоту. Он основан на
следующей теореме.
Теорема (малая теорема Ферма): Если p — простое число, то ap = a (mod p)
Доказательство: Пусть (a − 1)p = (a − 1) (mod p). Тогда
p
a p=(1+ ( a−1 )) =∑ C ip (a−1)i=1+(a−1) p=1+ ( a−1 ) =a mod p
p
i=0
44
ложноотрицательные. Фильтр Блума использует массив из m бит: a 0,…, am−1.
Изначально, все эти биты равны нулю, что означает, что множество пусто.
Пусть определены k различных хэшфункций hi : U → Zm, где U — множество
всех возможных элементов, i ∈ {1,…,k}.
( )
k
k1
Pr ( h1 ≠ j∧ …∧ hk ≠ j )=(Pr (hi ≠ j)) = 1−
m
Теперь, если в пустой фильтр Блума вставляется n различных элементов,
вероятность того, что j-й бит равен нулю, равна:
−kn
( )
kn
1 m
Pr (¿ a j=0)= 1− ≈e ,¿
m
для достаточно большого m (это следует из второго замечательного предела).
Вероятность ложноположительного срабатывания, т.е. вероятность того, что для
некоторого элемента y, который не был вставлен, все a hi(y) = 1, для i = 1,…,k
приблизительно равна:
(1−e )
−kn k
m
Рассмотрим в ней первую строку. Вероятность того, что в ней будут две единицы,
есть количеству строк с двумя единицами деленная на общее количество строк, т.е. в
точности J(A, B).
Для вычисления оценки этой вероятности берется k различных хэш-функций.
Причем это k вычисляется по формуле:
k=
[ ]
1
e
2
,
46
Алгоритм основан на том наблюдении, что если имеется массив k-
разрядных двоичных случайных чисел и мы обнаружили, что максимальное число
нулевых старших битов среди его элементов равно t, то можно сказать, что в нем
порядка 2t элементов.
Итак, пусть x 0 , … , x N−1 – массив, число уникальных элементов которого
требуется найти. Пусть h: U {0, 1}k – хэш-функция (типичные значения k ∈ {32,
64}).
Обозначим число нулевых старших битов y как ν(y).
Пусть y i=h(x i). Для j = 0, …,m-1 вычислим следующие значения:
M j = max v ( y i),
b
y i = j(mod 2 )
j=0
( ( ) du)
inf m −1
2+ u
a m= m ∫ log
0
1+ u
47
Заметим, что если выбрать случайный набор, на нем каждый дизъюнкт будет
выполняться с вероятностью 7/8. Действительно, вероятность того, что один литерал не
выполняется равна 1/2, следовательно, дизъюнкт, в котором ровно три переменных, не
выполняется с вероятностью (1/2)3 = 1/8. Следовательно, вероятность, что дизъюнкт
выполняется равна 1 − 1/8 = 7/8. Следовательно, мат. ожидание числа выполняющихся
дизъюнктов равно (7/8)k.
Утверждение. Для любого экземпляра задачи 3-SAT существует набор, на котором
выполняется не менее, чем 7/8 всех условий. (Очевидно)
Если мы хотим найти набор, на котором выполняется не меньше, чем 7/8 всех
дизъюнктов, можно генерировать случайные наборы, пока не найдем такой. Обозначим p i
— вероятность того, что случайное присваивание выполняет ровно i дизъюнктов. Тогда
мат. ожидание:
k
∑ i pi= 78 k .
i=0
k
7
k =∑ j p j= ∑ j p j+ ∑ j pj ≤ ∑ k ' p j+ ∑ k p j=¿ ¿
8
() () () ()
j=0 7 7 7 7
j< k j> k j< k j≥ k
8 8 8 8
48
O(h)-память; O(h) – время. Для сбалансированных оценки не меняются.
3. Способ толстых узлов(можно с родителями)
49
Общий случай.
P – максимальное количество узлов, ссылающихся на узел. В каждом есть
обратные ссылки на все ссылающиеся на него узлы (Их не более, чем P).
Пусть количество версий в одном узле не превышает 2*P.
Аналогично прошлому: не полностью заполняем, добавляем состояния; полностью
заполняем, клонируем.
Ф( D)- сумма длин всех списков изменения в последней версии O(1)
1, 2, 3, -3, 4, -4, -2, 5, 6, -6, 7, -7, 8, -8; между 6 и -6 вставляем 9, -9. Сложность О(1)
O(n)
Алгоритм Бойера-Мура
Алгоритм Бойера-Мура, разработанный двумя учеными — Бойером (Robert S.
Boyer) и Муром (J. Strother Moore), считается наиболее быстрым среди алгоритмов общего
50
назначения, предназначенных для поиска подстроки в строке. Важной особенностью
алгоритма является то, что он выполняет сравнения в шаблоне справа налево в отличие от
многих других алгоритмов.
Алгоритм Бойера-Мура считается наиболее эффективным алгоритмом поиска
шаблонов в стандартных приложениях и командах, таких как Ctrl+F в браузерах и
текстовых редакторах.
Алгоритм сравнивает символы шаблона x справа налево, начиная с самого правого,
один за другим с символами исходной строки y. Если символы совпадают, производится
сравнение предпоследнего символа шаблона и так до конца. Если все символы шаблона
совпали с наложенными символами строки, значит, подстрока найдена, и поиск окончен.
В случае несовпадения какого-либо символа (или полного совпадения всего шаблона) он
использует две предварительно вычисляемых эвристических функций, чтобы сдвинуть
позицию для начала сравнения вправо.
Таким образом для сдвига позиции начала сравнения алгоритм Бойера-Мура
выбирает между двумя функциями, называемыми эвристиками хорошего суффикса и
плохого символа (иногда они называются эвристиками совпавшего суффикса и стоп-
символа). Так как функции эвристические, то выбор между ними простой — ищется такое
итоговое значение, чтобы мы не проверяли максимальное число позиций и при этом
нашли все подстроки равные шаблон
Достоинства:
Недостатки:
51
32. Рекурсия. Виды рекурсии. Оптимизация хвостовой рекурсии.
Рекурсия – вызов функции (процедуры) из неё же самой, непосредственно (простая
рекурсия) или же через другие функции (сложная или косвенная или множественная
рекурсия).Например: функция A вызывает функцию B, а функция B – функцию А. Так же
рекурсия имеет условие выхода(останова).
рекурсивной функции — вычисление ряда Фибоначчи, где для получения значения n-го
члена необходимо вычислить (n-1)-й и (n-2)-й.
Хвостовая рекурсия
Int f(int a) {
If(a<0) return 1;
Return f(a-1)
}
Не хвостовая рекурсия
Int f(int a) {
If (a<0) return 1;
Return f(a-1)*(a+5)
}
Множественная рекурсия
52
Int f(int a) {
If (a<0) return 1;
Return f(a-1)-f(a-2)
}
53