Решение задач на использование алгоритмов обработки данных
Цель лекции: изучить основные приемы разработки алгоритмов обработки данных, научиться применять их при решении задач и учитывать трудоемкость и эффективность используемых алгоритмов.
Рассмотренные в предыдущих лекциях алгоритмы в основном относятся к базовым алгоритмам обработки данных и являются результатом исследований и разработок, проводившихся на протяжении десятков лет. Они, как и прежде, продолжают играть важную роль во все расширяющемся использовании в вычислительных процессах. На этих алгоритмах строится большинство задач повышенной сложности и задач олимпиадного уровня.
Приведем общую схему решения задач по программированию.
- Чтение условия. Необходимо внимательно прочесть условие задачи, не пропуская ни одной фразы.
- Построение математической модели. Необходимо понять, в чем заключается задача – построить ее математическую модель (на листке бумаги или образно), то есть достаточно формально и математически строго понять условие.
- Построение общей схемы решения. Теперь следует перейти от понимания того, что необходимо сделать, к пониманию того, как это сделать, то есть наметить эффективный алгоритм решения задачи и пути его реализации.
- Стыковка. Под стыковкой понимается уточнение решений, принятых на предыдущем этапе. Необходимо достаточно медленно и тщательно продумать, из каких частей будет состоять программа, какие массивы и структуры будут выделены и т.д.
- Реализация. На этом этапе собственно пишется сама программа. Иногда предпочтительнее программирование "сверху вниз", иногда – "снизу вверх" или их комбинация.
- Тестирование и отладка. Добившись того, чтобы программа компилировалась, необходимо убедиться в ее правильности. Проблемы могут быть в мелких ошибках, допущенных в процессе написания: перепутанные имена переменных, неверный знак в формуле и т.д. Решение может быть принципиально неправильным или неэффективным. Размер массивов может быть недостаточным или, напротив, чрезмерным, что будет вызывать ошибку "превышен предел памяти".
Алгоритмы сортировки данных
Данную тему следует рассматривать в двух аспектах. Во-первых, при решении различных задач повышенной сложности данные довольно часто требуется упорядочить по некоторому признаку (то есть отсортировать). При этом, если специально не оговорено иное, считается, что массив требуется отсортировать в порядке неубывания значений его элементов (для различных элементов – в порядке возрастания). Во-вторых, задача сама по себе может требовать построения оптимального в смысле определенных требований или нестандартного алгоритма сортировки. Помимо этого, специфика задач повышенной сложности может состоять в формализации критерия, по которому следует сортировать данные.
Особую роль при выборе метода сортировки играет его трудоемкость и эффективность. Приведем таблицу, в которой для известных универсальных алгоритмов сортировки приведены порядки для количества выполняемых тем или иным алгоритмом в худшем случае операций сравнения и присваивания.
Название сортировки | Количество сравнений | Количество присваиваний |
---|---|---|
Простой обмен (пузырьковая) | O(N2) | O(N2) |
Прямой выбор | O(N2) | O(N) |
Простая вставка | O(N2) | O(N2) |
Быстрая | O(N2) (на практике O(N log N) ) | O(N2) (на практике O(N log N) ) |
Слияниями | O(N log N) | O(N log N) |
Пирамидальная | O(N log N) | O(N log N) |
Таким образом, наилучшую теоретическую оценку имеют два последних из перечисленных в таблице алгоритмов, однако, в практическом программировании для упорядочивания данных обычно используют быструю сортировку, как в силу высокой производительности (особенно выигрывает данный алгоритм по числу реально выполняемых присваиваний), так и в силу простой реализации. Тем не менее, в задачах повышенной сложности данные могут быть представлены так, что отсортировать за отведенное время их можно будет лишь с помощью пирамидальной сортировки или какой-либо другой.
Пример 1. Задача "Поразрядная сортировка"
Поразрядная сортировка была изобретена в 1920-х годах как побочный результат использования сортирующих машин. Такая машина обрабатывала перфокарты, имевшие по 80 колонок. Каждая колонка представляла отдельный символ. В колонке было 12 позиций, и в них для представления того или иного символа пробивались отверстия. Цифру от 0 до 9 кодировали одним отверстием в соответствующей позиции (еще две позиции в колонке использовали для кодировки букв).
Запуская машину, оператор закладывал в ее приемное устройство стопку перфокарт и задавал номер колонки на перфокартах. Машина "просматривала" эту колонку на картах и по цифровому значению 0, 1, ..., 9 в ней распределяла ("сортировала") карты на 10 стопок.
Несколько колонок (разрядов) с закодированными цифрами представляли натуральное число, т.е. номер. Чтобы получить стопку карт, упорядоченных по номерам, оператор действовал так. Вначале он распределял карты на 10 стопок по значению младшем разряде. Эти стопки в порядке возрастания значений в младшем разряде он складывал в одну и повторял процесс, но со следующим разрядом, и т.д. Получив стопки карт, распределенных по значениям в старшем разряде, оператор складывал их по возрастанию этих значений и получал то, что нужно.
Значения в разрядах номеров заданы цифрами, поэтому поразрядную сортировку еще называют цифровой. Заметим, что цифры от 0 до 9 упорядочены по возрастанию, поэтому цифровая сортировка располагает числа в лексикографическом порядке.
Пример.
Описание решения.
Принцип решения разберем на конкретном примере. Пусть задана последовательность трехзначных номеров:
733 877 323 231 777 721 123
Распределим данную последовательность по младшей цифре на стопки:
231 721 733 323 123 877 777
Далее сложим получившиеся стопки в одну в порядке возрастания последней цифры.
231 721 733 323 123 877 777
На следующем шаге номера, которые обрабатываются именно в этой последовательности, распределяются по второй цифре на следующие стопки.
721 323 123 231 733 877 777
Затем из них также образуется одна последовательность.
721 323 123 231 733 877 777
Обратим внимание, что перед последним шагом все номера с числом сотен 7, благодаря предыдущим шагам, расположены один относительно другого по возрастанию.
На последнем шаге номера распределяются по старшей цифре на стопки:
123 231 323 721 733 777 877
и образуется окончательная последовательность:
123 231 323 721 733 777 877.
#include "stdafx.h" #include <iostream> using namespace std; const int D = 3; const int B = 10; typedef int T[D]; typedef T *List; void SortD(int k); void Done(); void outDigs(int i); List Data; int PFirst[B], PLast[B], *PQNext; int first, n, newL, tempL, i, nextI; int _tmain(int argc, _TCHAR* argv[]){ int k; cout << "Введите количество элементов массива n "; cin >> n; Data = new T[n]; PQNext = new int[n]; for ( k = 0 ; k < n ; k++ ){ PQNext[k] = k + 1; for ( int r = 0 ; r < D ; r++ ) Data[k][r] = 0; } for ( k = 0 ; k < n ; k++ ) for ( int r = 0 ; r < D ; r++ ) Data[k][r] = rand()%B; first = 0; Done(); cout << endl; for ( k = D - 1 ; k >= 0 ; k-- ) SortD(k); Done(); cout << endl; delete [] PQNext; delete [] Data; system("pause"); return 0; } // описание функции поразрядной сортировки void SortD(int k){ for ( tempL = 0 ; tempL < B ; tempL++ ){ PFirst[tempL] = n; PLast[tempL] = n; } i = first; while (i != n){ tempL = Data[i][k]; nextI = PQNext[i]; PQNext[i] = n; if ( PFirst[tempL] == n ) PFirst[tempL] = i; else PQNext[PLast[tempL]] = i; PLast[tempL] = i; i = nextI; } tempL = 0; while ( tempL < B && PFirst[tempL] == n ) tempL++; first = PFirst[tempL]; while ( tempL < B - 1 ){ newL = tempL + 1; while ( newL < B && PFirst[newL] == n ) newL++; if ( newL < B ) PQNext[PLast[tempL]] = PFirst[newL]; tempL = newL; } } /*описание функции вывода элементов в соответсвии со списком индесов в массиве PQNext*/ void Done(){ int i = first; while ( i != n ){ outDigs(i); i = PQNext[i]; } } /*описание функции вывода элементов из массива Data, индекс которого задан ее аргументом*/ void outDigs(int i){ int j = 0; while ( Data[i][j] == 0 && j < D ) j++; if ( j == D ) cout << 0; else while ( j < D ) cout << Data[i][j++]; cout << " "; }Листинг .