
Лекция-13-Иванченко.ppt
- Количество слайдов: 35
Тема Алгоритмы и структуры данных Лекция 24. 10. 11 г. 1
Основные понятия Исследование алгоритмов и структур данных является одной из основ программирования, а также богатым полем элегантных технологий и сложных математических изысканий. И это — что-то большее, чем развлечение для теоретически подготовленных: хороший алгоритм или структура данных могут позволить решить в течение нескольких секунд проблему, которая без них решалась бы годы. В таких специальных областях, как графика, базы данных, синтаксический разбор, цифровой анализ и моделирование, возможность решения задачи целиком и полностью зависит от наличия специальных алгоритмов и структур данных. Если вы разрабатываете программы в новых для вас областях программирования, то вы должны выяснить, какие наработки уже существуют, иначе вы потратите свое время впустую в попытках плохо сделать то, что уже кем-то было сделано хорошо. Лекция 24. 10. 11 г. 2
Основные понятия Каждая программа зависит от алгоритмов и структур данных, но редко бывает нужно изобретать новые алгоритмы. Даже в сложной программе, например в компиляторе или Web-браузере, структуры данных по большей части являются массивами, списками, деревьями и хэш-таблицами. Когда программе нужна более изощренная структура, она, скорее всего, будет основываться на этих более простых структурах. Соответственно, задача программиста — знать, какие алгоритмы и структуры доступны, а также понимать, как выбрать среди них нужные. Такова, вкратце, ситуация. Есть лишь горстка основных алгоритмов, которые применяются практически в каждой программе, — это, прежде всего, поиск и сортировка, и даже эти алгоритмы зачастую включены в библиотеки. Почти все структуры данных также сделаны на основе нескольких фундаментальных структур. Поэтому материал данной главы знаком почти всем программистам. Мы написали работающие версии программ, чтобы дискуссия была более конкретной, и при желании вы можете обратиться непосредственно к исходному коду, но делайте это, только если вы разобрались в том, что вам могут предложить ваш язык программирования и его библиотеки. Лекция 24. 10. 11 г. 3
Массивы как простейшие структуры данных Массив можно рассматривать как простейшую структуру данных, объединяющую фиксированное количество однотипных элементов и допускающую прямой, или иначе - произвольный доступ к своим элементам. Элементами массива могут быть данные произвольного типа: символы, числа, строки, структуры и пр. На практике часто элементами массива бывают структуры, в которых один из членов структуры играет роль «ключа» , а остальная часть структуры содержит поля с данными. Пример – учебная карточка студента, в которой роль ключа играет номер зачетной книжки, который однозначно идентифицирует студента данного вуза. Далее при рассмотрении различных алгоритмов поиска в массиве по ключу будем использовать в качестве предметной области Интернет, точнее – язык гипертекстовой разметки HTML. А в качестве элементарной порции данных будем рассматривать пару «символ-мнемоника – числовой код символа» , например: “copy” – 0 x 00 a 9 (это знак охраны авторского права © ). Определение этой структуры: struct Nameval { char *name; int value; }; Рассмотрим небольшое введение в проблему. Лекция 24. 10. 11 г. 4
Что такое World Wide Web? World Wide Web, WWW, Всемирная паутина - это сеть информационных ресурсов. Web использует три механизма, делающих эти ресурсы доступными максимально возможной аудитории: Ø Единообразная схема именования мест размещения ресурсов в Web (напр. , URI). Ø Протоколы для доступа к именованным ресурсам Web (напр. , HTTP). Ø Гипертекст для быстрой навигации по ресурсам (напр. , HTML). Лекция 24. 10. 11 г. 5
Что такое URI ? Каждый доступный во Всемирной паутине ресурс - документ HTML, рисунок, видеоклип, программа и т д. - имеет адрес, который называется Universal Resource Identifier (URI) - Универсальный идентификатор ресурса и обычно состоит из трёх частей: Ø Имя механизма (протокола), используемого для доступа к ресурсу. Ø Имя машины - владельца ресурса (хоста). Ø Имя самого ресурса, даваемое как путь внутри хоста. Рассмотрим URI следующей страницы: http: //www 2. research. att. com/~bs/C++0 x. FAQ. html#think Здесь: http – название протокола, по которому доступен документ (страница), www 2. research. att. com – имя хоста (этот компьютер принадлежит AT&T Labs Research – ведущему мировому исследовательскому центру в области информатики и телекоммуникаций), ~bs/C++0 x. FAQ. html – путь к документу внутри хоста (один из разделов домашней страницы Бьярна Страуструпа, который посвящен последним новостям в вопросе разработки нового стандарта языка С++, который называется С++11), think – имя закладки на данной странице. Лекция 24. 10. 11 г. 6
http: //www 2. research. att. com/~bs/C++0 x. FAQ. html#think Лекция 24. 10. 11 г. 7
Что такое HTML? Чтобы представить информацию для глобального использования, нужен универсальный язык, который понимали бы все компьютеры. Во Всемирной паутине языком публикации является Hyper. Text Markup Language (HTML) - язык разметки гипертекстов. HTML предоставляет авторам Web-ресурсов средства для: Ø публикации электронных документов с заголовками, текстом, таблицами, списками, фотографиями и т. д. Ø загрузки электронной информации с помощью щелчка мыши на гипертекстовой ссылке. Ø разработки форм для выполнения транзакций с удаленными службами, для использования в поиске информации, резервировании, заказе продуктов и т. д. Ø включения электронных таблиц, видеоклипов, звуковых фрагментов и других приложений непосредственно в документы. Лекция 24. 10. 11 г. 8
Краткая история HTML Язык HTML был разработан Тимом Бернерс-Ли во время его работы в CERN (Европейский центр ядерных исследований) и распространен первым веббраузером Mosaic. В 1990 -х годах язык добился особенных успехов, был расширен и дополнен благодаря быстрому росту Интернета. В Web очень важно использование одних и тех же соглашений языка HTML авторами Web-страниц и производителями программного обеспечения. Это явилось причиной совместной работы над спецификациями языка HTML. Язык HTML разрабатывался с той точки зрения, что все типы устройств должны иметь возможность использовать информацию в Web: § персональные компьютеры с графическими дисплеями с различным разрешением и числом цветов, § сотовые телефоны, § переносные устройства, § устройства для вывода и ввода речи, § компьютеры с высокой и низкой частотой и т. д. Лекция 24. 10. 11 г. 9
Интернационализация в HTML 4 Эта версия HTML разработана с помощью экспертов в области интернационализации, так что документы можно писать на любом языке и легко передавать их по всему миру. HTML теперь предоставляет лучшую поддержку различных языков в одном документе. Это обеспечивает более эффективное индексирование документов для поисковых машин, типографию высшего качества, преобразование текста в речь, более удобные переносы и т. д. Важным шагом стало принятие стандарта ISO/IEC: 10646 (Unicode, Юникод) в качестве набора символов для документов HTML. Это наиболее содержательный стандарт в мире, в котором решены вопросы представления национальных символов, направления письма, пунктуации и других языковых вопросов. Лекция 24. 10. 11 г. 10
Мнемоники в HTML Символ-мнемоника — это конструкция SGML, которая ссылается на символ из набора символов документа. В HTML предопределено большое количество спецсимволов. Чтобы вставить определенный символ в разметку, нужно вставить определенную ссылку-мнемонику в HTML структуру. Для того, чтобы поддерживать такие мнемоники, веб-браузер должны распознавать названия мнемоник и конвертировать их в символы Юникода. Лекция 24. 10. 11 г. 11
Символы-мнемоники для символов ISO 10646 Лекция 24. 10. 11 г. 12
Последовательный поиск Рассмотрим два алгоритма поиска – последовательный (линейный) поиск и двоичный поиск. В качестве примера для обоих алгоритмов будем использовать массив структур типа Nameval (массив пар «символ-мнемоника – числовой код символа» ). . /* Последовательный поиск */ typedef struct Nameval { char *name; int value; } Nameval; int lookup 1(char *name, Nameval tab[], int ntab) { int i; for (i = 0; i < ntab; i++, tab++) if (strcmp(name, tab->name) == 0) return i; return -1; } int main() { Nameval htmlchars[] = { "alpha", 0 x 03 B 1, "beta", 0 x 03 B 2, "gamma", 0 x 03 B 3, "delta", 0 x 03 B 4, "epsilon", 0 x 03 B 5, "zeta", 0 x 03 B 6, "eta", 0 x 03 B 7, "theta", 0 x 03 B 8, "iota", 0 x 03 B 9, "kappa", 0 x 03 BA, "lambda", 0 x 03 BB, "mu", 0 x 03 BC }; int n = sizeof(htmlchars) / sizeof (*htmlchars); char *mnemo = "iota"; int i = lookup 1(mnemo, htmlchars, n); if(i >= 0) printf("i=%d: %s %xn", Лекция 24. 10. 11 г. i, mnemo, htmlchars[i]. value); . . . } 13
Анализ алгоритма последовательного поиска Алгоритм последовательного поиска является простейшим алгоритмом и не предъявляет никаких требований к массиву, в котором ведется поиск. Однако его быстродействие – наихудшее. Количество сравнений, которое делает этот алгоритм в наилучшем случае =1, а в наихудшем =N, т. е. в среднем необходимо сделать N/2 сравнений. Гораздо лучшие результаты дает алгоритм двоичного поиска, который реализует стратегию двукратного уменьшения области поиска на каждой итерации. Это оказывается возможным, если массив предварительно упорядочен (отсортирован) по значению ключа. Для сортировки массива пар «символ-мнемоника – числовой код символа» применим рекурсивный алгоритм быстрой сортировки qsort_, рассмотренный ранее на лекции № 7. Т. к. этот алгоритм применялся нами для сортировки массива типа int[], его нужно модифицировать для сортировки массива Nameval[]. Лекция 24. 10. 11 г. 14
Модифицированный алгоритм qsort_ Модификация состоит в замене типа int для массива на тип Nameval , а также использовании другого механизма сравнения двух элементов массива – вместо операции «меньше» использована библиотечная функция сравнения строк strcmp. void swap(Nameval v[], int i, int j) { Nameval temp; temp = v[i]; v[i] = v[j]; v[j] = temp; } void qsort_(Nameval v[], int left, int right) { int i, last; if (left >= right) return; swap(v, left, (left + right)/2); last = left; for (i = left + 1; i <= right; i++) if (strcmp(v[i]. name, v[left]. name) < 0) swap(v, ++last, i); swap(v, left, last); qsort_(v, left, last-1); qsort_(v, last+1, right); } Лекция 24. 10. 11 г. 15
Двоичный поиск Коротко алгоритм двоичного поиска формулируется так: Сравниваем заданное значение со средним элементом массива. Если это значение меньше среднего элемента, то поиск продолжаем в левой части массива; в противном случае – в правой части. Повторяем до тех пор, пока не найдем нужный элемент или не убедимся, что его в массиве нет. int lookup 2(char *name, Nameval tab[], int ntab) { int low = 0, hight = ntab - 1, mid, cmp; while (low <= hight) { mid = (low + hight) / 2; if ((cmp = strcmp(name, tab[mid]. name)) == 0) return mid; (cmp < 0) ? (hight = mid - 1) : (low = mid + 1); } return -1; } Лекция 24. 10. 11 г. 16
Двоичный поиск Следующая программа предварительно сортирует массив и вызывает функцию lookup 2. int main() { Nameval htmlchars[] = { "alpha", 0 x 03 B 1, "beta", 0 x 03 B 2, "gamma", 0 x 03 B 3, "delta", 0 x 03 B 4, "epsilon", 0 x 03 B 5, "zeta", 0 x 03 B 6, "eta", 0 x 03 B 7, "theta", 0 x 03 B 8, "iota", 0 x 03 B 9, "kappa", 0 x 03 BA, "lambda", 0 x 03 BB, "mu", 0 x 03 BC }; int i, n = sizeof(htmlchars) / sizeof (*htmlchars); char *mnemo = "iota"; qsort_(htmlchars, 0, n-1); for(i = 0; i < n; i++) printf("%s ", htmlchars[i]. name); printf("n"); i = lookup 2(mnemo, htmlchars, n); if(i >= 0) printf("i=%d: %s %xn", i, mnemo, htmlchars[i]. value); system("PAUSE"); return 0; } Лекция 24. 10. 11 г. 17
Анализ алгоритма двоичного поиска Двоичный поиск отбрасывает за каждый шаг половину данных, поэтому количество шагов пропорционально тому, сколько раз можно поделить n на 2, пока не останется один элемент. Без учета округления это число равно log 2 n. Если в массиве 1000 элементов, то линейный поиск может потребовать до 1000 шагов, в то время как двоичный — только около 10; при миллионе элементов оценка максимальной трудоемкости для линейного поиска составит миллион шагов, а для двоичного — 20. Очевидно, чем больше число элементов, тем больше преимущество двоичного поиска. Лекция 24. 10. 11 г. 18
Динамически расширяемые массивы Массивы — простейший способ группировки данных; вовсе не случайно большинство языков имеют эффективные и удобные индексируемые массивы и даже представляют строки в виде массивов символов. Массивы просты в использовании, обладают фиксированным временем доступа к любому элементу, хорошо работают с двоичным поиском и быстрой сортировкой, а также почти совсем не тратят лишних ресурсов. Для наборов данных фиксированного размера, которые могут быть созданы даже во время компиляции, или же для гарантированно небольших объемов данных массивы подходят идеально. Однако хранение меняющегося набора значений в массиве может быть весьма ресурсоемким, поэтому, если количество элементов непредсказуемо и потенциально неограниченно, может оказаться удобнее использовать другую структуру данных. Лекция 24. 10. 11 г. 19
Структура данных «вектор» (Vector) Сконструируем абстракцию «вектор» , которая во многом будет похожа на массив (состоит из однотипных элементов и обеспечивает прямой доступ к элементам по индексу), но допускает изменение размеров во время работы и неограниченное добавление новых элементов. Основные идеи для реализации этой абстракции: 1 -я идея) Использовать структуру языка С для представления вектора, членами которой будут: • массив достаточного размера для хранения элементов вектора, • число, равное количеству элементов вектора, • число, равное текущему размеру массива. typedef struct Vector { int sz; double* elem; int space; } Vector; space sz elem[0] elem[1] elem[…] elem[sz-1] Лекция 24. 10. 11 г. … … … 20
Структура данных «вектор» (Vector) Основные идеи для реализации абстракции «вектор» (продолжение): Выводы по предыдущему рисунку: а) «пустой вектор» – это: sz = 0, space = 0, elem = NULL. б) «полный вектор» – к вектору невозможно добавить новый элемент, т. к. нет свободного пространства: sz = space. 2 -я идея) Если при попытке добавления нового элемента обнаруживается состояние «полный вектор» , то массив, в котором хранится вектор, увеличивает свой размер в два раза. Такое увеличение происходит за 3 шага: • выделяется новая область памяти размером 2*space, • «старый» массив переписывается в эту область, • прежняя область освобождается и устанавливается space = 2*space. Лекция 24. 10. 11 г. 21
Структура данных «вектор» (Vector) Теперь начнем разработку функций, поддерживающих абстракцию «вектор» . Первая из функций - init должна выполнять инициализацию вектора, т. е. создвать пустой вектор: void init(Vector *v) { v->sz = v->space = 0; v->elem = NULL; } Вторая функция - push_back добавляет к вектору новый элемент: void push_back(Vector *v, double d) { if (v->sz == 0) // память ещё не выделялась reserve(v, 8); else if (v->sz == v->space) // нет свободного пространства reserve(v, 2*v->space); v->elem[v->sz] = d; // добавляем новый элемент в конец ++v->sz; // инкрементируем счетчик } Что делает функция reserve ? Лекция 24. 10. 11 г. 22
Структура данных «вектор» (Vector) Функция reserve играет вспомогательную роль – перемещает вектор на новое место, размер которого больше, чем существующий размер: void reserve(Vector *v, int newalloc) { if (newalloc <= v->space) return; double* p = (double*)calloc(newalloc, sizeof(double)); int i; for (i=0; i
Структура данных «вектор» (Vector) Проанализируем работу функции resize на примерах. Исходное состояние: sz = 10, space = 16. 12 9 0 1 7 23 35 4 8 11 45 6 2 6 3 4 5 7 8 9 10 11 12 13 14 15 После вызова resize(v, 14) : sz = 14, space = 16. 12 9 0 1 7 23 35 4 8 11 45 6 0 0 2 6 9 10 11 12 13 14 15 0 3 4 5 7 8 0 0 После вызова resize(v, 7) : sz = 7, space = 16. 12 9 0 1 7 23 35 4 8 2 6 3 4 5 7 8 После вызова resize(v, 18) : sz = 18, space = 18. 12 9 0 1 7 23 35 4 8 11 45 6 0 0 2 6 10 11 12 13 3 4 5 7 8 9 Лекция 24. 10. 11 г. 0 0 0 14 15 16 17 24
Структура данных «вектор» (Vector) Теперь можно приступить к конструированию модульной структуры приложения, использующего абстракцию «вектор» . Прежде всего, сформируем интерфейс, в который поместим описание структуры Vector, а также описания клиентских функций для работы с векторами: init, push_back и resize. Как отмечалось ранее, интерфейс помещается в заголовочный файл, который назовем Vector. h : // Vector. h typedef struct Vector { int sz; double* elem; int space; } Vector; void init(Vector*); void resize(Vector*, int); void push_back(Vector*, double); Реализация всех функций, как интерфейсных, так и вспомогательных, размещается в одном файле Vector. c : Лекция 24. 10. 11 г. 25
sz" src="https://present5.com/presentation/-29918034_22365896/image-26.jpg" alt="// Vector. c #include
Структура данных «вектор» (Vector) Покажем, как можно использовать абстракцию Vector: #include
Расширение функциональности Vector Модифицируем функцию быстрой сортировки для структуры данных Vector и включим ее в интерфейс: // эту строку помещаем в Vector. h void qsortv(Vector*, int); // определения функций помещаем в Vector. c void swapv(Vector *v, int i, int j) { double x; x = v->elem[i]; v->elem[i] = v->elem[j]; v->elem[j] = x; } void qsortv(Vector *v, int left, int right) { int i, last; if (left >= right) return; swapv(v, left, (left + right)/2); last = left; for (i = left + 1; i <= right; i++) if (v->elem[i] < v->elem[left]) swapv(v, ++last, i); swapv(v, left, last); qsortv(v, left, last-1); qsortv(v, last+1, right); } Лекция 24. 10. 11 г. 28
Пример сортировки Vector #include
Структура данных Vector. Выводы В рассмотренных выше примерах структура данных Vector предназначалась для хранения данных типа double. Очевидно, что не составит особого труда приспособить ее для хранения данных других типов, например, структур: typedef struct Nameval { char *name; int value; } Nameval; typedef struct Vector. NV { int sz; Nameval* elem; int space; } Vector. NV; void init(Vector. NV*); void resize(Vector. NV*, int); void push_back(Vector. NV*, Nameval); void qsortv(Vector. NV*, int); Второй вывод: на базе абстракции Vector можно конструировать другие динамические структуры данных, поддерживающие другие стратегии добавления и исключения элементов, например: стеки, очереди, деки, кучи и хэш-таблицы. Лекция 24. 10. 11 г. 30
Структура данных «стек» (Stack) Вектор как динамически расширяемый массив Лекция 24. 10. 11 г. 31
Структура данных «очередь» (queue) Вектор как динамически расширяемый массив Лекция 24. 10. 11 г. 32
Структура данных «дек» (deq - double ended queue) Вектор как динамически расширяемый массив Лекция 24. 10. 11 г. 33
Структура данных «куча» (heap) Вектор как динамически расширяемый массив Лекция 24. 10. 11 г. 34
Структура данных «хэш-таблица» (hash table) Вектор как динамически расширяемый массив Лекция 24. 10. 11 г. 35