28
Лекция 3 Бинарный поиск Связные списки Курносов Михаил Георгиевич к.т.н. доцент Кафедры вычислительных систем Сибирский государственный университет телекоммуникаций и информатики http://www.mkurnosov.net/teaching

Лекция 3: Бинарный поиск. Связные списки

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Лекция 3: Бинарный поиск. Связные списки

Лекция 3

Бинарный поискСвязные списки

Курносов Михаил Георгиевич

к.т.н. доцент Кафедры вычислительных систем Сибирский государственный университет

телекоммуникаций и информатики

http://www.mkurnosov.net/teaching

Page 2: Лекция 3: Бинарный поиск. Связные списки

Контроль

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

Page 3: Лекция 3: Бинарный поиск. Связные списки

Задача поиска элемента по ключу

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

Page 4: Лекция 3: Бинарный поиск. Связные списки

Линейный поиск (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)

Page 5: Лекция 3: Бинарный поиск. Связные списки

Бинарный поиск (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. Если центральный больше, делаем текущей левую половину массива

Page 6: Лекция 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)

Page 7: Лекция 3: Бинарный поиск. Связные списки

Эффективность бинарного поиска

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

Page 8: Лекция 3: Бинарный поиск. Связные списки

Эффективность бинарного поиска

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

𝒏

𝟐𝒌= 𝟏, 𝒏 = 𝟐𝒌 ,

𝐥𝐨𝐠𝟐𝒏 = 𝐥𝐨𝐠𝟐 𝟐𝒌

𝒌 = 𝐥𝐨𝐠𝟐𝒏

𝑻 𝒏 = 𝟕𝐥𝐨𝐠𝟐𝒏 + 𝟑 = 𝑶(𝐥𝐨𝐠𝒏)

Page 9: Лекция 3: Бинарный поиск. Связные списки

Эффективность бинарного поиска

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

𝒏

𝟐𝒌= 𝟏, 𝒏 = 𝟐𝒌 ,

𝐥𝐨𝐠𝟐𝒏 = 𝐥𝐨𝐠𝟐 𝟐𝒌

𝒌 = 𝐥𝐨𝐠𝟐𝒏

𝑻 𝒏 = 𝟕𝐥𝐨𝐠𝟐𝒏 + 𝟑 = 𝑶(𝐥𝐨𝐠𝒏)

Бинарный поиск неэффективно использует

кеш-память процессора –

доступ к элементам массива

непоследовательный (прыжки по массиву)

Page 10: Лекция 3: Бинарный поиск. Связные списки

Задан отсортированный массив 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

Page 11: Лекция 3: Бинарный поиск. Связные списки

Задан неупорядоченный массив ключей (новые элементы добавляться крайне редко)

Требуется периодически осуществлять поиск в массиве

Решение 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

Page 12: Лекция 3: Бинарный поиск. Связные списки

Связные списки (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)

Page 13: Лекция 3: Бинарный поиск. Связные списки

Односвязный список (Singly linked list)

13

Данные Next Данные Next Данные Next NULL

Head

Размер списка заранее не известен –элементы добавляются во время работы программы (динамически)

Память под элементы выделяется динамически (функции: malloc, calloc, free)

Page 14: Лекция 3: Бинарный поиск. Связные списки

Односвязный список (Singly linked list)

14

#include <stdio.h>

#include <stdlib.h>

struct listnode {

char *data; /* Data */

int value; /* Data */

struct listnode *next; /* Next node */

};

Page 15: Лекция 3: Бинарный поиск. Связные списки

Создание элемента (выделение памяти)

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)

Page 16: Лекция 3: Бинарный поиск. Связные списки

Создание элемента (выделение памяти)

16

int main()

{

struct listnode *node;

/* Список из одного элемента */

node = list_createnode(“Ivanov Ivan”, 178);

return 0;

}

data: “Ivanov Ivan”value: 178

next: NULL

0x22340AFF

node = 0x22340AFF

Page 17: Лекция 3: Бинарный поиск. Связные списки

Добавление элемента в начало списка

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

Page 18: Лекция 3: Бинарный поиск. Связные списки

Добавление элемента в начало списка

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

Page 19: Лекция 3: Бинарный поиск. Связные списки

Добавление элемента в начало списка

19

NULL

head = 0xAF22

Data

ValueNext

Data

ValueNext

3. Делаем головой списка узел newnode

NULL

head = 0x12A2

Data

ValueNext

Data

ValueNext

Data

Value

Next0xAF22

newnode = 0x12A2

0x12A2 0xAF22

Page 20: Лекция 3: Бинарный поиск. Связные списки

Добавление элемента в начало списка

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)

Page 21: Лекция 3: Бинарный поиск. Связные списки

Добавление элемента в начало списка

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

Page 22: Лекция 3: Бинарный поиск. Связные списки

Поиск элемента в списке (Lookup)

22

head

Начиная с головы списка просматриваем все узлы и сравниваем ключи

В худшем случае требуется просмотреть все узлы, это требует O(n) операций

data: “Pluton”value: 178

next: 0x3434ff99

data: “Mars”value: 182

next: 0xdeadbeaf

data: “Saturn”value: 169

next: NULL

Page 23: Лекция 3: Бинарный поиск. Связные списки

Поиск элемента в списке (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)

Page 24: Лекция 3: Бинарный поиск. Связные списки

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

Page 25: Лекция 3: Бинарный поиск. Связные списки

Удаление элемента (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

Три возможных ситуации:

удаляемый узел – в начале списка

удаляемый узел – внутренний узел (есть элементы слава и справа)

удаляемый узел – в конце списка

Удаляемый элемент

Page 26: Лекция 3: Бинарный поиск. Связные списки

Удаление элемента (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)

Page 27: Лекция 3: Бинарный поиск. Связные списки

Удаление элемента

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;

}

Page 28: Лекция 3: Бинарный поиск. Связные списки

Домашнее чтение

28

Прочитать о реализации связных списков в “практике программирования” [Kernighan2011, С. 61-66]