Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

информатика.Методы сортировок

.pdf
Скачиваний:
10
Добавлен:
04.06.2015
Размер:
2.25 Mб
Скачать

З*п2

п2-З*п+10

о

1

2

3

 

 

n

 

Рисунок 2. Если п> 2, то 3 * n2

больше, чем n2 - 3 * п + 1О

количество единиц времени, даже если размер задачи равен 1. Следовательно, еслиf(n) == log n, задачу при п == 1 следует рассматривать отдельно.

Чтобы подчеркнуть значение правильной оценки степени роста функции, рассмотрим таблицу и график, представленные на рис. 3. В таблице (рис. 3, а) показаны разные значения аргумента п и приближенные значения некоторых функций, зависящих от п, в порядке увеличения

скорости их роста.

0(1) < 0(10g2n) < О(n) < 0(n*log2n) < о(n2) < О(n3) < 0(211)

По этой таблице можно оценить относительную скорость роста значений различных функций. (На рис. 3 , б показаны графики этих функций).

1 Константа означает, что время выполнения алгоритма

постоянно и, следовательно, не зависит от размера задачи

Время выполнения логарифмического алгоритма (logarithmic algorithm) медленно возрастает с увеличениемразмера задачи. Если

размер задачи возводится в квадрат, ее сложность увеличивается

всего в два раза. Позднее мы убедимся, что алгоритм бинарного поиска обладает именно такими свойствами. Напомним, что при бинарном поиске массив делится пополам, а затем поиск продолжается в одной из полученных половин массива. Обычно логарифмические алгоритмы решают задачу, сводя ее к задаче меньшего размера. Основание логарифма не влияет на сложность

алгоритма, поэтому его можно не указывать.

10

пВремя выполнения линейного алгоритма (linear algorithm) прямо пропорционально размеру задачи. Если размер задачи возводитсяв квадрат, объем времени увеличиваетсяточно так же

n*log2n

Время выполнения алгоритма, имеющего сложность O(n*log2n)

 

растет быстрее, чем у линейного алгоритма. Такие алгоритмы обычно

 

разбивают задачи на подзадачи и решают их по отдельности. Пример

n2

такого алгоритма - сортировка слиянием -

рассматривается далее

Время

выполнения квадратичного

алгоритма

(quadratic

 

аlgогithm)быстро возрастает с увеличением размера задачи. В

 

алгоритмах такого типа часто используются два вложенных

 

цикла. Такие алгоритмы следует применять лишь для решения

 

небольшихзадач.

 

 

n3

Время выполнения кубического алгоритма (qubic algorithm) еще

 

быстрее возрастаетс увеличениемразмера задачи по сравнению с

 

квадратичным. Алгоритмы, использующие три

вложенных

 

цикла,

часто оказываются кубическими. Такие

алгоритмы

следует применять лишь для решения небольшихзадач

2n

С

увеличением

размера

задачи

время

выполнения

экспоненциального алгоритма (exponential algorithm) обычно

резко возрастает, поэтому на практике такие алгоритмы

применяются редко

Если сложность алгоритма А пропорциональна функции f(n) , а сложность алгоритма В пропорциональна функции g, которая растет медленнее, чем функция f, то совершенно очевидно, что алгоритм В эффективнее алгоритмаА, если размер решаемой задачи достаточно велик. Сложность алгоритма является решающим фактором при оценке его эффективности.

