Рефераты

VB, MS Access, VC++, Delphi, Builder C++ принципы(технология), алгоритмы программирования

зависимости от степени первоначальной упорядоченности списка. Из таблицы

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

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

Алгоритм быстрой сортировки, который описывается далее в этой главе,

способен отсортировать тот же список из 2000 элементов примерно за 0,12

сек, независимо от первоначального порядка расположения элементов в списке.

Пузырьковая сортировка может превзойти этот результат, только если примерно

97 процентов списка было упорядочено до начала сортировки.

=====239

@Таблица 9.2. Время пузырьковой сортировки 2.000 элементов

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

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

наилучшие результаты, если список изначально уже почти упорядочен. Если

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

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

выбором.

Быстрая сортировка

Быстрая сортировка (quicksort) — рекурсивный алгоритм, который использует

подход «разделяй и властвуй». Если сортируемый список больше, чем

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

два подсписка, а затем рекурсивно вызывает себя для сортировки двух

подсписков.

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

проста. Если алгоритм вызывается для подсписка, содержащего не более одного

элемента, то подсписок уже отсортирован, и подпрограмма завершает работу.

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

разбиения списка на два подсписка. Она помещает элементы, которые меньше,

чем выбранный элементы в первый подсписок, а остальные — во второй, и затем

рекурсивно вызывает себя для сортировки двух подсписков.

Public Sub QuickSort(List() As Long, ByVal min as Integer, _

ByVal max As Integer)

Dim med_value As Long

Dim hi As Integer

Dim lo As Integer

‘ Если осталось менее 1 элемента, подсписок отсортирован.

If min >= max Then Exit Sub

‘ Выбрать значение для деления списка.

med_value = list(min)

lo = min

hi = max

Do

Просмотр от hi до значения < med_value.

Do While list(hi) >= med_value

hi = hi - 1

If hi = med_value.

lo = lo + 1

Do While list(lo) < med_values

lo = lo + 1

If lo >= hi Then Exit Do

Loop

If lo >= hi Then

lo = hi

list(hi) = med_value

Exit Do

End If

‘ Поменять местами значения lo и hi.

list(hi) = list(lo)

Loop

‘ Рекурсивная сортировка двух подлистов.

QuickSort list(), min, lo - 1

QuickSort list(), lo + 1, max

End Sub

=========240

Есть несколько важных моментов в этой версии алгоритма, которые стоит

упомянуть. Во-первых, значение med_value для деления списка не входит ни в

один подсписок. Это означает, что в двух подсписках содержится на одни

элемент меньше, чем в исходном списке. Т.к. число рассматриваемых элементов

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

Эта версия алгоритма использует в качестве разделителя первый элемент в

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

списка, так чтобы два подсписка были примерно равного размера. Тем не

менее, если элементы первоначально почти отсортированы, то первый элемент —

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

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

алгоритма будет примерно такой, как показано на рис. 9.4.

В этом случае каждый вызов подпрограммы требует порядка O(N) шагов для

перемещения всех элементов во второй подсписок. Т.к. алгоритм рекурсивно

вызывает себя N - 1 раз, время его выполнения будет порядка O(N2), что не

лучше, чем у ранее рассмотренных алгоритмов. Ситуацию еще более ухудшает

то, что уровень вложенности рекурсии алгоритма N - 1. Для больших списков

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

программы.

=========242

@Рис. 9.4. Быстрая сортировка упорядоченного списка

Существует много стратегий выбора разделительного элемента. Можно

использовать элемент из середины списка. Это может оказаться неплохим

выбором, тем не менее, может оказаться и так, что это окажется наименьший

или наибольший элемент списка. При этом один подсписок будет намного

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

O(N2) и глубокому уровню рекурсии.

Другая стратегия может заключаться в том, чтобы просмотреть весь список,

вычислить среднее арифметическое всех значений, и использовать его в

качестве разделительного значения. Этот подход будет давать неплохие

результаты, но потребует дополнительных усилий. Дополнительный проход со

сложностью порядка O(N) не изменит теоретическое время выполнения

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

Третья стратегия — выбрать средний из элементов в начале, конце и середине

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

