
- •Код процедуры быстрой сортировки
- •Способ выбора опорного элемента
- •Эффективность алгоритма
- •Задача сортировки данных. Определения:
- •Примеры:
- •Задача сортировки данных. Определения:
- •Примеры:
- •Задача сортировки данных. Определения:
- •Примеры:
- •Алгоритм сортировки обменом.
- •Код процедуры сортировки по методу Шелла
- •Задача сортировки данных. Определения:
- •Примеры:
- •Идея метода
- •Рекурсивный характер алгоритма
- •Алгоритм:
- •Пирамидальная сортировка:
- •Две формы хеширования
- •Выбор хеш-функции
- •Выбор хеш-функции в методе деления
- •Выбор хеш-функции в методе умножения
1.Динамические структуры данных. Динамические массивы и линейные списки; сходство и различие между ними.
Динамические переменные, создаваемые во время выполнения, позволяют более экономно расходовать ресурс оперативной памяти. Однако, главным образом такие переменные используются для формирования на их основе структур, с изменяющимися во время выполнения программы составом и размерами – динамических структур данных. Переменные, входящие в состав динамических структур, необходимо связывать друг с другом. Каждый элемент динамической структуры должен содержать один или несколько адресов связанных с ним элементов, т.е. ссылок на эти элементы. Самый простой способ соединить отдельные элементы между собой заключается в том, чтобы снабдить каждый из них только одной ссылкой на другой элемент. В результате получается динамическая структура, называемая линейным списком. Между элементами линейного списка существует отношение предыдущий-последующий. Стек — это структура, работа с которой происходит по принципу LIFO: последним пришел — первым ушел. Включение элемента в стек и удаление элемента из стека выполняются только с одной стороны, которая называется вершиной стека. Очередь — это структура, работа с которой происходит по принципу FIFO: первым пришел — первым ушел. Включение элемента в очередь и удаление элемента из очереди выполняются с двух разных сторон, которые называются хвостом и головой очереди, соответственно
2.Операции добавления, удаления и поиска для списка. Алгоритмы, реализации этих операций.
Самый простой способ соединить отдельные элементы между собой заключается в том, чтобы снабдить каждый из них только одной ссылкой на другой элемент. В результате получается динамическая структура, называемая линейным списком. Между элементами линейного списка существует отношение предыдущий-последующий.
Для элемента линейного списка можно определить следующий тип данных:
struct Element
{ Tdata info;
Element *next;
};
Для создания списка необходимо выполнить следующие действия:
определить указатель на начало списка
определить рабочую переменную-указатель
создать первый элемент списка с помощью указателя на начало
присвоить рабочему указателю значение указателя на начало списка
5. создать новый элемент с помощью поля ссылки элемента, адресуемого рабочим указателем
6. присвоить рабочему указателю значение поля ссылки, адресуемого им элемента
7. если список не завершен, то перейти к пункту 5
Новый элемент может быть добавлен в список до или после указанного
Начнем рассмотрение со второго случая. Пусть задан указатель p на начало списка и указатель q на некоторый элемент списка.Требуется вставить новый элемент после элемента, адресуемого указателем q.
Алгоритм добавления нового элемента после указанного:
определить рабочий указатель r
создать с его помощью новый элемент и записать в него требуемое значение
связать новый элемент с элементом, следующим за указанным
связать указанный элемент с новым элементом
Добавление до указанного элемента:
добавить новый элемент после указанного
скопировать в него значение , хранящееся в указанном элементе
записать в указанный элемент требуемое значение
Для удаления элемента необходимо иметь ссылку на предшествующий ему элемент. Пусть задан указатель p на начало списка и указатель q на элемент списка, предшествующий удаляемому. Требуется удалить элемент, следующий за указанным.
Алгоритм поиска в списке сводится к последовательному перемещению вдоль списка с помощью рабочего указателя q.
3.Стек как структура с дисциплиной доступа LIFO. Основные операции со стеком и алгоритмы их реализации.
Стек — это структура, работа с которой происходит по принципу LIFO: последним пришел — первым ушел (от англ. Last - In - First - Out). Включение элемента в стек и удаление элемента из стека выполняются только с одной стороны, которая называется вершиной стека.
Основные операции над стеком:
Инициализация стека, то есть подготовка структуры;
Включение нового элемента в стек (англ. push – заталкивать);
Проверка стека на пустоту;
Исключение элемента из стека (англ. pop – выталкивать).
Структуру стека и действия со стеком определим следующим образом:
struct Element
{
Tdata info;
Element *next;
};
typedef Element * Tstack;
Tstack top;
void InitStack(Tstack &t)
{ // инициализация стека
t = NULL;
}
bool StackIsEmpty(Tstack t)
{ // проверка на пустоту
return t = = NULL;
}
Выборка из стека:
Tdata PopStack(Tstack &t)
{ // взять из стека
Tstack r = t;
Tdata x = t->info;
t = t->next;
delete r;
return x;
}
Добавление в стек: void PushStack(Tstack &t, Tdata x)
{ // поместить в стек
Tstack r = new Tstack;
r->info = x;
r->next = t;
t = r;
}
4.Очередь как структура с дисциплиной доступа FIFO. Основные операции с очередью и алгоритмы их реализации.
Очередь — это структура, работа с которой происходит по принципу FIFO: первым пришел — первым ушел (от англ. First - In - First - Out). Включение элемента в очередь и удаление элемента из очереди выполняются с двух разных сторон, которые называются хвостом и головой очереди, соответственно.
Основные операции над очередью:
Инициализация очереди, то есть подготовка структуры;
Включение нового элемента в очередь(англ. push – заталкивать);
Проверка очереди на пустоту;
Исключение элемента из очереди(англ. pop – выталкивать).
Структуру очереди и действия с очередью определим следующим образом:
typedef struct
{
Element *head, *tail;
} Tqueue;
Тип Element для очереди определен также, как и для стека
Инициализация и проверка на пустоту:
void InitQueue(Tqueue &q)
{ // инициализация очереди
q.head = q.tail = NULL;
}
bool QueueIsEmpty(Tqueue q)
{ // проверка на пустоту
return q.head == NULL && q.tail == NULL;
}
Выборка из очереди:
Tdata PopQueue(Tqueue &q)
{ // взять из очереди
Element *r = q.head;
Tdata x = q.head->info;
if (q.head->next)
q.head = q.head->next;
else
q.head = q.tail = NULL;
delete r;
return x;
}
Добавление в очередь:
void PushQueue(Tqueue &q, Tdata x)
{ // поместить в очередь
Element *r = new Element;
r->info = x;
r->next = NULL;
if (q.tail)
{ q.tail->next = r;
q.tail = r; }
else
q.head = q.tail = r;
}
5.Рекурсивные и итеративные алгоритмы. Общая структура рекурсивного алгоритма. Алгоритм быстрой сортировки.
Одним из основных принципов программирования является принцип декомпозиции – принцип «разделяй и властвуй». В соответствии с этим принципом любая сложная задача должна быть сведена (редуцирована) к набору более простых задач. Одним из способов такого редуцирования является процедурная декомпозиция, при которой для каждой из подзадач создается своя, реализующая ее функция.
Аттерационный вызов ф-ции(циклы):
Этот случай имеет место, когда вызываемая функция вызывает другую функцию, а та, в свою очередь, третью и т.д. Аргументы и локальные переменные каждой следующей вызываемой функции размещаются в системном стеке над данными ранее вызванной функции. Поскольку ни одна из функций не может завершить работу ранее, чем ее завершит вызванная ею функция, освобождение стека происходит в порядке обратном его заполнению
Рекурсивный вызов функции:
Если функция содержит внутри себя обращение к себе самой, то имеет место рекурсивный вызов. Подобные функции называются рекурсивными. Для того, чтобы цепочка рекурсивных вызовов имела конечную длину необходимо чтобы рекурсивная функция содержала условие выхода из рекурсии. Длина цепочки рекурсивных вызовов называется глубиной рекурсии.
Рекурсивная процедура P должна иметь вид:
if C then
P // рекурсивный вызов
else T // непосредственное вычисление или действие
или
while C do
P; // рекурсивный вызов
T // непосредственное вычисление или действие
или некоторую другую эквивалентную форму.
Алгоритм быстрой сортировки.
Метод быстрой сортировки разработан в 1960 г. английским ученомым Чарльзом Хоаром (Charles Antony Richard Hoare).
Сортировка Хоара является усовершенствованием сортировки методом обменом.
Идея метода
Идея этого метода основана на том факте, что наибольшая эффективность достигается, если производить обмены элементов, отстоящих друг от друга на большом расстоянии.
Рекурсивный характер алгоритма
Алгоритм Хоара является рекурсивным и сводится к сортировке отдельных подмассивов исходного массива
По этой причине этот метод сортировки называют также сортировкой сегментацией
Границы сортируемого подмассива задаются значениями переменных L и R
Алгоритм:
Выберем некоторым образом элемент x (опорный элемент) и будем просматривать массив, двигаясь слева направо, пока не найдем элемент a[i]>x
Затем будем просматривать массив справа налево, пока не найдем элемент a[j]<x
Поменяем местами эти элементы и сместим индексы i и j на единицу вправо и влево, соответственно
Продолжим "просмотр с обменом", пока индексы i и j не встретятся где-то в середине массива
В результате массив оказался поделенным на две части: левую с a[i]<x и правую с a[j]>x
При этом выполнилось условие i>j
То же самое необходимо проделать с левой и правой частями массива
В левом подмассиве нужно установить правую границу R=j
В правом подмассиве нужно установить левую границу L=i
Процесс продолжается до тех пор, пока каждая часть не будет содержать только один элемент. Мы получили типичный рекурсивный алгоритм
Код процедуры быстрой сортировки
void QuickSort (int a[], int L, int R)
// Усовершенствованный метод обмена
{
int i =L; int j = R;
int x = a[(L + R)>>1];
do {
while (a[i]<x) i++;
while (a[j]>x) j--;
if(i<=j) {
x = a[i];
a[i] = a[j];
j
] = x;
i++; j--;
}
}
while (i<=j);
if (i<R) QuickSort(a,i,R);
if (j>L) QuickSort(a,L,j);
}
Способ выбора опорного элемента
В качестве опорного значения x можно выбирать значение любого элемента сортируемого подмассива
Оказывается однако, что в подавляющем большинстве случаев выбор в качестве x значения среднего элемента приводит к хорошим результатам
Эффективность алгоритма
Алгоритм быстрой сортировки следует применять для больших (N > 15) массивов, так как для массивов размером меньше пороговой (N < 15) этот алгоритм становится неэффективным
Неэффективен этот алгоритм и на частично упорядоченных массивах
6.Сравнительный анализ рекурсивных и итеративных алгоритмов. Когда необходима рекурсия? Задача о ханойских башнях.
При рекурсивных вызовах происходит повторное выполнение одного и того же кода (тела функции) при изменяющихся значениях некоторых переменных. То же самое происходит в циклах, где многократно повторяется выполнение кода, представляющего тело цикла. В этом сходство циклов и рекурсии, а в чем состоит их различие? Первое различие уже обозначено – в цикле повторно выполняется некоторая часть операторов содержащей его функции, рекурсия связана с вызовом отдельной функции.
Каждый следующий шаг цикла (итерация) начинается после завершения предыдущего
Неверное задание условия выхода из цикла приводит к «зависанию» программы
Каждый следующий рекурсивный вызов происходит до завершения предыдущего с сохранением данных в системном стеке
Неверное задание условия выхода из рекурсии приводит к переполнению стека
Алгоритмы, основанные на использовании циклов называются итеративными, а на использовании рекурсии – рекурсивными
Любую рекурсивную программу можно преобразовать в чисто итеративную. Но для этого придется самим создавать стек для рекурсивных вызовов и оперировать с ним, и порой эти операции до такой степени заслоняют основной алгоритм, что понять его становится весьма и весьма трудно.
Поэтому алгоритмы, которые по своей природе скорее рекурсивны, чем итеративны, следует представлять в виде рекурсивных процедур.
Задача о ханойских башнях.
На одном из стержней лежат 64 золотых кольца, различных по размеру и расположенных пирамидой: снизу самого большого диаметра, потом меньшего, и, наконец, на верху пирамиды — самый маленький.
Эти кольца нужно переложить на другой стержень, получив точно такую же пирамиду. Третий стержень используется как промежуточный. Условия перемещения колец следующие:
-за один раз можно перемещать только одно кольцо;
-кольцо можно брать только с вершины одной из пирамид, а класть либо на пустой стержень, либо на кольцо большего диаметра.
В стек помещаются целые числа, величина числа соответствует размеру кольца. Каждый стержень имеет свой стек. И тогда перемещение кольца с одного стержня на другой эквивалентно взятию числа из одного (соответствующего стержню) стека и помещению в другой стек.
структура процедуры Hanoj будет такой:
void Hanoj(stack d1,d2,d3; //Стержни
int k) //Кол-во колец
{
int i,e;
if (k>0)
{
Hanoj(d1,d3,d2,k-1);
e=pop(d1);// кольцо берется со стержня d1
push(d2,e);// кольцо кладется на стержень d2
Hanoj(d3,d2,d1,k-1);
}
}
7.Задача сортировки данных. Сортировка одномерных массивов. Простые алгоритмы сортировки. Алгоритм сортировки выбором.