Для упрощения анализа алгоритмов будем использовать некоторые математическиесвойства обозначения О-большое. При этом следует иметь в виду, что запись ОИn)) означает «порядкаf(n»> или «имеет порядок f(n»>. Символ О - это не функция.

1.При оценке сложности алгоритма можно учитывать только старшую

степень. Например, если алгоритм имеет сложность О(n3 + 4*n2 + 3*n), он имеет порядок О(п3 ). Из таблицы, показанной на рис. 3, а, видно, что слагаемое п3 намного больше, чем слагаемые 4*n 2 и 3*n, особенно при больших значениях п, когда порядок функции n3 + 4*n2 + 3*п совпадает с порядком функции п'. Иначе говоря, эти функции имеют одинаковый порядок роста. Итак, даже если сложностьалгоритмаравна О(n3 + 4*n 2 + 3*n), можно говорить, что он имеет порядок просто О(n3 ). Как правило,

алгоритмы имеют сложность О(((n)) ,

где функцией f(n}

является одна из

функций, перечисленныхна рис. 3.

 

 

2.При оценке сложности

алгоритма можно

игнорировать

множитель при старшей

степени.

Например, если

алгоритм имеет

11

сложность О(5*n3 ),МОЖНО говорить, что он имеет порядок О(n3). Это

утверждение следует из определения величины O(f(n)), если положить k

== 5.

 

3.0(f(n))+O(g(n))=O(f(n)+g(n)). Функции,

описывающие сложность

алгоритма, можно складывать. Например, если алгоритм имеет сложность

о(n2)+о(n), то говорят, что он имеет сложностьо(n2+n). В соответствии с лт.Г, это МОЖНО записать просто как о(n2). Аналогичные правила

выполняютсядля умножения.

Из указанных выше свойств следует, что при оценке эффективности алгоритма нужно оценить лишь порядок его сложности. Точная формула,

описывающая сложность алгоритма, зачастую весьма сложна, а иногда и

просто невозможна.

Наихудший и средний варианты. При решении конкретных задач одинаковой размерности время выполнения алгоритма может оказаться разным. Например, время, необходимое для поиска п элементов, может зависеть от природы самих элементов. Обычно оценивается максимальное время, необходимое для решения задачи размера п, т.е. наихудший вариант. Анализ наихудшего варианта (worts-case analysis) приводит к оценке O(f(n)), если при решении задачи, имеющей размер п, в наихудшем случае алгоритм выполняется не более чем за k*f(n} единиц времени для всех значений п, за исключением их конечного числа. Хотя анализ

наихудшего варианта приводит к пессимистическим оценкам, это не

означает, что алгоритм всегда будет работать медленно. Следует иметь в виду, что наихудший вариант на практике встречается редко.

12

а)

n

---------------------------------

Функция

 

110

11100

111,000

1110,000

11100,000

111,000,000

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1

 

1

 

1

 

1

 

1

 

1

 

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

/Og2n

 

3

 

6

 

9

 

13

 

16

 

19

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

п

 

10

 

 

102

 

103

 

104

 

105

 

106

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

n* /Og2n

 

30

 

 

664

 

9,965

 

105

 

106

 

107

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

n2

 

102

 

 

104

 

106

 

108

 

1010

 

1012

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3

 

103

 

106

 

109

 

1012

 

1015

 

1018

 

I n

I

 

 

 

 

 

 

 

 

 

 

 

 

12"

103

 

 

1030

 

10301

 

103,010

 

1030,103

 

10301,030

 

I

 

 

 

 

 

 

 

 

 

 

 

 

б)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

100

 

 

3

 

п2

 

 

n*log2n

 

 

 

 

 

 

 

 

 

 

 

 

 

 

75

ro

~

о

о

с, 50

:I:

Q)

1::

Q)

~

о

25

 

 

 

 

 

 

 

n

1

 

 

log2n

 

 

 

 

 

5

10

15

20

 

 

n

 

 

 

РИСУНОК 3. Сравнение сложности алгоритмов: а) в табличном виде; б) в графическом виде

Функция f(n)==l на рисунке не показана., поскольку она не соответствует выбранному масштабу. Ее график представляет собой линию., проходящую через точку У = 1 параллельно

осих.

13

Анализ среднего варианта(average-case analysis) позволяет оценить среднее время выполнения алгоритма при решении задачи размера n.Говорят, что среднее время выполнения алгоритма А равно O(f(n)) , если при решении задачи размера п оно не превышает величины k* f(n) для всех значений п, за исключением их конечного числа. Как правило, анализ среднего варианта выполнить намного сложнее, чем анализ наихудшего варианта. Одна из трудностей заключается в определении вероятностей появления разных задач одинаковой размерности. Вторая трудность заключается в вычислении распределений разных значений. Анализ наихудшего варианта легче

поддается вычислениям и поэтому выполняется намного чаще.

Эффективностьалгоритмов поиска

в качестве еще одного примера рассмотрим два алгоритма поиска: последовательныйи бинарный поиск элемента в массиве.

Последовательный поиск. При последовательном поиске элемента в

массиве, имеющем длину п, элементы просматриваются по очереди,

начиная с первого, пока не обнаружится искомый, либо не будет достигнут конец массива. В наилучшем случае искомым элементом является первый. Для его обнаружения понадобитсятолько одно сравнение. Следовательно,в

наилучшем случае сложность алгоритма последовательного поиска равна

0(1). В наихудшем случае искомый элемент является последним. Для того чтобы его найти, понадобится п сравнений. Следовательно, в наихудшем