выбрать всего три элемента. При этом гарантируется, что этот элемент не

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

середине списка.

И, наконец, последняя стратегия, которая используется в программе Sort,

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

неплохим выбором. Даже если это не так, возможно на следующем шаге

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

наихудшего случая очень мала.

Интересно, что этот метод превращает ситуацию «небольшая вероятность того,

что всегда будет плохая производительность» в ситуацию «всегда небольшая

вероятность плохой производительности». Это довольно запутанное утверждение

объясняется в следующих абзацах.

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

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

будет порядка O(N2), Хотя маловероятно, что подобная организация списка в

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

при этом будет определенно порядка O(N2), неважно почему. Это то, что можно

назвать «небольшой вероятностью того, что всегда будет плохая

производительность».

===========242

При случайном выборе точки раздела первоначальное расположение элементов не

влияет на производительность алгоритма. Существует небольшая вероятность

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

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

вероятность плохой производительности». Независимо от первоначальной

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

будет порядка O(N2).

Тем не менее, все еще остается ситуация, которая может вызвать проблемы при

использовании любого из этих методов. Если в списке очень мало различных

значений в списке, алгоритм заносит множество одинаковых значений в

подсписок при каждом вызове. Например, если каждый элемент в списке имеет

значение 1, последовательность выполнения будет такой, как показано на рис.

9.5. Это приводит к большому уровню вложенности рекурсии и дает

производительность порядка O(N2).

Похожее поведение происходит также при наличии большого числа повторяющихся

значений. Если список состоит из 10.000 элементов со значениями от 1 до 10,

алгоритм довольно быстро разделит список на подсписки, каждый из которых

содержит только одно значение.

Наиболее простой выход — игнорировать эту проблему. Если вы знаете, что

данные не имеют такого распределения, то проблемы нет. Если данные имеют

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

сортировки. Описываемые далее в этой главе алгоритмы сортировки подсчетом и

блочной сортировки очень быстро сортируют списки, данных в которых

находятся в узком диапазоне.

Можно внести еще одно небольшое улучшение в алгоритм быстрой сортировки.

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

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

списков. Благодаря своей простоте, сортировка выбором быстрее при

сортировке примерно десятка записей.

@Рис. 9.5. Быстрая сортировка списка из единиц

==========243

@Таблица 9.3. Время быстрой сортировки 20.000 элементов

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

рекурсию до того, как подсписки уменьшатся до нуля, и использовать для

завершения работы сортировку выбором. В табл. 9.3 приведено время, которое

занимает выполнение быстрой сортировки 20.000 элементов на компьютере с

процессором Pentium с тактовой частотой 90 МГц, если останавливать

сортировку при достижении подсписками определенного размера. В этом тесте

оптимальное значение этого параметра было равно 15.

Следующий код демонстрирует доработанный алгоритм:

Public Sub QuickSort*List() As Long, ByVal min As Long, ByVal max As Long)

Dim med_value As Long

Dim hi As Long

Dim lo As Long

Dim i As Long

‘ Если в списке больше, чем CutOff элементов,

‘ завершить его сортировку процедурой SelectionSort.

If max - min < cutOff Then

SelectionSort List(), min, max

Exit Sub

End If

‘ Выбрать разделяющее значение.

i = Int((max - min + 1) * Rnd + min)

med_value = List(i)

‘ Переместить его вперед.

List(i) = List(min)

lo = min

hi = max

Do

‘ Просмотр сверху вниз от hi до значения < med_value.

Do While List(hi) >= med_value

hi = hi - 1

If hi = med_value.

lo = lo + 1

Do While List(lo) < med_value

lo = lo + 1

If lo >= hi Then Exit Do

Loop

If lo >= hi Then

lo = hi

List(hi) = med_value

Exit Do

End If

‘ Поменять местами значения lo и hi.

List(hi) = List(lo)

Loop

‘ Сортировать два подсписка.

QuickSort List(), min, lo - 1

QuickSort List(), lo + 1, max

End Sub

=======244

Многие программисты выбирают алгоритм быстрой сортировки, т.к. он дает

хорошую производительность в большинстве обстоятельств.

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

