Структуры данных «Плохие программисты думают о коде. Хорошие программисты думают о структурах данных и их взаимосвязях» Линус Торвальдс, создатель Linux. Структура данных (англ. data structure) — программная единица, позволяющая хранить и обрабатывать множество однотипных и/или логически связанных данных. Для добавления, поиска, изменения и удаления данных структура данных предоставляет некоторый набор функций, составляющих её интерфейс. Алгоритмы + Структуры данных = Программы Никлаус Вирт ( Niklaus Emil Wirth) — швейцарский учёный, специалист в области информатики, один из известнейших теоретиков в области разработки языков программирования, профессор компьютерных наук Швейцарской высшей технической школы Цюриха (ETHZ), лауреат премии Тьюринга 1984 года. Создатель и ведущий проектировщик языков программирования Паскаль, Модула-2, Оберон. Массивы 7 8 0 1 1 5 2 4 3 7 4 5 6 7 8 9 Свойства массива Константный доступ по индексу &a[0]+i*sizeof(type) • Непрерывный кусок памяти • Фиксированный размер(задается при создании) sizeof(type)* maxCountElements • Вставка Начало Конец Середина (произвольное место) Поиск элемента: Удаление Списки Двусвязные списки Операции Вставка Удаление Начало O(1) O(1) Конец O(1) Tail O(1) двусвязность Середина (произвольное место) Поиск элемента: Вложенные классы. class Outer{ // … friend class Inner; class Inner{ friend class Outer; //для Inner доступна приватная часть Outer // вложенный класс //для Outer доступна приватная часть Inner // … }; // … }; void Outer::Inner::MethodInner(const Outer &t){ // … memInner = t.MethodOuter(); // вызов метода объемлющего класса // … } Перебор элементов контейнера реализовать методы доступа непосредственно как методы контейнера; инкапсулировать все операции доступа в отдельный класс-итератор. Deque::iterator it; Значение итератора представляет собой позицию в контейнере. Класс-итератор должен обеспечивать следующий минимальный набор операций: получение элемента в текущей позиции итератора(*); присваивание итератора(=); проверка совпадения позиций, представляемых двумя итераторами(== и !=); перемещение итератора к следующему элементу контейнера(++). Итератор с таким набором операций в стандартной библиотеке шаблонов называется прямым. Если добавить операцию перемещения к предыдущей позиции(декремент --), то получим итератор, который в стандартной библиотеке называется двунаправленным ... begin() end() Полуоткрытый интервал обладает двумя достоинствами: не нужно специально обрабатывать пустой интервал, так как в пустом интервале значения begin() и end() равны; упрощается проверка завершения перебора элементов контейнера – цикл продолжается до тех пор, пока итератор не достигнет позиции end(). Head Node info next prev фиктивный 0 0 Tail Перегрузка операций инкремента и декремента. Префиксная форма: double d = ++x; x += 1; double d = x; Постфиксная форма: double d = x++; double d = x; x += 1; Три основные момента перегрузки инкремента и декремента: Вы должны перегружать как префиксную, так и постфиксную формы. При реализации префиксной формы сначала вы должны увеличить или уменьшить значение, а затем вернуть измененное значение. При реализации постфиксной формы вы должны сохранить начальное значение и вернуть это значение. 13 Перегрузка операций инкремента и декремента. // Префиксный инкремент тип& operator ++(){ // Увеличить операнд и вернуть результат } // Постфиксный инкремент тип operator ++(int not_used){ //Сохранить копию исходного значения операнда // Увеличить операнд //Вернуть сохраненное значение } 14 Перегрузка операций инкремента и декремента. TMoney& operator ++(){ return (*this +=100); } TMoney operator ++(int){ TMoney t =*this; *this +=1; return t; } 15 Стек (англ. stack — стопка; читается стэк) — абстрактный тип данных, представляющий собой список элементов, организованных по принципу LIFO (англ. last in — first out, «последним пришёл — первым вышел»). Впервые стек был предложен в 1946 году Аланом Тьюрингом, как средство возвращения из подпрограмм. В 1955 году немцы Клаус Самельсон и Фридрих Бауэр из Технического университета Мюнхена использовали стек для перевода языков программирования и запатентовали идею в 1957 году. Но международное признание пришло к ним лишь в 1988 году. Над стеком и его элементами можно выполнять следующие операции: 1. добавление элемента в стек (push); 2. вытягивание (удаление) элемента из стека (pop); 3. просмотр элемента в вершине стека без его вытягивания из стека(top); 4. проверка, пустой ли стек; 5. определение количества элементов в стеке; 6. отображение (просмотр) всего стека. Организация данных с помощью стека эффективна когда нужно реализовать: •обмен данными между методами приложения с помощью параметров; •синтаксический анализ разнообразных выражений. В программировании стек можно реализовывать разными способами, например: •в виде массива O(1) ограниченный размер; •в виде односвязного списка O(1); •в виде двусвязного списка O(1). В стандартной библиотеке шаблонов C++ – STL контейнер stack. Стек с поддержкой минимума(максимума). push 2 push 4 min? push 1 push 3 push 5 min? push 4 min? pop pop pop pop min? Данные Минимум Очередь — это структура данных, которая построена по принципу FIFO — First In, First Out (первым пришел, первым ушел). Очереди часто используются в программировании сетей, операционных систем и других ситуациях, в которых различные процессы должны разделять ресурсы, такие как процессорное время. Описывается следующим набором операций enqueue — добавление элемента в очередь; dequeue — удаления элемента из очереди; empty — проверка на пустоту. Реализация: 1. На базе массива O(1) ограниченный размер 2. На базе списка O(1) 3. В стандартной библиотеке шаблонов C++ – STL контейнер queue 4. Очередь на базе двух стеков. leftStack будем использовать для операции pushBack, rightStack для операции popFront. При этом, если при попытке извлечения элемента из rightStack он оказался пустым, просто перенесем все элементы из leftStack в него (при этом элементы в rightStack получатся уже в обратном порядке, что нам и нужно для извлечения элементов, а leftStack станет пустым). PushBack 1, 2, 3, 4 PopFront Обход графа в ширину https://prog-cpp.ru/wp-content/uploads/width.gif Обход графа в глубину https://prog-cpp.ru/wp-content/uploads/height.gif Дерево – это неориентированный граф, который с одой стороны является связным, а с другой стороны не содержит циклов. A F B C G E Свойства: 1. Если в дереве n вершин, то в нем ровно n-1 ребро; 2. В дереве между любыми двумя вершинами есть только один путь. D F G Корневое дерево – это дерево в котором одна из вершин помечена как корень E A D B C Способы представления деревьев 1. Список родителей 1 3 9 6 5 1 4 7 2 8 2 3 4 5 6 7 8 9 Способы представления деревьев 2. Список детей 1 3 9 2 3 4 5 6 7 8 6 7 5 1 6 5 9 1 4 7 4 2 2 8 8 9 Способы представления деревьев 3. Более общее представление D E A B C F G T M Двоичное(бинарное) дерево — иерархическая структура данных, в которой каждый узел имеет не более двух потомков (детей). Как правило, первый называется родительским узлом, а дети называются левым и правым наследниками. Алгоритмы обработки деревьев как правило являются рекурсивными, а связано это с тем, что корневое дерево определяется рекурсивно или рекуррентно. Высота дерева(глубина) – это количество уровней, на которых располагаются вершины. Height(root) root //высота дерева из одной вершины height=1 foreach children c of root: height = max(height, Height(c) + 1) feturn height PrintTree(root) print(root) foreach children c of root: PrintTree(c) Прямой обход (NLR) Прямой обход: F, B, A, D, C, E, G, I, H. 1. Проверяем, не является ли текущий узел пустым или NULL. 2. Показываем поле данных корня (или текущего узла). 3. Обходим левое поддерево рекурсивно, вызвав функцию прямого обхода. 4. Обходим правое поддерево рекурсивно, вызвав функцию прямого обхода. Центрированный (симметричный) обход (LNR) Центрированный обход: A, B, C, D, E, F, G, H, I. 1. Проверяем, не является ли текущий узел пустым или NULL. 2. Обходим левое поддерево рекурсивно, вызвав функцию центрированного обхода. 3. Показываем поле данных корня (или текущего узла). 4. Обходим правое поддерево рекурсивно, вызвав функцию центрированного обхода. Обратный обход (LRN) Обратный порядок: A, C, E, D, B, H, I, G, F. 1. Проверяем, не является ли текущий узел пустым или NULL . 2. Обходим левое поддерево рекурсивно, вызвав функцию обратного обхода. 3. Обходим правое поддерево рекурсивно, вызвав функцию обратного обхода. 4. Показываем поле данных корня (или текущего узла). Дерево поиска. 1 2 3 9 15 17 9 2 1 17 3 15 19 Бинарное дерево поиска (англ. binary search tree, BST) — структура данных для работы с упорядоченными множествами. Бинарное дерево поиска обладает следующим свойством: если x — узел бинарного дерева с ключом k, то все узлы в левом поддереве должны иметь ключи, меньшие k, а в правом поддереве большие k. 19 Основные операции: 1. Поиск элемента 3. Поиск максимума/минимума min 2. Вставка элемента max Основные операции: 4. Удаление узла