случае сложность алгоритма последовательного поиска равна О(п).В среднем случае искомый элемент находится в средней ячейке массива и обнаруживаетсяпосле n/2 сравнений.

Бинарный ПОИСК. Является ли бинарный поиск более эффективным, чем последовательный? Алгоритм бинарного поиска, предназначен для

поиска элемента в упорядоченном массиве и основан на повторяющемся

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

размер очередного массива уменьшается вдвое по сравнению с предыдущим.

В ходе очередного разбиения массива алгоритм выполняет сравнения. Сколько сравнений выполняет алгоритм при поиске элемента в массиве,

имеющем длину n? Точный ответ на этот вопрос, разумеется, зависит от позиции искомого элемента в массиве. Однако можно вычислить

максимальное количество сравнений, т.е. наихудший вариант. Допустим,

что п == 2k , где k - некоторое натуральное число. Алгоритм поиска

выполняет следующие шаги.

14

1. Проверяет среднюю ячейку массива, имеющего длину n.

2. Проверяет среднюю ячейку массива, имеющего длину n/2.

3. Проверяет среднюю ячейку массива, имеющего длину n/22 и т.д,

Чтобы проверить среднюю ячейку массива, сначала нужно поделить массив пополам. После того как массив, состоящий из n элементов, поделен пополам, делится пополам одна из его половин. Эти деления продолжаются до тех пор, пока 'не останется только один элемент. Для этого потребуется

выполнить k разбиений массива. Это возможно, поскольку n/2k==1.

(Напомним, что n = 2k.) В наихудшем случае алгоритм выполнит k разбиений

и, следовательно, k сравнений. Поскольку п == 2k, получаем, что k== Iog2 n. Что произойдет, если число п не будет степенью двойки? Легко

найти наименьшее число k, удовлетворяющее условию

2k- 1<n<2k

(Например, если n равно 30, то k=5, поскольку 24 = 16 < 30 < 32 < 25.)

Алгоритм по-прежнему выполнит по меньшей мере k разбиений, пока не возникнет массив, состоящий из одного элемента. Итак, получаем следующие

оценки.

k-l < Iog2 п < k, k < 1+log2n< k+1, k~l+ 10g2n.

Следовательно, в наихудшем случае сложность бинарного поиска

оценивается величиной бинарного поиска равна O(lOg2n), если ni-2k в принципе

сложность алгоритма в наихудшем случае имеет порядок O(10g2n) для любого

значения п.

Можно ли утверждать, что бинарный поиск лучше последовательного?

Намного

лучше!

Например,

Iog21 000000==19,

поэтому

алгоритм

последовательного

поиска выполнит миллион сравнений, в то время как

алгоритм

бинарного поиска -

не более 20. Если массивы велики, бинарный

поиск намного эффективнее последовательного.

 

 

Однако

следует

иметь в

виду,

что условие упорядоченности

массива

приводит к дополнительным затратам, которые могут стать существенными.

В следующем разделе мы попробуем их оценить.

Алгоритмы сортировки И их эффективность

Сортировка (sorting) - это процесс упорядочения набора элементов в возрастающем или убывающем порядке. Сортировка необходима во многих ситуациях. Например, иногда нужно упорядочить данные, прежде чем включить их в отчет. Однако чаще всего сортировка выполняется в качестве первого шага некоторых алгоритмов. Например, поиск данных является одной из наиболее распространенных задач, выполняемых компьютерами. Если набор данных достаточно велик, необходимо применять эффективный метод, например алгоритм бинарного поиска. Однако для применения алгоритма

15

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

Алгоритмы сортировки разделяются на две категории. При выполнении внутренней сортировки (intemal зоп) предполагается,что все данные находятся в оперативной памяти компьютера. Мы будем рассматриватьтолько алгоритмы внутренней сортировки. При выполнении внешней сортировки (extemal воп)

данные могут храниться на вспомогательныхзапоминающихустройствах,

например на жестком диске.

Упорядочивать можно целые числа, строки символов и даже объекты. Легко представить себе результаты сортировки набора целых чисел или строк. Однако для набора объектов эта операция непривычна. Если каждый объект

содержит только одну переменную-член,то их сортировка ничем не отличается

от сортировки целых чисел. Однако если в объектах содержатся несколько

данных-членов, нужно указать, какая переменная определяет порядок