Как и быстрая сортировка, сортировка слиянием (mergesort) — это рекурсивный

алгоритм. Он также разделяет список на два подсписка, и рекурсивно

сортирует подсписки.

Сортировка слиянием делит список пополам, формируя два подсписка

одинакового размера. Затем подсписки рекурсивно сортируются, и

отсортированные подсписки сливаются, образуя полностью отсортированный

список.

Хотя этап слияния легко понять, это наиболее интересная часть алгоритма.

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

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

особенно если размер элементов велик. Если размер временного размера очень

большой, он может приводить к обращению к файлу подкачки и значительно

снижать производительность. Работа с временным массивом также приводит к

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

массивами.

Так же, как и в случае с быстрой сортировкой, можно ускорить выполнение

сортировки слиянием, остановив рекурсию, когда подсписки достигают

определенного минимального размера. Затем можно использовать сортировку

выбором для завершения работы.

=========245

Public Sub Mergesort(List() As Long, Scratch() As Long, _

ByVal min As Long, ByVal max As Long)

Dim middle As Long

Dim i1 As Long

Dim i2 As Long

Dim i3 As Long

‘ Если в списке больше, чем CutOff элементов,

‘ завершить его сортировку процедурой SelectionSort.

If max - min < CutOff Then

Selectionsort List(), min, max

Exit Sub

End If

‘ Рекурсивная сортировка подсписков.

middle = max \ 2 + min \ 2

Mergesort List(), Scratch(), min, middle

Mergesort List(), Scratch(), middle + 1, max

‘ Слить отсортированные списки.

i1 = min ‘ Индекс списка 1.

i2 = middle + 1 ‘ Индекс списка 2.

i3 = min ‘ Индекс объединенного списка.

Do While i1 List(j) Then _

j = j + 1

End If

If List(j) > tmp Then

‘ Потомок больше. Поменять его местами с родителем.

List(min) = List(j)

‘ Перемещение этого потомка вниз.

min = j

Else

‘ Родитель больше. Процедура закончена.

Exit Do

End If

Else

Exit Do

End If

Loop

List(min) = tmp

End Sub

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

из дерева элементов, необычайно прост:

Private Sub BuildHeap()

Dim i As Integer

For i = (max + min) \ 2 To min Step -1

HeapPushDown list(), i, max

Next i

End Sub

Приоритетные очереди

Приоритетной очередью (priority queue) легко управлять при помощи процедур

BuildHeap и HeapPushDown. Если в качестве приоритетной очереди используется

пирамида, легко найти элемент с самым высоким приоритетом — он всегда

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

корня уже не будет пирамидой.

Для того, чтобы снова превратить дерево без корня в пирамиду, возьмем

последний элемент (самый правый элемент на нижнем уровне) и поместим его на

вершину пирамиды. Затем при помощи процедуры HeapPushDown продвинем новый

корневой узел вниз по дереву до тех пор, пока дерево снова не станет

пирамидой. В этот момент, можно получить на выходе приоритетной очереди

следующий элемент с наивысшим приоритетом.

Public Function Pop() As Long

If NumInQueue < 1 Then Exit Function

' Удалить верхний элемент.

Pop = Pqueue(1)

' Переместить последний элемент на вершину.

PQueue(1) = PQueue(NumInPQueue)

NumInPQueue = NumInPQueue - 1

' Снова сделать дерево пирамидой.

HeapPushDown PQueue(), 1, NumInPQueue

End Function

Чтобы добавить новый элемент к приоритетной очереди, увеличьте пирамиду.

Поместите новый элемент на свободное место в конце массива. Полученное

дерево также не будет пирамидой.

Чтобы снова преобразовать его в пирамиду, сравните новый элемент с его

родителем. Если новый элемент больше, поменяйте их местами. Заранее

известно, что второй потомок меньше, чем родитель, поэтому нет

необходимости сравнивать новый элемент с другим потомком. Если элемент

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

Продолжайте сравнение нового элемента с родителем и перемещение его по

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

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

готова к работе.

Private Sub HeapPushUp(List() As Long, ByVal max As Integer)

Dim tmp As Long

Dim j As Integer

