Структуры данных (10 сентября 2024)
- Забытое с прошлой лекции
- Формулировки: logan = O(nb), nb = O(cn)
- Примеры
- 5 циклов for, n5 / 5! (константа таки важна)
- Длинные числа, числа Фибоначчи на самом деле считаются за квадрат (договорённость, какие операции сколько работают)
- Для всех чисел от 1 до n сгенерить список делителей (nlogn, 2 цикла for, оцениваем сумму гармонического ряда).
- С++
- Неинициализированные переменные, борьба с Warning-ами: -Wall, -Wextra, -Wshadow
- RangeCheckError:
int a[10], x; a[10];
// undefined behavior
- RangeCheckError fix:
vector<int> a; a.at(10); #define _GLIBCXX_DEBUG
- C++. Структуры:
struct T { int a, b; }; T x; x.a; T y = {2, 3}; T z = T {2, 3};
- C++. Ссылки, передача параметров в функции (по значения, по ссылке).
- Указатели (рассказать уже в разделе про списки!):
int b, *a = &b;
. Они же массивы: int *a = new int[n]; a[1] = 3;
- Неасимптотические оптимизации
- cmath: sqrt, sin, cos, atan, ...
- Вызов функции (если оптимизатор не уберёт его). Пример с рекурсией.
- Ввод, вывод (scanf/printf, cin/cout)
- Операции с памятью: new, delete. Работают не за O(1). Пример: vector<vector<int>>.
- Случайный, а не последовательный доступ к памяти. Пример с проходом по матрице (умножение матриц).
- Деление и взятие по модулю:
a /= b; a %= b;
. Пример с (a+b)%m.
- Быстрые операции: memcpy, strcmp
- Простейшие структуры данных (и интерфейсы)
- Немного про мышление. Важно не ломать себе мозг об мелочи, которых не понимаете: две плохие крайности вообще игнорировать, пытаться понять каждую запятую.
- Сумма на отрезке. Частичные суммы.
- Массив фиксированного размера
- int a[n];
- Интерфейс: get(i), set(i,x) − O(1)
- Минусы: удаление и добавление в середину за O(n), поиск за O(n)
- Двусвязный список
struct node { node *l, *r; int data; };
struct list { int length; node *head, *tail; }; // пустой список: head = tail = 0
- Интерфейс:
add_front, add_back, del_front, del_back, add_after, del_node
. Всё за O(1).
- Добавим ссылки внутрь:
node* link[]; link[i]
− node добавленный i-й операцией, тогда появляется del(i) за O(1)
- Поиск за O(n), обращение к i-му элементу за O(min(i, n-i))
- Пишем код поиска x:
Node *p = head; while (p && p->x != x) p = p->next;
− Перерыв −
- Односвязный список
struct node { node *next; int data; }; node *head = 0;
- Версия на массиве:
struct node { int next, data; } a[n]; int head = -1;
- Пишем код добавления в начало для обеих версий:
head = new Node {head, x};
- Расширяющийся массив (вектор)
int size, an, a[size];
- get(i), set(i, x), del_back (pop_back) − O(1)
- add_back (push_back) − O(1) в среднем, доказательство
- C++:
vector<int> a; a.push_back(x), a.pop_back
(x)
- Стек, очередь, дек
- Стек :
add_back = push, del_back = pop
за O(1), stack<int>
- Очередь :
add_front = push, del_back = pop
за O(1), queue<int>
- Дек :
add_front, add_back, del_front, del_back
за O(1), deque<int>
- Реализация на двусвязном списке
- Реализация стека, очереди, дека на динамическом массиве
- Реализация стека, очереди, дека на циклическом массиве
- Сравнение реализаций на двусвязном списке и на массиве.
- Очередь и стек с минимумом
- Стек с минимумом: частичные минимумы
- Очередь с минимумом через два стека:
Q = <S, S>
, push = O(1), pop = O(1) + reverse iff need