следования объектов. Эта переменная-членназывается ключом сортировки (5011 key). Например, если объекты хранят информацию о людях, их можно упорядочить по имени, возрасту или почтовому индексу. Независимо от выбора ключа сортировки алгоритм сортировки упорядочивает все объекты по значению этой переменноЙ-члена.

Для простоты будем предполагать, что сортировка применяется к числам или символам. Все рассматриваемые алгоритмы ориентируются на возрастающий порядок. Изменить порядок на убывающий довольно просто. В каждом примере предполагается, что данные хранятся в массиве.

Представьте себе данные, которые можно проверять все сразу. Для их упорядочения можно было бы выбрать наибольший элемент, поставить его на свое место, затем найти следующий наибольший элемент, поставить его на свое место и.т.д. Карточным игрокам этот процесс напоминает перетасовку карт в определенном порядке. Этот интуитивный алгоритм формализуется с помощью сортировки методом выбора (selection sort).

Чтобы упорядочить массив в возрастающем порядке, нужно выбрать наибольший элемент. Поскольку наибольший элемент нужно поставить в самый конец массива, его нужно поменять местами с последним элементом, даже если эти элементы идентичны. Теперь, игнорируя последний (наибольший) элемент массива, выполним поиск наибольшего элемента в оставшейся части массива и поменяем его местами с предпоследним элементом исходного массива. Этот процесс продолжается до тех пор, пока не будут найдены и переставлены n-l элемент из п элементов массива. Оставшийся элемент, стоящий первым, не нарушает порядок и поэтому не

рассматривается.

На рис.4 показан пример сортировки методом выбора. Среди пяти

целых чисел выбираетсянаибольшее-

число 37, которое меняется местами

с последним элементом массива -

числом 13. (Числа, стоящие на

правильных местах, выделены полужирным шрифтом. Это соглашение

16

принято для всех остальных рисунков.) Затем среди оставшихся четырех чисел снова выбирается наибольшее - число 29, - которое меняется местами с предпоследним элементом - числом 13. Обратите внимание, что следующий выбор - число 14- уже стоит на правильном месте, однако алгоритм игнорируетэтот факт и выполняетфиктивную перестановкучисла 14 на одном и том же месте. В принципе намного эффективнее выполнять фиктивные перестановки, чем каждый раз проверять, нужна перестановка или нет. В заключение выбирается число 13, которое меняется местами со вторым элементом массива - числом 1о. Теперь массив упорядочен по

возрастанию.

Выбранные элементы закрашены:

элементы, стоящие на своих местах,

выделены полужирным шрифтом

Исходный массив

После 1-го обмена:

После 2-го обмена:

После З-го обмена:

После 4-го обмена:

РИСУНОК 4. Сортировка массива, состоящего из пяти целых чисел, методом выбора

Рассмотрим функцию на языке С++, выполняющую сортировку массива theАrrауметодом выбора.

'JJpeJe.!'тип-элементи-массива DatuTY/)f!"

