Upload
mikhail-kurnosov
View
477
Download
1
Embed Size (px)
DESCRIPTION
Citation preview
Лекция 3
Бинарный поискСвязные списки
Курносов Михаил Георгиевич
к.т.н. доцент Кафедры вычислительных систем Сибирский государственный университет
телекоммуникаций и информатики
http://www.mkurnosov.net/teaching
Контроль
2
Свойства O, Θ, Ω
1. O(3n3 + 1000n2) = …. ?
2. Θ(n2 + logn) = ….
3. O(nlogn + n2 ) = …. ?
4. O(2n + n6 – 1000n3) = …. ?
5. Ω(n6 + n!) = …. ?
Алгоритмы сортировки
1. Алгоритмы сортировки со сложностью O(nlogn) в худшем случае?
2. Сортировка за линейное время в худшем случае?
3. Что такое сортировка “на месте” (in place)?
4. QuickSort vs. MergeSort
Задача поиска элемента по ключу
3
Имеется последовательность ключей
𝑎1, 𝑎2, … , 𝑎𝑖 , … , 𝑎𝑛
Требуется найти индекс (номер) ключа, который совпадает с заданным ключом key
Index 1 2 3 4 5 6 7 8 9 10
Key 178 150 190 177 155 181 179 167 204 175
Data
Пример
Дана последовательность из 10 ключейТребуется найти элемент с ключом key = 181
Решение – искомый элемент с индексом 6
Линейный поиск (Linear search)
4
function Search(v[1:n], n, value)
for i = 1 to n do
if v[i] = value then
return i
end if
end for
return -1
end function
Просматриваем элементы с начиная с первого и сравниваем ключи
В худшем случае искомый элемент находится в конце массива или отсутствует
Количество операций в худшем случае (worst case)
T(n) = 2n = O(n)
TLinearSearch = O(n)
Бинарный поиск (Binary Search)
5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
2 3 3 5 7 9 13 23 25 29 31 33 37 41 42 46 49 50 52 67 73 81 94
Поиск key = 42
Имеется упорядоченная последовательность ключей
𝑎1 ≤ 𝑎2 ≤ ⋯ ≤ 𝑎𝑖 ≤ ⋯ ≤ 𝑎𝑛
Требуется найти позицию элемента, ключ которого совпадает с заданным ключом key
Бинарный поиск (Binary search)
1. Если центральный элемент равен искомому, конец
2. Если центральный меньше, делаем текущей правую половину массива
3. Если центральный больше, делаем текущей левую половину массива
Бинарный поиск (Binary Search)
6
function BinarySearch(v[1:n], n, key)l = 1 // Левая граница массива (low)h = n // Правая граница массива (high)while l <= h do
mid = (l + h) / 2 // Возможно переполнение mid,// Решение: mid = l + ((h - l) / 2)
if v[mid] = key thenreturn mid
else if key > v[mid] thenl = mid + 1
else h = mid - 1
end ifend whilereturn -1
end function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
2 3 3 5 7 9 13 23 25 29 31 33 37 41 42 46 49 50 52 67 73 81 94
key = 100 (worst case)
Эффективность бинарного поиска
7
function BinarySearch(v[1:n], n, key)l = 1 // Левая граница массива (low)h = n // Правая граница массива (high)while l <= h do
mid = (l + h) / 2 // 2 операции if v[mid] = key then // 2 оп.
return midelse if key > v[mid] then // 2 оп.
l = mid + 1 // 1 оп.else
h = mid - 1end if
end whilereturn -1
end function
Количество операций в худшем случае
T(n) = 2 + kTwhile + 1 = 7k + 3
k – количество итераций цикла while Twhile – количество операций в теле цикла while
Twhile = 2 + 2 + 2 + 1 = 7
Эффективность бинарного поиска
8
function BinarySearch(v[1:n], n, key)l = 1 // Левая граница массива (low)h = n // Правая граница массива (high)while l <= h do
mid = (l + h) / 2 // 2 операции if v[mid] = key then // 2 оп.
return midelse if key > v[mid] then // 2 оп.
l = mid + 1 // 1 оп.else
h = mid - 1end if
end whilereturn -1
end function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
2 3 3 5 7 9 13 23 25 29 31 33 37 41 42 46 49 50 52 67 73 81 94
key = 100(worst case)
Количество k разбиений массива
Массив длины n Массив длины n / 2 Массив длины n / 4 ... Массив длины n / 2k = 1
𝒏
𝟐𝒌= 𝟏, 𝒏 = 𝟐𝒌 ,
𝐥𝐨𝐠𝟐𝒏 = 𝐥𝐨𝐠𝟐 𝟐𝒌
𝒌 = 𝐥𝐨𝐠𝟐𝒏
𝑻 𝒏 = 𝟕𝐥𝐨𝐠𝟐𝒏 + 𝟑 = 𝑶(𝐥𝐨𝐠𝒏)
Эффективность бинарного поиска
9
function BinarySearch(v[1:n], n, key)l = 1 // Левая граница массива (low)h = n // Правая граница массива (high)while l <= h do
mid = (l + h) / 2 // 2 операции if v[mid] = key then // 2 оп.
return midelse if key > v[mid] then // 2 оп.
l = mid + 1 // 1 оп.else
h = mid - 1end if
end whilereturn -1
end function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
2 3 3 5 7 9 13 23 25 29 31 33 37 41 42 46 49 50 52 67 73 81 94
key = 100(worst case)
Количество k разбиений массива
Массив длины n Массив длины n / 2 Массив длины n / 4 ... Массив длины n / 2k = 1
𝒏
𝟐𝒌= 𝟏, 𝒏 = 𝟐𝒌 ,
𝐥𝐨𝐠𝟐𝒏 = 𝐥𝐨𝐠𝟐 𝟐𝒌
𝒌 = 𝐥𝐨𝐠𝟐𝒏
𝑻 𝒏 = 𝟕𝐥𝐨𝐠𝟐𝒏 + 𝟑 = 𝑶(𝐥𝐨𝐠𝒏)
Бинарный поиск неэффективно использует
кеш-память процессора –
доступ к элементам массива
непоследовательный (прыжки по массиву)
Задан отсортированный массив A[n]
Алгоритм Galloping проверяет ключи с индексами1, 3, 7, 15,… , 2𝑖 − 1,…
Проверка идет то тех пор, пока не будет найден элемент
𝐴 2𝑖 − 1 > 𝑘𝑒𝑦
Далее выполняется бинарный поиск в интервале2𝑖−1 − 1,… , 2𝑖 − 1
TGalloping(n) = O(logn)
Galloping search (“Поиск от края”)
10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
2 3 3 5 7 9 13 23 25 29 31 33 37 41 42 46 49 50 52 67 73 81 94
Поиск key = 31
Galloping search = One sided binary search, exponential search, doubling search J. L. Bentley and A. C.-C. Yao. An almost optimal algorithm for unbounded searching //
Information processing letters, 5(3):82–87, 1976
Задан неупорядоченный массив ключей (новые элементы добавляться крайне редко)
Требуется периодически осуществлять поиск в массиве
Решение 1 – “в лоб” за O(n)Каждый раз при поиске использовать линейный поиск за O(n)
Решение 2 – в среднем за O(logn)
1. Один раз отсортировать массив за O(nlogn) или за O(n + k)
2. Использовать экспоненциальный поиск (Galloping) – O(logn)
Поиск в массиве
11
function Search(v[1:n], n, key)if issorted = false then
Sort(v, n) // Устойчивая сортировка, вызывается редкоissorted = true
end ifreturn GallopSearch(v, n, key) // Эксп. поиск O(logn)
end function
Связные списки (Linked lists)
12
Связный список (Linked list) – динамическая структура данных для хранения информации, в которой каждый элемент хранит указатели на один или несколько других элементов
Операция ОписаниеВычислительная
сложностьСложность по памяти
AddFront(L, x)Добавляет элемент x
в начало списка LO(1) O(1)
AddEnd(L, x)Добавляет элемент x
в конец списка LO(n) O(1)
Lookup(L, x) Отыскивает элемент x
в списке LO(n) O(1)
Size(L) Возвращает
количество элементов в списке L
O(1) или O(n) O(1)
Односвязный список (Singly linked list)
13
Данные Next Данные Next Данные Next NULL
Head
Размер списка заранее не известен –элементы добавляются во время работы программы (динамически)
Память под элементы выделяется динамически (функции: malloc, calloc, free)
Односвязный список (Singly linked list)
14
#include <stdio.h>
#include <stdlib.h>
struct listnode {
char *data; /* Data */
int value; /* Data */
struct listnode *next; /* Next node */
};
Создание элемента (выделение памяти)
15
struct listnode *list_createnode(char *data, int value)
{struct listnode *p;
p = malloc(sizeof(*p)); // Выделяем памятьif (p != NULL) {
p->data = data;p->value = value;p->next = NULL;
}return p;
}
Сложность создания элемента
TCreateNode = O(1)
Создание элемента (выделение памяти)
16
int main()
{
struct listnode *node;
/* Список из одного элемента */
node = list_createnode(“Ivanov Ivan”, 178);
return 0;
}
data: “Ivanov Ivan”value: 178
next: NULL
0x22340AFF
node = 0x22340AFF
Добавление элемента в начало списка
17
NULL
head = 0xAF22
Data
ValueNext
Data
ValueNext
NULL
head = 0xAF22
Data
ValueNext
Data
ValueNext
Data
ValueNext
NULL
1. Создаем новый узел newnode в памяти
newnode = 0x12A2
0xAF22
0x12A2 0xAF22
Добавление элемента в начало списка
18
NULL
head = 0xAF22
Data
ValueNext
Data
ValueNext
NULL
head = 0xAF22
Data
ValueNext
Data
ValueNext
Data
Value
Next0xAF22
2. Устанавливаем указатель next узла newnode на head
0xAF22
newnode = 0x12A2
0x12A2 0xAF22
Добавление элемента в начало списка
19
NULL
head = 0xAF22
Data
ValueNext
Data
ValueNext
3. Делаем головой списка узел newnode
NULL
head = 0x12A2
Data
ValueNext
Data
ValueNext
Data
Value
Next0xAF22
newnode = 0x12A2
0x12A2 0xAF22
Добавление элемента в начало списка
struct listnode *list_addfront(
struct listnode *list,
char *data, int value)
{
struct listnode *newnode;
newnode = list_createnode(data, value);
if (newnode != NULL) {
newnode->next = list;
return newnode;
}
return list;
}20
TAddFront = O(1)
Добавление элемента в начало списка
21
int main()
{
struct listnode *head;
head = list_addfront(NULL, "Ivanov Ivan", 178);
head = list_addfront(head, "Petrov Petr", 182);
return 0;
}
data: “Ivanov Ivan”value: 178
next: NULL
data: “Petrov Petr”value: 182
next: 0x2233FF
0x2233FF0x6600AAhead = 0x6600AA
Поиск элемента в списке (Lookup)
22
head
Начиная с головы списка просматриваем все узлы и сравниваем ключи
В худшем случае требуется просмотреть все узлы, это требует O(n) операций
data: “Pluton”value: 178
next: 0x3434ff99
data: “Mars”value: 182
next: 0xdeadbeaf
data: “Saturn”value: 169
next: NULL
Поиск элемента в списке (Lookup)
23
struct listnode *list_lookup(struct listnode *list, char *data, int value)
{for ( ; list != NULL; list = list->next) {
if (strcmp(list->data, data) == 0 && list->value == value)
{return list;
}}return NULL; // Не нашли
}TLookup = O(n)
int main()
{
struct listnode *node, *p;
node = list_addfront(NULL, “Mars", 178);
node = list_addfront(node, “Pluton", 182);
node = list_addfront(node, “Saturn", 169);
p = list_lookup(node, “Pluton", 182);
if (p != NULL) {
printf("Data: %s\n", p->data);
}
return 0;
}
Поиск элемента в списке (Lookup)
24
Удаление элемента (Delete)
25
head
1. Находим элемент в списке (за время O(n))
2. Корректируем указатели (за O(1))
3. Удаляем элемент из памяти (за O(1))
data: “Pluton”value: 178
next: 0x3434ff99
data: “Mars”value: 182
next: 0xdeadbeaf
data: “Saturn”value: 169
next: NULL
Три возможных ситуации:
удаляемый узел – в начале списка
удаляемый узел – внутренний узел (есть элементы слава и справа)
удаляемый узел – в конце списка
Удаляемый элемент
Удаление элемента (Delete)
struct listnode *list_delete(struct listnode *list, char *data, int value)
{struct listnode *p, *prev = NULL;
for (p = list; p != NULL; p = p->next) {if (strcmp(p->data, data) == 0 && p->value == value) {
if (prev == NULL)list = p->next; // Удаляем голову
elseprev->next = p->next; // Есть элемент слева
free(p); // Освобождаем памятьreturn list; // Указатель на новую голову
}prev = p; // Запоминаем предыдущий элемент (левый)
}return NULL; // Не нашли
}
26
TDelete = O(n)
Удаление элемента
27
int main()
{
struct listnode *node, *p;
node = list_addfront(NULL, “Mars", 178);
node = list_addfront(node, “Pluton", 182);
node = list_addfront(node, “Saturn", 169);
p = list_delete(node, “Pluton", 182);
if (p != NULL) {
node = p; // Указатель на новую голову
printf(“Item deleted\n");
}
return 0;
}
Домашнее чтение
28
Прочитать о реализации связных списков в “практике программирования” [Kernighan2011, С. 61-66]