tmp = List (max)

Do

j = max \ 2

If j < 1 Then Exit Do

If List(j) < tmp Then

List (max) = List(j)

max = j

Else

Exit Do

End If

Loop

List(max) = tmp

End Sub

Подпрограмма Push добавляет новый элемент к дереву и использует

подпрограмму HeapPushDown для восстановления пирамиды.

Public Sub Push (value As Long)

NumInPQueue = NumInPQueue + 1

If NumInPQueue > PQueueSize Then ResizePQueue

PQueue(NumInPQueue) = value

HeapPushUp PQueue(), NumInPQueue

End Sub

========252

Анализ пирамид

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

помощи создания множества пирамид меньшего размера. Для каждого внутреннего

узла дерева строится пирамида с корнем в этом узле. Если дерево содержит N

элементов, то в дереве O(N) внутренних узлов, и в итоге приходится создать

O(N) пирамид.

При создании каждой пирамиды может потребоваться продвигать элемент вниз по

пирамиде, возможно до тех пор, пока он не достигнет концевого узла. Самые

высокие из построенных пирамид будут иметь высоту порядка O(log(N)). Так

как создается O(N) пирамид, и для построения самой высокой из них требуется

O(log(n)) шагов, то все пирамиды можно построить за время порядка O(N *

log(N)).

На самом деле времени потребуется еще меньше. Только некоторые пирамиды

будут иметь высоту порядка O(log(N)). Большинство из них гораздо ниже.

Только одна пирамида имеет высоту, равную log(N), и половина пирамид —

высоту всего в 2 узла. Если суммировать все шаги, необходимые для создания

всех пирамид, в действительности потребуется не больше O(N) шагов.

Чтобы увидеть, так ли это, допустим, что дерево содержит N узлов. Пусть H —

высота дерева. Это полное двоичное дерево, следовательно, H=log(N).

Теперь предположим, что вы строите все большие и большие пирамиды. Для

каждого узла, который находится на расстоянии H-I уровней от корня дерева,

строится пирамида с высотой I. Всего таких узлов 2H-I, и всего создается 2H-

I пирамид с высотой I.

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

тех пор, пока он не достигнет концевого узла. Перемещение элемента вниз по

пирамиде с высотой I требует до I шагов. Для пирамид с высотой I полное

число шагов, которое потребуется для построения 2H-I пирамид, равно I*2H-I.

Сложив все шаги, затрачиваемые на построение пирамид разного размера,

получаем 1*2H-1+2*2H-2+3*2H-3+…+(H-1)* 21. Вынеся за скобки 2H, получим

2H*(1/2+2/22+3/23+…+(H-1)/2H-1).

Можно показать, что (1/2+2/22+3/23+…+(H-1)/2H-1) меньше 2. Тогда полное

число шагов, которое нужно для построения всех пирамид, меньше, чем 2H*2.

Так как H — высота дерева, равная log(N), то полное число шагов меньше, чем

2log(N)*2=N*2. Это означает, что для первоначального построения пирамиды

требуется порядка O(N) шагов.

Для удаления элемента из приоритетной очереди, последний элемент

перемещается на вершину дерева. Затем он продвигается вниз, пока не займет

свое окончательное положение, и дерево снова не станет пирамидой. Так как

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

означает, что новый элемент к приоритетной очереди на основе пирамиды можно

добавить за O(log(N)) шагов.

Другим способом работы с приоритетными очередями является использование

упорядоченного списка. Вставка или удаление элемента из упорядоченного

списка с миллионом элементов занимает примерно миллион шагов. Вставка или

удаление элемента из сопоставимой по размерам приоритетной очереди,

основанной на пирамиде, занимает всего 20 шагов.

======253

Алгоритм пирамидальной сортировки

Алгоритм пирамидальной сортировки просто использует уже описанные алгоритмы

для работы с пирамидами. Идея состоит в том, чтобы создать приоритетную

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

Для удаления элемента алгоритм меняет его местами с последним элементом в

пирамиде. Это помещает удаленный элемент в конечное положение в конце

массива. Затем алгоритм уменьшает счетчик элементов списка, чтобы исключить