yoid selectionSon(DataType thеАггщ'f/. ln111)

/T-finoPJz-(j-()-Ч-Zl(i'(1ет элементы .Нассива п() вО)J")-"u"-"c'-"n'"·1····(1·-·····/1···'["'n....(..)...,"

//Предусловие: массив tJ](:!i1Гf~ау состоит из н элементис.

//Постусловие: массив t}U!,AIT(~1~упорядочен по возпаспшниы­

//число 11 остается без изменения.

//ВызываемыефУUКI(IIU: inl1еХ(I!LаГ,f!;еs!,-"н'ар.

//--------------------------------------------------------­

//last .::::: индекс последнего элемента в подмассиве.

//подлежащем сортировке.

/?larK(!s! ~:::: индекс найденного наибольшего злементо

jor (intlast .:::: n-/: last >.:::: 1; --last)

(

t

17

1/Инвариант: массив theI417'ay/7aSl -+- l ..п-Г]упорядочен, // а егоразмерпревышаетразмермассиваIhe1tlJ'~1~UY[().. last}

11;/ Выбираем наибольший элемент в массиве Ihe,A1TayfU.. /ast/

int lallf (gest = intiех(!/l.lагр:еs{rthеАfтау, /ast-+- J),'

// Меняем местами элементы 11-1еА174(О,,/!агдеs!/ и Iht!./11T(~Y //l1S/! SH'op(tJle,A/'TaJ:f1al}!,'cst/, thejl,.r{~~:/!ast!);

}// Конец оператораюк

) // Конец функция sеlесtiоnSогl

Функция sеlесfiоn/)огt вьпывает две функиии: indех(!fIаГfZсst и S1'1Ylp.

;nt Index(!fL(J1If(~esf(COnc5t Datal)'pe lhеАГ/lfl1У[]. ;111 ыге!

//

//llаходUПI наибольшии элемент масснии.

// Предусловие: размер массива t}l~.A}T(H: задается аргумента» // ыге, причем ыге > 1.

!/ Постусловие: возеращает индекс наибольшего элемента массива. j/ АргументыН(! изменяются.

//--------------------------------------------------------­

;111 jndех~sоJ~'аг == (); // Индекс наибольшегоэлемента.

// найденного 00 сих пор. [о» (пи сипепнпаех ::;: 1; сипенипасх <. ы:«;

t- -сипепнпаех)

{

//Инвариант: tlle~4J7·ay[inllexSof·'aJ"1>

//fhe14rJIfа.у[О..сипе11t1ndех-!J index~Sof'ar := сипепнпаех;

}///{ОllСll операторакт

тит in(lexSoFaJIf; // Индекс наибольшегоэлементо

/ // Конец функции index(~flial"ge.\'l

»ои! змар!l)аtат.~lJеЬ х, [J111a7)Jpe& J)

//--------------------------------------------------------­

//()6Л1ен ()В}'Х энементов.

// Предусловие:аргументы.х и .У элементы, подлежащиеобмену.

/>/ Постусловие: содержимое ячейки х находится в ячейке J;'. и наоборап

//--------------------------------------------------------­

I l

})а1а Т)/])е /е111Р =:: -<-у:.

Х' == ));

\t' :::::- ге111/):

} // Конец функции S)~'{IIJ

Анализ. Как следует из описания алгоритма, сортировка сводится к сравнениям, обменам и перестановкам элементов. Для начала подсчитаем количествоэтих операций. Как правило, такие операции более дорогостоящи,

чем операции управления счетчиком цикла или манипуляции с индексами

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

второстепеннымиоперациями.

18

Очевидно, цикл for в функции selectionSort выполняется n-l раз. Таким образом, функция selectionSort n-l раз вызывает функции

indexOfLargest и swap. При каждом вызове функции indexOfLargest ее

цикл выполняется last раз (т.е. size-l раз, где size равно last+l). Итак, при n-l вызове функции indexOf Largest для значений переменной last от n-l до 1 общее количество итераций цикла равняется

(n-l )+(n-2)+ ... +1 == n*(n-l )/2.

Поскольку при каждой итерации в функции indexOf Largest выполняетсяодно сравнение,их общее количестворавно n*(n-l )/2.

В результате для n-l вызова функции swap выполняется n-l обменов. Для каждого обмена нужно выполнить три присваивания. Следовательно, общее количествооперацийприсваиванияравно 3*(n-l).

В сумме алгоритм сортировки методом выбора выполняет

n*(n-I)/2+З*(n-l) = n 2/2 + 5*n/2-З основных операций.

Применяя свойства обозначения О-большое, можем отбросить

слагаемые с младшими степенями. В итоге получим величину О(n /2).

Игнорируя множитель 1/2, получаем окончательную оценку о(n2 ). Итак, сложность алгоритма сортировки методом выбора равна о(n2).

Хотя алгоритм сортировки методом выбора не зависит от

первоначального расположения данных, что можно отнести к преимуществам

этого метода, его можно применять только для небольших массивов,

поскольку величина О(n-?) довольно быстро растет. Хотя алгоритм выполняет

о(n2) сравнений, в ходе сортировки осуществляется только О(n) перестановок.

Алгоритм сортировки методом выбора хорош, когда перестановки представляют собой затратные операции, а сравнения - нет. Это может произойти, когда каждый элемент данных достаточно велик, а ключ сортировки мал. Разумеется, хранение данных в связанном списке позволяет эффективно выполнять перестановки элементов любого алгоритма.

Сортировка методом пуэырькв

Алгоритм сортировки методом пузырька (bubble sort) сравнивает между собой соседние элементы и меняет их местами, если они нарушают порядок. Для этого приходится несколько раз просматривать одни и те же элементы. Во время первого прохода сравниваются два первых элемента массива; если они нарушают порядок, их меняют местами. Затем сравнивается другая пара, т.е. 2-й и 3-й элементы. Если они нарушают порядок, их меняют местами. Просмотр, сравнение и обмен двух элементов выполняется до тех пор, пока не будет достигнут конец массива.

19