Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы по дисциплине АОП.docx
Скачиваний:
66
Добавлен:
24.04.2019
Размер:
2.91 Mб
Скачать

Int ival;

float fval;

char *sval;

} u;

Очевидно, что получить значение поля объединения можно лишь того типа, какой был ранее записан в это объединение:

u.ival = 10;

x = u.fval; // err!

Лекция 13. Алгоритмы и структуры данных.

Вопрос №73. Сравнение алгоритмов последовательного и двоичного поиска.

Рассмотрим два алгоритма поиска – последовательный (линейный) поиск и двоичный поиск.

Алгоритм последовательного поиска является простейшим алгоритмом и не предъявляет никаких требований к массиву, в котором ведется поиск. Однако его быстродействие – наихудшее. Количество сравнений, которое делает этот алгоритм в наилучшем случае =1, а в наихудшем =N, т.е. в среднем необходимо сделать N/2 сравнений.

Гораздо лучшие результаты дает алгоритм двоичного поиска, который реализует стратегию двукратного уменьшения области поиска на каждой итерации. Это оказывается возможным, если массив предварительно упорядочен (отсортирован) по значению ключа.

Коротко алгоритм двоичного поиска формулируется так:

Сравниваем заданное значение со средним элементом массива.

Если это значение меньше среднего элемента, то поиск продолжаем в левой части массива; в противном случае – в правой части.

Повторяем до тех пор, пока не найдем нужный элемент или не убедимся, что его в массиве нет.

Двоичный поиск отбрасывает за каждый шаг половину данных, поэтому количество шагов пропорционально тому, сколько раз можно поделить n на 2, пока не останется один элемент. Без учета округления это число равно log2n.

Если в массиве 1000 элементов, то линейный поиск может потребовать до 1000 шагов, в то время как двоичный — только около 10; при миллионе элементов оценка максимальной трудоемкости для линейного поиска составит миллион шагов, а для двоичного — 20.

Очевидно, чем больше число элементов, тем больше преимущество двоичного поиска.

Вопрос №74. Структура данных «вектор» (динамически расширяемый массив): основные характеристики и внутреннее устройство. Интерфейс структуры данных «вектор».

Сконструируем абстракцию «вектор», которая во многом будет похожа на массив (состоит из однотипных элементов и обеспечивает прямой доступ к элементам по индексу), но допускает изменение размеров во время работы и неограниченное добавление новых элементов.

Основные идеи для реализации этой абстракции:

1-я идея) Использовать структуру языка С для представления вектора, членами которой будут:

  • массив достаточного размера для хранения элементов вектора,

  • число, равное количеству элементов вектора,

  • число, равное текущему размеру массива.

Основные идеи для реализации абстракции «вектор»

а) «пустой вектор» – это: sz = 0, space = 0, elem = NULL .

б) «полный вектор» – к вектору невозможно добавить новый элемент, т.к. нет свободного пространства: sz = space .

2-я идея) Если при попытке добавления нового элемента обнаруживается состояние «полный вектор», то массив, в котором хранится вектор, увеличивает свой размер в два раза. Такое увеличение происходит за 3 шага:

  • выделяется новая область памяти размером 2*space,

  • «старый» массив переписывается в эту область,

  • прежняя область освобождается и устанавливается space = 2*space.

Теперь начнем разработку функций, поддерживающих абстракцию «вектор».

Первая из функций - 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 играет вспомогательную роль – перемещает вектор на новое место, размер которого больше, чем существующий размер:

void reserve(Vector *v, int newalloc) {

if (newalloc <= v->space) return;

double* p = (double*)calloc(newalloc, sizeof(double));

int i;

for (i=0; i<v->sz; ++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; i<newsize; ++i) v->elem[i] = 0;

v->sz = newsize;

}

Теперь можно приступить к конструированию модульной структуры приложения, использующего абстракцию «вектор». Прежде всего, сформируем интерфейс, в который поместим описание структуры Vector, а также описания клиентских функций для работы с векторами: init, push_back и resize.

Как отмечалось ранее, интерфейс помещается в заголовочный файл, который назовем Vector.h :

// Vector.h

typedef struct Vector

{ int sz; double* elem; int space; } Vector;