из рассмотрения последнюю позицию

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

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

оказаться меньше, чем его потомки. Поэтому алгоритм использует процедуру

HeapPushDown для продвижения элемента на его место. Алгоритм продолжает

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

пирамиде не останется элементов.

Public Sub Heapsort(List() As Long, ByVal min As Long, ByVal max As Long)

Dim i As Long

Dim tmp As Long

' Создать пирамиду (кроме корневого узла).

For i = (max + min) \ 2 To min + 1 Step -1

HeapPushDown List(), i, max

Next i

' Повторять:

' 1. Продвинуться вниз по пирамиде.

' 2. Выдать корень.

For i = max To min + 1 Step -1

' Продвинуться вниз по пирамиде.

HeapPushDown List(), min, i

' Выдать корень.

tmp = List(min)

List(min) = List(i)

List(i) = tmp

Next i

End Sub

Предыдущее обсуждение приоритетных очередей показало, что первоначальное

построение пирамиды требует O(N) шагов. После этого требуется O(log(N))

шагов для восстановления пирамиды, когда элемент продвигается на свое

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

требуется всего порядка O(N)*O(log(N))=O(N*log(N)) шагов, чтобы получить из

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

пирамидальной сортировки составляет порядка O(N)+O(N*log(N))=O(N*log(N)).

=========254

Такой же порядок сложности имеет алгоритм сортировки слиянием и в среднем

алгоритм быстрой сортировки. Так же, как и сортировка слиянием,

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

элементов до начала сортировки. Быстрая сортировка плохо работает со

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

слиянием и пирамидальная сортировка лишены этого недостатка.

Хотя обычно пирамидальная сортировка работает немного медленнее, чем

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

хранения временных значений, как для сортировки слиянием. Пирамидальная

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

пределах исходного массива списка.

Сортировка подсчетом

Сортировка подсчетом (countingsort) — специализированный алгоритм, который

очень хорошо работает, если элементы данных — целые числа, значения которых

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

быстро, например, если значения находятся между 1 и 1000.

Если список удовлетворяет этим требованиям, сортировка подсчетом

выполняется невероятно быстро. В одном из тестов на компьютере с

процессором Pentium с тактовой частотой 90 МГц, быстрая сортировка 100.000

элементов со значениями между 1 и 1000 заняла 24,44 секунды. Для сортировки

тех же элементов сортировке подсчетом потребовалось всего 0,88 секунд — в

27 раз меньше времени.

Выдающаяся скорость сортировки подсчетом достигается за счет того, что при

этом не используются операции сравнения. Ранее в этой главе отмечалось, что

время выполнения любого алгоритма сортировки, использующего операции

сравнения, порядка O(N*log(N)). Без использования операций сравнения,

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

порядка O(N).

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

элементов, имеющих определенное значение. Если значения находятся в

диапазоне между min_value и max_value, алгоритм создает массив Counts с

нижней границей min_value и верхней границей max_value. Если используется

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

Если существует M значений элементов, массив содержит M записей, и время

выполнения этого шага порядка O(M).

For i = min To max

Counts(List(i)) = Counts(List(i)) + 1

Next i

В конце концов, алгоритм обходит массив Counts, помещая соответствующее

число элементов в отсортированный массив. Для каждого значения i между

min_value и max_value, он помещает Counts(i) элементов со значением i в

массив. Так как этот шаг помещает по одной записи в каждую позицию в

массиве, он требует порядка O(N) шагов.

new_index = min

For i = min_value To max_value

For j = 1 To Counts(i)

sorted_list(new_index) = i

new_index = new_index + 1

Next j

Next i

======255

Алгоритм целиком требует порядка O(M)+O(N)+O(N)=O(M+N) шагов. Если M мало

по сравнению с N, он выполняется очень быстро. Например, если M Value Then min_value = Value

If max_value < Value Then max_value = Value

Set item = item.NextCell

Loop

' Если min_value = max_value, значит, есть единственное

' значение, и список отсортирован.

If min_value = max_value Then Exit Sub

' Если в списке не более, чем CutOff элементов,

' завершить сортировку процедурой LinkInsertionSort.

If count Value Then min_value = Value

