Файл: Алгоритмизация как обязательный этап разработки программы (Преобразование Фурье).pdf

ВУЗ: Не указан

Категория: Курсовая работа

Дисциплина: Не указана

Добавлен: 04.04.2023

Просмотров: 95

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Важный момент: сортировка вставками обрабатывает элементы массива по порядку. Поскольку алгоритм проходит по элементам слева направо, мы знаем, что все, что слева от текущего индекса — уже отсортировано. На этом рисунке показано, как увеличивается отсортированная часть массива с каждым проходом:

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

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

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

3

7

4

4

6

5

8

Алгоритм начинает работу с индекса 0 и значения 3. Поскольку это первый индекс, массив до него включительно считается отсортированным.

Далее мы переходим к числу 7. Поскольку 7 больше, чем любое значение в отсортированной части, мы переходим к следующему элементу.

На этом этапе элементы с индексами 0..1 отсортированы, а про элементы с индексами 2..n ничего не известно.

Следующим проверяется значение 4. Так как оно меньше семи, мы должны перенести его на правильную позицию в отсортированную часть массива. Остается вопрос: как ее определить? Это осуществляется методом FindInsertionIndex. Он сравнивает переданное ему значение (4) с каждым значением в отсортированной части, пока не найдет место для вставки.

Итак, мы нашли индекс 1 (между значениями 3 и 7). Метод Insert осуществляет вставку, удаляя вставляемое значение из массива и сдвигая все значения, начиная с индекса для вставки, вправо. Теперь массив выглядит так:

3

4

4

7

6

5

8


Теперь часть массива, начиная от нулевого элемента и заканчивая элементом с индексом 2, отсортирована. Следующий проход начинается с индекса 3 и значения 4. По мере работы алгоритма мы продолжаем делать такие вставки.

3

4

4

5

6

7

8

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

Пример сортировки вставками:

public void Sort(T[] items)

{

int sortedRangeEndIndex = 1;

while (sortedRangeEndIndex < items.Length)

{

if (items[sortedRangeEndIndex].CompareTo(items[sortedRangeEndIndex - 1]) < 0)

{

int insertIndex = FindInsertionIndex(items, items[sortedRangeEndIndex]);

Insert(items, insertIndex, sortedRangeEndIndex);

}

sortedRangeEndIndex++;

}

}

private int FindInsertionIndex(T[] items, T valueToInsert)

{

for (int index = 0; index < items.Length; index++) {

if (items[index].CompareTo(valueToInsert) > 0)

{

return index;

}

}

throw new InvalidOperationException("The insertion index was not found");

}

private void Insert(T[] itemArray, int indexInsertingAt, int indexInsertingFrom)

{

// itemArray = 0 1 2 4 5 6 3 7

// insertingAt = 3

// insertingFrom = 6

//

// Действия:

// 1: Сохранить текущий индекс в temp

// 2: Заменить indexInsertingAt на indexInsertingFrom

// 3: Заменить indexInsertingAt на indexInsertingFrom в позиции +1

// Сдвинуть элементы влево на один.

// 4: Записать temp на позицию в массиве + 1.

// Шаг 1.

T temp = itemArray[indexInsertingAt];

// Шаг 2.

itemArray[indexInsertingAt] = itemArray[indexInsertingFrom];

// Шаг 3.

for (int current = indexInsertingFrom; current > indexInsertingAt; current--)

{

itemArray[current] = itemArray[current - 1];

}

// Шаг 4.

itemArray[indexInsertingAt + 1] = temp;

}

Сортировка выбором.

Сортировка выбором — это некий гибрид между пузырьковой и сортировкой вставками. Как и сортировка пузырьком, этот алгоритм проходит по массиву раз за разом, перемещая одно значение на правильную позицию. Однако, в отличие от пузырьковой сортировки, он выбирает наименьшее неотсортированное значение вместо наибольшего. Как и при сортировке вставками, упорядоченная часть массива расположена в начале, в то время как в пузырьковой сортировке она находится в конце.

Давайте посмотрим на работу сортировки выбором на нашем неотсортированном массиве.

3

4

7

4

6

5

8


При первом проходе алгоритм с помощью метода FindIndexOfSmallestFromIndex пытается найти наименьшее значение в массиве и переместить его в начало.

