Скачать презентацию Тема Алгоритмы и структуры данных продолжение Лекция 31 Скачать презентацию Тема Алгоритмы и структуры данных продолжение Лекция 31

Лекция-14-Иванченко.ppt

  • Количество слайдов: 26

Тема Алгоритмы и структуры данных (продолжение) Лекция 31. 10. 11 г. 1 Тема Алгоритмы и структуры данных (продолжение) Лекция 31. 10. 11 г. 1

Динамически расширяемые массивы Массивы — простейший способ группировки данных; вовсе не случайно большинство языков Динамически расширяемые массивы Массивы — простейший способ группировки данных; вовсе не случайно большинство языков имеют эффективные и удобные индексируемые массивы и даже представляют строки в виде массивов символов. Массивы просты в использовании, обладают фиксированным временем доступа к любому элементу, хорошо работают с двоичным поиском и быстрой сортировкой, а также почти совсем не тратят лишних ресурсов. Для наборов данных фиксированного размера, которые могут быть созданы даже во время компиляции, или же для гарантированно небольших объемов данных массивы подходят идеально. Однако хранение меняющегося набора значений в массиве может быть весьма ресурсоемким, поэтому, если количество элементов непредсказуемо и потенциально неограниченно, может оказаться удобнее использовать другую структуру данных. Лекция 31. 10. 11 г. 2

Структура данных «вектор» (Vector) Сконструируем абстракцию «вектор» , которая во многом будет похожа на Структура данных «вектор» (Vector) Сконструируем абстракцию «вектор» , которая во многом будет похожа на массив (состоит из однотипных элементов и обеспечивает прямой доступ к элементам по индексу), но допускает изменение размеров во время работы и неограниченное добавление новых элементов. Основные идеи для реализации этой абстракции: 1 -я идея – использовать структуру языка С для представления вектора, членами которой будут: • массив достаточного размера для хранения элементов вектора, • число, равное количеству элементов вектора, • число, равное текущему размеру массива. typedef struct Vector { int sz; double* elem; int space; } Vector; space sz elem[0] elem[1] elem[…] elem[sz-1] Лекция 31. 10. 11 г. … … … 3

Структура данных «вектор» (Vector) Выводы по предыдущему рисунку: а) «пустой вектор» – это: sz Структура данных «вектор» (Vector) Выводы по предыдущему рисунку: а) «пустой вектор» – это: sz = 0, space = 0, elem = NULL. б) «полный вектор» – к вектору невозможно добавить новый элемент, т. к. нет свободного пространства: sz = space. 2 -я идея – если при попытке добавления нового элемента обнаруживается состояние «полный вектор» , то массив, в котором хранится вектор, увеличивает свой размер в два раза. Такое увеличение происходит за 3 шага: • выделяется новая область памяти размером 2*space, • «старый» массив переписывается в эту область, • прежняя область освобождается и устанавливается space = 2*space. Лекция 31. 10. 11 г. 4