If max_value < Value Then max_value = Value

Next i

' Если min_value = max_value, значит, есть единственное

' значение, и список отсортирован.

If min_value = max_value Then Exit Sub

' Создать пустой массив с отсчетами блоков.

ReDim counts(l To NumBuckets)

value_scale = _

CDbl (NumBuckets - 1) / _

CDbl (max_value - min_value)

' Создать отсчеты блоков.

For i = min To max

If List(i) = max_value Then

bucket_num = NumBuckets

Else

bucket_num = _

Int((List(i) - min_value) * _

value_scale) + 1

End If

counts(bucket_num) = counts(bucket_num) + 1

Next i

' Преобразовать отсчеты в смещение в массиве.

ReDim offsets(l To NumBuckets)

next_spot = min

For i = 1 To NumBuckets

offsets(i) = next_spot

next_spot = next_spot + counts(i)

Next i

' Разместить значения в соответствующих блоках.

For i = min To max

If List(i) = max_value Then

bucket_num = NumBuckets

Else

bucket_num = _

Int((List(i) - min_value) * _

value_scale) + 1

End If

Scratch (offsets (bucket_num)) = List(i)

offsets(bucket_num) = offsets(bucket_num) + 1

Next i

' Рекурсивная сортировка блоков, содержащих

' более одного элемента.

next_spot = min

For i = 1 To NumBuckets

If counts(i) > 1 Then ArrayBucketSort _

Scratch(), List(), next_spot, _

next_spot + counts(i) - 1, counts(i)

next_spot = next_spot + counts(i)

Next i

' Скопировать временный массив назад в исходный список.

For i = min To max

List(i) = Scratch(i)

Next i

End Sub

Из-за накладных расходов, которые требуются для работы со связными

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

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

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

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

версии станут практически эквивалентными по скорости.

Новую версию также можно сделать еще быстрее, используя функцию API MemCopy

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

Эта усовершенствованную версию алгоритма демонстрирует программа FastSort.

===========259-261

Резюме

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

описанных в этой главе, из которых можно вывести несколько правил, которые

могут помочь вам выбрать алгоритм сортировки.

Эти правила, изложенные в следующем списке, и информация в табл. 9.4 может

помочь вам подобрать алгоритм, который обеспечит максимальную

производительность:

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

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

11. если более 99 процентов списка уже отсортировано, используйте

пузырьковую сортировку;

12. если список очень мал (100 или менее элементов), используйте сортировку

выбором;

13. если значения находятся в связном списке, используйте блочную

сортировку на основе связного списка;

14. если элементы в списке — целые числа, разброс значений которых невелик

(до нескольких тысяч), используйте сортировку подсчетом;

15. если значения лежат в широком диапазоне и не являются целыми числами,

используйте блочную сортировку на основе массива;

16. если вы не можете тратить дополнительную память, которая требуется для

блочной сортировки, используйте быструю сортировка

Если вы знаете структуру данных и различные алгоритмы сортировки, вы можете

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

@Таблица 9.4. Преимущества и недостатки алгоритмов сортировки

=========263

Глава 10. Поиск

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

определенный элемент в списке. В этой главе описаны некоторые алгоритмы для

поиска элементов в упорядоченных списках. Она начинается с краткого

описания сортировки методом полного перебора. Хотя этот алгоритм

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

очень простым, что облегчает его реализацию и отладку. Из-за простоты этого

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

алгоритмов для очень маленьких списков.

Далее в главе описан двоичный поиск. При двоичном поиске список многократно

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

намного быстрее, чем полный перебор. Заключенная в этом методе идея

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

Затем в главе описан интерполяционный поиск. Так же, как и в методе

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

части. При использовании интерполяционного поиска, алгоритм делает

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

выполняется намного быстрее, если данные в списках распределены равномерно.

В конце главы обсуждаются методы следящего поиска. Применение этого метода

иногда уменьшает время поиска в несколько раз.

Примеры программ

Программа Search демонстрирует все описанные в главе алгоритмы. Введите

значение элементов, которые должен содержать список, и затем нажмите на

кнопку Make List (Создать список), и программа создаст список на основе

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

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

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