Имея такой маленький массив, мы сразу можем сказать, что наименьшее значение — 3, и оно уже находится на правильной позиции. На этом этапе мы знаем, что на первой позиции в массиве (индекс 0) находится самое маленькое значение, следовательно, начало массива уже отсортировано. Поэтому мы начинаем второй проход — на этот раз по индексам от 1 до n – 1.

На втором проходе мы определяем, что наименьшее значение — 4. Мы меняем его местами со вторым элементом, семеркой, после чего 4 встает на свою правильную позицию.

3

4

4

7

6

5

8

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

После еще двух проходов алгоритм завершает свою работу:

3

4

4

5

6

7

8

Пример сортировки выбором:

public void Sort(T[] items)

{

int sortedRangeEnd = 0;

while (sortedRangeEnd < items.Length)

{

int nextIndex = FindIndexOfSmallestFromIndex(items, sortedRangeEnd);

Swap(items, sortedRangeEnd, nextIndex);

sortedRangeEnd++;

}

}

private int FindIndexOfSmallestFromIndex(T[] items, int sortedRangeEnd)

{

T currentSmallest = items[sortedRangeEnd];

int currentSmallestIndex = sortedRangeEnd;

for (int i = sortedRangeEnd + 1; i < items.Length; i++)

{

if (currentSmallest.CompareTo(items[i]) > 0)

{

currentSmallest = items[i];

currentSmallestIndex = i;

}

}

return currentSmallestIndex;

}

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

До сих пор мы рассматривали линейные алгоритмы. Они используют мало дополнительной памяти, но имеют квадратичную сложность. На примере сортировки слиянием мы посмотрим на алгоритм типа «разделяй и властвуй» (divide and conquer).

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

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


Предположим, что в телефонной книге 1000 страниц. Если вы открываете ее на середине, вы отбрасываете 500 страниц, в которых нет искомого человека. Если вы не попали на нужную страницу, вы выбираете правую или левую сторону и снова оставляете половину доступных вариантов. Теперь вам надо просмотреть 250 страниц. Таким образом мы делим нашу задачу пополам снова и снова и можем найти человека в телефонной книге всего за 10 просмотров. Это составляет 1% от всего количества страниц, которые нам пришлось бы просмотреть при линейном поиске.

При сортировке слиянием мы разделяем массив пополам до тех пор, пока каждый участок не станет длиной в один элемент. Затем эти участки возвращаются на место (сливаются) в правильном порядке.

Давайте посмотрим на такой массив:

3

8

2

1

5

4

6

7

Разделим его пополам:

3

8

2

1

5

4

6

7

И далее будем делить пополам, пока не останутся части с одним элементом.

3

8

2

1

5

4

6

7

3

8

2

1

5

4

6

7

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

3

8

1

2

4

5

6

7

Сначала мы получаем группы по два отсортированных элемента, потом «собираем» их в группы по четыре элемента.

1

2

3

8

4

5

6

7


В конце собираем все вместе в отсортированный массив.

1

2

3

4

5

6

7

8

Для работы алгоритма мы должны реализовать следующие операции:

Операцию для рекурсивного разделения массива на группы (метод Sort).

Слияние в правильном порядке (метод Merge).

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

Пример сортировки слиянием:

public void Sort(T[] items)

{

if (items.Length <= 1)

{

return;

}

int leftSize = items.Length / 2;

int rightSize = items.Length - leftSize;

T[] left = new T[leftSize];

T[] right = new T[rightSize];

Array.Copy(items, 0, left, 0, leftSize);

Array.Copy(items, leftSize, right, 0, rightSize);

Sort(left);

Sort(right);

Merge(items, left, right);

}

private void Merge(T[] items, T[] left, T[] right)

{

int leftIndex = 0;

int rightIndex = 0;

int targetIndex = 0;

int remaining = left.Length + right.Length;

while(remaining > 0)

{

if (leftIndex >= left.Length)

{

items[targetIndex] = right[rightIndex++];

}

else if (rightIndex >= right.Length)

{

items[targetIndex] = left[leftIndex++];

}

else if (left[leftIndex].CompareTo(right[rightIndex]) < 0)

{

items[targetIndex] = left[leftIndex++];

}

else

{

items[targetIndex] = right[rightIndex++];

}

targetIndex++;

remaining--;

}

}

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

Быстрая сортировка — это еще один алгоритм типа «разделяй и властвуй». Он работает, рекурсивно повторяя следующие шаги:

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

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

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

Давайте посмотрим на работу алгоритма на следующем массиве:

3

7

4

4

6

5

8