Структура данных «вектор» (Vector) Теперь начнем разработку функций, поддерживающих абстракцию «вектор» . Первая из Структура данных «вектор» (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 ? Лекция 31. 10. 11 г. 5

Структура данных «вектор» (Vector) Функция reserve играет вспомогательную роль – перемещает вектор на новое Структура данных «вектор» (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; isz; ++i) p[i] = v->elem[i]; // копируем. . . if(v->elem) free(v->elem); // освобождаем память. . . v->elem = p; v->space = newalloc; } Наконец, ещё одна полезная функция изменения размера вектора resize: void resize(Vector *v, int newsize) { int i; reserve(v, newsize); for(i = v->sz; ielem[i] = 0; v->sz = newsize; } Лекция 31. 10. 11 г. 6

Структура данных «вектор» (Vector) Проанализируем работу функции resize на примерах. Исходное состояние: sz = Структура данных «вектор» (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 Лекция 31. 10. 11 г. 0 0 0 14 15 16 17 7

Структура данных «вектор» (Vector) Теперь можно приступить к конструированию модульной структуры приложения, использующего абстракцию Структура данных «вектор» (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 : Лекция 31. 10. 11 г. 8

sz" src="https://present5.com/presentation/-29918034_22365952/image-9.jpg" alt="// Vector. c #include #include "Vector. h" void init(Vector *v) { v->sz" /> // Vector. c #include #include "Vector. h" void init(Vector *v) { v->sz = v->space = 0; v->elem = NULL; } void reserve(Vector *v, int newalloc) { if (newalloc <= v->space) return; double* p = (double*)calloc(newalloc, sizeof(double)); int i; for (i=0; isz; ++i) p[i] = v->elem[i]; if(v->elem) free(v->elem); v->elem = p; v->space = newalloc; } void resize(Vector *v, int newsize) { int i; reserve(v, newsize); for(i = v->sz; ielem[i] = 0; v->sz = newsize; } 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; } Лекция 31. 10. 11 г. 9

Структура данных «вектор» (Vector) Покажем, как можно использовать абстракцию Vector: #include <stdio. h> #include Структура данных «вектор» (Vector) Покажем, как можно использовать абстракцию Vector: #include #include #include "Vector. h" void Print(Vector *v) { int i; printf("sz=%d, space=%dnelem: ", v->sz, v->space); for(i = 0; i < v->sz; ++i) printf("%5. 1 f ", v->elem[i]); printf("n--------n"); } int main() { Vector z; init(&z); int i; for(i = 0; i < 20; ++i) push_back(&z, (double)i*i); Print(&z); resize(&z, 25); Print(&z); resize(&z, 5); Print(&z); push_back(&z, 123. 45); Print(&z); system("PAUSE"); return 0; } Лекция 31. 10. 11 г. 10

Расширение функциональности Vector Модифицируем функцию быстрой сортировки для структуры данных Vector и включим ее Расширение функциональности 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); } Лекция 31. 10. 11 г. 11

Пример сортировки Vector #include <stdio. h> #include <stdlib. h> #include Пример сортировки Vector #include #include #include "Vector. h" void Print(Vector *v) { int i; printf("sz=%d, space=%dnelem: ", v->sz, v->space); for(i = 0; i < v->sz; ++i) printf("%5. 1 f ", v->elem[i]); printf("n--------n"); } int main() { Vector z; init(&z); int i; for(i = 0; i < 20; ++i) push_back(&z, (double)(20 -i)*(20 -i)); Print(&z); qsortv(&z, 0, 19); Print(&z); system("PAUSE"); return 0; } Лекция 31. 10. 11 г. 12

Структура данных Vector. Выводы В рассмотренных выше примерах структура данных Vector предназначалась для хранения Структура данных 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 можно конструировать другие динамические структуры данных, поддерживающие другие стратегии добавления и исключения элементов, например: стеки, очереди, кучи и хэш-таблицы. Лекция 31. 10. 11 г. 13

Структура данных «стек» (Stack) Стек – это одна из простейших динамических структур данных со Структура данных «стек» (Stack) Стек – это одна из простейших динамических структур данных со специфическими правилами включения и удаления элементов. Стек работает по принципу «последним пришёл – первым ушёл» (last-in, first-out – сокращённо LIFO). Включение новых элементов в стек всегда производится в конце последовательности элементов (в вершине стека). Получить (выбрать) элемент из стека можно также только из вершины, прямого доступа к элементам стека по индексу нет. Для размещения стека удобно использовать массив, а еще лучше – вектор, т. к. стек – это динамическая структура. typedef struct Stack{ int top; Vector v; } Stack; top elem[0] elem[1] elem[…] elem[top-1] Лекция 31. 10. 11 г. … … … 14

Структура данных «стек» (Stack) Теперь начнем разработку функций, поддерживающих абстракцию «стек» . Первая из Структура данных «стек» (Stack) Теперь начнем разработку функций, поддерживающих абстракцию «стек» . Первая из функций – init_s должна выполнять инициализацию стека, т. е. создавать пустой стек: void init_s(Stack *st) { адрес вектора! st->top = 0; init(&st->v); } Вторая функция - push_s добавляет к стеку новый элемент: void push_s(Stack *st, double d) { push_back(&st->v, d); // добавляем ++st->top; // инкрементируем вершину } Третья функция - pop_s извлекает из стека элемент: int pop_s(Stack *st, double *d) { if(st->top == 0) return 0; // если стек пуст *d = st->v. elem[--st->top]; --st->v. sz; результат – через аргумент! return 1; } Лекция 31. 10. 11 г. 15

Структура данных «стек» (Stack) Теперь займемся конструированием модульной структуры приложения, использующего абстракцию «стек» . Структура данных «стек» (Stack) Теперь займемся конструированием модульной структуры приложения, использующего абстракцию «стек» . По аналогии с абстракцией «вектор» сначала сформируем интерфейс, в который поместим описание структуры Stack, а также описания клиентских функций для работы со стеками: init_s, push_s и pop_s. Как отмечалось ранее, интерфейс помещается в заголовочный файл, который назовем Stack. h : // Stack. h #include "Vector. h" typedef struct Stack{ int top; Vector v; } Stack; void init_s(Stack*); void push_s(Stack*, double); int pop_s(Stack*, double*); Лекция 31. 10. 11 г. 16

Структура данных «стек» (Stack) Реализацию интерфейсных функций разместим в одном файле Stack. c : Структура данных «стек» (Stack) Реализацию интерфейсных функций разместим в одном файле Stack. c : // Stack. c #include "stack. h" void init_s(Stack *st) { st->top = 0; init(&st->v); } void push_s(Stack *st, double d) { push_back(&st->v, d); // добавляем ++st->top; // инкрементируем вершину } int pop_s(Stack *st, double *d) { if(st->top == 0) return 0; // если стек пуст *d = st->v. elem[--st->top]; --st->v. sz; return 1; // операция извлечения элемента завершилась } Лекция 31. 10. 11 г. 17

Структура данных «стек» (Stack) Покажем, как можно использовать абстракцию «стек» : #include <stdio. h> Структура данных «стек» (Stack) Покажем, как можно использовать абстракцию «стек» : #include #include #include "Stack. h" int main() { определяем стек Stack s; init_s(&s); инициализируем int i; заполняем… for(i = 0; i < 20; ++i) push_s(&s, (double)i*i); double x; for(i = 0; i < 20; ++i) { извлекаем… pop_s(&s, &x); printf("s[%d] = %5. 1 fn", s. top, x); } system("PAUSE"); return 0; } Лекция 31. 10. 11 г. 18

Структура данных «очередь» (queue) Очередь – также одна из простейших динамических структур данных со Структура данных «очередь» (queue) Очередь – также одна из простейших динамических структур данных со своими правилами включения и удаления элементов. Очередь работает по принципу «первым пришёл – первым ушёл» (first-in, first-out – сокращённо FIFO). Включение новых элементов в очередь всегда производится в конце последовательности элементов (в «хвосте» - tail). Получить (выбрать) элемент из очереди можно только из «головы» (head) очереди; прямого доступа к элементам очереди по индексу также нет. Для размещения очереди в памяти компьютера можно использовать массив, но лучше – вектор, т. к. очередь – это также динамическая структура. typedef struct Queue{ int head, tail; Vector v; } Queue; head … … tail elem[head] elem[…] elem[tail-1] Лекция 31. 10. 11 г. … … … 19

Структура данных «очередь» (queue) Разработаем функции, поддерживающих абстракцию «очередь» . Первая из функций – Структура данных «очередь» (queue) Разработаем функции, поддерживающих абстракцию «очередь» . Первая из функций – init_q должна выполнять инициализацию очереди, т. е. создавать пустую очередь: void init_q(Queue *q) { q->head = q->tail = 0; init(&q->v); } Вторая функция - enqueue добавляет к очереди новый элемент: void enqueue(Queue *q, double d) { push_back(&q->v, d); // добавляем в «хвост» ++q->tail; // инкрементируем «хвост» } Третья функция - dequeue извлекает из очереди элемент: int dequeue(Queue *q, double *d) { if(q->head == q->tail) return 0; // если очередь пуста *d = q->v. elem[q->head++]; // извлекаем из «головы» return 1; } Лекция 31. 10. 11 г. 20

Структура данных «очередь» (queue) Теперь займемся конструированием модульной структуры приложения, использующего абстракцию «очередь» . Структура данных «очередь» (queue) Теперь займемся конструированием модульной структуры приложения, использующего абстракцию «очередь» . По аналогии с абстракциями «вектор» и «стек» сначала сформируем интерфейс, в который поместим описание структуры Queue, а также описания клиентских функций для работы с очередями: init_q, enqueue и dequeue. Как отмечалось ранее, интерфейс помещается в заголовочный файл, который назовем Queue. h : // Queue. h #include "Vector. h" typedef struct Queue { int head, tail; Vector v; } Queue; void init_q(Queue*); void enqueue(Queue*, double); int dequeue(Queue*, double*); Лекция 31. 10. 11 г. 21

Структура данных «очередь» (queue) Реализацию интерфейсных функций поместим в один файл Queue. c : Структура данных «очередь» (queue) Реализацию интерфейсных функций поместим в один файл Queue. c : // Queue. c #include "Queue. h" void init_q(Queue *q) { q->head = q->tail = 0; init(&q->v); } void enqueue(Queue *q, double d) { push_back(&q->v, d); // добавляем в «хвост» ++q->tail; // инкрементируем «хвост» } int dequeue(Queue *q, double *d) { if(q->head == q->tail) return 0; // если очередь пуста *d = q->v. elem[q->head++]; // извлекаем из «головы» return 1; } Лекция 31. 10. 11 г. 22

Структура данных «очередь» (queue) Покажем, как можно использовать абстракцию «очередь» : #include <stdio. h> Структура данных «очередь» (queue) Покажем, как можно использовать абстракцию «очередь» : #include #include #include "Queue. h" int main() { определяем очередь Queue q; init_q(&q); инициализируем int i; заполняем… for(i = 0; i < 20; ++i) enqueue(&q, (double)i*i); double x; for(i = 0; i < 20; ++i) { извлекаем… dequeue(&q, &x); printf("s[%d] = %5. 1 fn", q. head-1, x); } system("PAUSE"); return 0; } Лекция 31. 10. 11 г. 23

Структура данных «очередь» (queue) Сделаем важное замечание относительно абстракции «очередь» . Если при работе Структура данных «очередь» (queue) Сделаем важное замечание относительно абстракции «очередь» . Если при работе с очередью количество операций включения примерно такое же как и количество операция извлечения, то размер очереди не будет большим, однако размер массива, в котором она размещается, будет непрерывно возрастать в силу того, что очередь постоянно «ползёт» от начала массива к его концу. Следовательно, необходимо применять специальные меры для полезного использования пустых элементов массива, расположенных левее «головы» очереди (т. е. необходима своеобразная «чистка мусора» ). Одно из часто используемых решений – «закольцевать» массив, в котором размещается очередь, т. е. считать, что сразу за последним элементом массива расположен первый элемент. Однако при этом решение проблемы переполнения «кольца» несколько усложняется. Другое решение – периодически перемещать очередь к началу массива… Лекция 31. 10. 11 г. 24

Структура данных «куча» (heap) Вектор как динамически расширяемый массив Лекция 31. 10. 11 г. Структура данных «куча» (heap) Вектор как динамически расширяемый массив Лекция 31. 10. 11 г. 25

Структура данных «хэш-таблица» (hash table) Вектор как динамически расширяемый массив Лекция 31. 10. 11 Структура данных «хэш-таблица» (hash table) Вектор как динамически расширяемый массив Лекция 31. 10. 11 г. 26