После создания списка выберите алгоритмы, которые вы хотите использовать,

установив соответствующие флажки. Затем введите значение, которое вы хотите

найти и нажмите на кнопку Search (Поиск), и программа выполнит поиск

элемента при помощи выбранного вами алгоритма. Так как список содержит не

все возможные элементы в заданном диапазоне значений, то вам может

понадобиться ввести несколько различных значений, прежде чем одно из них

найдется в списке.

Программа также позволяет задать число повторений для каждого из алгоритмов

поиска. Некоторые алгоритмы выполняются очень быстро, поэтому для того,

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

повторений.

=======265

На рис. 10.1 показано окно программы Search после поиска элемента со

значением 250.000. Этот элемент находился на позиции 99.802 в списке из

100.000 элементов. Чтобы найти этот элемент, потребовалось проверить 99.802

элемента при использовании алгоритма полного перебора, 16 элементов — при

использовании двоичного поиска и всего 3 — при выполнении интерполяционного

поиска.

Поиск методом полного перебора

При выполнении линейного (linear) поиска или поиска методом полного

перебора (exhaustive search), поиск ведется с начала списка, и элементы

перебираются последовательно, пока среди них не будет найден искомый.

Public Function LinearSearch(target As Long) As Long

Dim i As Long

For i = 1 To NumItems

If List(i) >= target Then Exit For

Next i

If i > NumItems Then

Search = 0 ' Элемент не найден.

Else

Search = i ' Элемент найден.

End If

End Function

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

элементы в начале списка быстрее, чем элементы, расположенные в конце.

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

конце списка или вообще не присутствует в нем. В этих случаях, алгоритм

проверяет все элементы в списке, поэтому время его выполнения сложность в

наихудшем случае порядка O(N).

@Рис. 10.1. Программа Search

========266

Если элемент находится в списке, то в среднем алгоритм проверяет N/2

элементов до того, как обнаружит искомый. Таким образом, в усредненном

случае время выполнения алгоритма также порядка O(N).

Хотя алгоритмы, которые выполняются за время порядка O(N), не являются

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

неплохие результаты. Для небольших списков этот алгоритм имеет приемлемую

производительность.

Поиск в упорядоченных списках

Если список упорядочен, то можно слегка модифицировать алгоритм полного

перебора, чтобы немного повысить его производительность. В этом случае,

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

большим, чем значение искомого элемента, то он завершает свою работу. При

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

раньше.

Например, предположим, что мы ищем значение 12 и дошли до значения 17. При

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

со значением 12, значит, элемент 12 в списке отсутствует. Следующий код

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

Public Function LinearSearch(target As Long) As Long

Dim i As Long

NumSearches = 0

For i = 1 To NumItems

NumSearches = NumSearches + 1

If List(i) >= target Then Exit For

Next i

If i > NumItems Then

LinearSearch = 0 ' Элемент не найден.

ElseIf List(i) <> target Then

LinearSearch = 0 ' Элемент не найден.

Else

LinearSearch = i ' Элемент найден.

End If

End Function

Эта модификация уменьшает время выполнения алгоритма, если элемент

отсутствует в списке. Предыдущей версии поиска требовалось проверить весь

список до конца, если искомого элемента в нем не было. Новая версия

остановится, как только обнаружит элемент больший, чем искомый.

Если искомый элемент расположен случайно между наибольшим и наименьшим

элементами в списке, то в среднем алгоритму понадобится порядка O(N) шагов,

чтобы определить, что искомый элемент отсутствует в списке. Время

выполнения при этом имеет тот же порядок, но на практике его

производительность будет немного выше. Программа Search использует эту

версию алгоритма.

======267

Поиск в связных списках

Поиск методом полного перебора — это единственный способ поиска в связных

списках. Так как доступ к элементам возможен только при помощи указателей

NextCell на следующий элемент, то необходимо проверить по очереди все

элементы с начала списка, чтобы найти искомый.

Так же, как и в случае поиска полным перебором в массиве, если список

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

большим, чем значение искомого элемента.

Public Function LListSearch(target As Long) As SearchCell

Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15


© 2010 Современные рефераты