Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по МОИ (глава2).doc
Скачиваний:
8
Добавлен:
05.11.2018
Размер:
397.82 Кб
Скачать

Алгоритм 12.1. Нахождение k-го наименьшего элемента

Вход. Массив элементов Array[i], 1in, принадлежащих множеству, на котором установлен линейный порядок, и целое k, 1kn.

Выход. k-й наименьший элемент из массива Array.

Метод. В основе алгоритма лежит рекурсивная процедура, Selection (Выбор) заключающаяся в разбиении всей последовательности по некоторому элементу m на три подпоследовательности S1, S2 и S3 такие, что S1 содержит все элементы, меньшие m, S2 – все элементы, равные m, а S3 – все элементы, бóльшие m. Сосчитав количество элементов, находящееся в S1 и S2, можно определить, в какой из последовательностей необходимо далее искать k-й наименьший элемент, и, таким образом, свести задачу к задаче меньшей размерности.

Но чтобы получить линейный алгоритм, надо уметь за линейное время находить разбивающий элемент так, чтобы длина каждой из подпоследовательностей S1 и S2 была бы не больше фиксированной доли длины исходной последовательности. Здесь вся хитрость заключается в способе выбора разбивающего элемента m. Последовательность, заданная массивом разбивается на подпоследовательности по пять элементов в каждой. Каждая из подпоследовательностей упорядочивается, и из медиан этих подпоследовательностей составляется новая подпоследовательность M. Ввиду изложенного способа выбора M она содержит только n/5 элементов (x наибольшее целое число, содержащееся в x), и можно найти её медиану в пять раз быстрее, чем у последовательности из n элементов.

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

Основные составляющие программы нахождения k-го наименьшего элемента последовательности с использованием алгоритма 12.1 приведены ниже.

Программа 12.1 Нахождения k-го наименьшего элемента

// Программа использует порядковые статистики для выбора k-го минимального

// элемента в последовательности длины NUM

#include <stdlib.h>

#include <stdio.h>

#include <io.h>

#define NUM 124

int Selection(int array[], unsigned order, unsigned init, unsigned fin);

void main()

{

unsigned order, index;

int result, *array;

// В этом месте должна стоять программа ввода массива array и выбора order

result=Selection(array, order, 0, NUM-1);

if (result==-32767) printf("Error: order вне границ массива \n");

else printf("The %d-й минимальный элемент в массиве is %d\n", order, result);

delete [ ] array;

} // Конец main

int Selection(int array[], unsigned order, unsigned init, unsigned fin)

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

// array, order=k, массив задаётся своими границами init и fin.

unsigned min, max, num_med, indi, indj;

int *median, mean, temp;

if (fin<init) printf("Неправильное использование: init > fin \n");

if ((init>(order-1))||(fin<(order-1))) return -32767;

if ((fin-init+1)<25)

{

// В этом месте должна стоять функция сортировки короткого массива с числом

// элементов, меньшим 25, находящихся в большом массиве array между

// элементами с номерами init и fin

return (array[order-1]);

} else

{

min=init; max=init+4; indi=0;

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

// пяти элементов

median=new int[(fin-init+1)/5+1];

// Разбиение на группы по 5 элементов, сортировка элементов в группах и

// нахождение медианы в каждой группе

while (max<=fin)

{

// В этом месте должна стоять функция сортировки короткого массива из пяти

// элементов массива array, имеющих индексы между min и max

median[indi++]=array[min+2];

min+=5; max+=5;

}

num_med=indi;

mean=Selection(median, (num_med>>1)+1, 0, num_med-1);

delete [ ] median;

// Если разбиение массива array на три части даёт подмассивы, содержащие

// элементы меньшие, равные и бóльшие, чем mean соответственно

indi=init; indj=fin;

while(indi<=indj)

{

while ((array[indj]>=mean)&&(indj>=init)) indj--;

while ((array[indi]<mean)&&(indi<=fin)) indi++;

if (indi<indj)

{

// Поменять местами array[indi] и array[indj]

temp=array[indi]; array[indi++]=array[indj]; array[indj--]=temp;

}

}

if(indi>=order) return Selection(array, order, init, indi-1);

while (array[indi]==mean) indi++;

for (indj=indi+1; indj<=fin; indj++)

if(array[indj]==mean)

{

array[indj]=array[indi]; array[indi++]=mean;

}

if(indi>=order) return mean;

else return Selection(array, order, indi, fin);

}

} // Конец Selection

Некоторые пояснения к программе. Если размер входной последовательности относительно мал (не более 25 элементов), то гораздо проще отсортировать данную последовательность, например, методом взбалтывания, и выбрать её k-й элемент.

Основная описанная в алгоритме процедура применяется только в случае наличия достаточно большого количества элементов во входной последовательности. Тогда производится выбор разбивающего элемента как медианы медиан пятёрок элементов, на которые разбивается входная последовательность. Элементы внутри пятёрок сортируются, например, методом взбалтывания, и выбирается средний элемент их пятёрки (медиана). Данные медианы хранятся в динамическом массиве median. После этого к массиву median применяется та же процедура Selection, используемая для нахождения медианы этого массива m, которая и выбирается в качестве разбивающего элемента. Размер массива median равен n/5. После разбиения последовательности на три подпоследовательности S1 (состоящую из элементов, меньших m), S2 (состоящую из элементов, равных m) и S3 (состоящую из элементов, бóльших m), программа определяет в какой их данных подпоследовательностей будет находиться k-й элемент всей последовательности. Если он находится в S2, то работа заканчивается и программа возвращает результат, равный m. Если же искомый элемент находится в S1 или S3, то программа применяет процедуру Selection к соответствующей подпоследовательности. Количество элементов в каждой из подпоследовательностей S1 и S3 находится в пределах от n/4 до 3n/4 (убедитесь в этом самостоятельно). Поэтому сумма длин подпоследовательностей, к которым применяется процедура Selection на каждом шаге (массив median и подпоследовательность S1 или S3) будет меньше n. Поэтому исходный массив разбивается на пятёрки элементов. Некоторые другие «магические числа» (вместо 5) тоже годятся. Попробуйте найти их самостоятельно.

Теорема 8. Алгоритм 12.1 находит k-й наименьший элемент в n-элементной последовательности за время O(n).

Доказательство. Корректность алгоритма доказывается непосредственной индукцией по n, и эта часть доказательства остаётся в качестве упражнения. Пусть T(n) – время, затрачиваемое на выбор k-го наименьшего элемента из последовательности длины n. Длина последовательности M (median) не больше n/5, и поэтому рекурсивный вызов процедуры Selection(median, n/10, 0, n/5) в строке  для массива median занимает время, не большее T(n/5).

Каждая из последовательностей S1 и S3 имеет длину не более 3n/4. Поэтому рекурсивный вызов Selection в строке  или  занимает времени не более T(3n/4). Все остальные операторы тратят не более O(n) времени. Таким образом, для некоторых постоянных c и b

Несложно показать (докажите самостоятельно), что T(n)  20bn удовлетворяет последней системе. 

Теперь целесообразно рассмотреть среднее время, затрачиваемое на выбор k-го наименьшего элемента в последовательности из n элементов. Для дальнейшего рассмотрения этого вопроса потребуется определение транзитивного замыкания.

Определение. Транзитивным замыканием отношения R (см. раздел 2.1) называется такое отношение R+, что cR+d тогда и только тогда, когда существует последовательность истинных утверждений вида e1Re2, e2Re3, , em–1Rem, где m2, c=e1 и d=em.

Пусть теперь S={a1, a2,an} – множество из n различных элементов, а T – дерево решений какого-нибудь алгоритма для нахождения k-го наименьшего элемента в S. Каждый путь p в T определяет такое отношение Rp на S, что ai Rp aj, если два различных элемента ai и aj сравниваются в некотором узле, лежащем на p, и в результате этого сравнения либо ai < aj, либо ajai. Пусть – транзитивное замыкание отношения Rp. Образно говоря, если aiaj, то последовательность сравнений, представленная путём p, выясняет, что ai < aj, поскольку никакой элемент не сравнивается сам с собой.

Лемма 2. Если на пути p выясняется, что am является k-м наименьшим элементом в S, то для любого im, 1in, либо aiam, либо amai.

Доказательство. Допустим, что некоторый элемент au не связан с am соотношением . Докажем, что, поместив au либо перед, либо после am в линейном порядке, заданном на S, получим противоречие с предположением, что путь p правильно установил, что am является k-м наименьшим элементом в S. Пусть S1 ={ajajau} (где подобная запись означает: множество S1 состоит из элементов aj, для которых выполняется условие ajau), S2={ajauaj}, и в S3 входят остальные элементы из S. По предположению au и входят в am.

Если aj – произвольный элемент в S1 (соответственно в S2) и aiaj (соответственно ajai), то в силу транзитивности ai также принадлежит S1 (соответственно в S2). Следовательно, можно построить такой линейный порядок R, совместимый с ,что все элементы множества S1 предшествуют всем элементам из S3, которые в свою очередь предшествуют всем элементам из S2.

По предположению элемент au не связан отношением ни с одним элементом из S3. Допустим, что au предшествует am при линейном порядке R, т.е. auam. Тогда можно построить новый порядок R, совпадающий с R во всём, кроме того, что au следует непосредственно за am. Отношение R также совместимо с . Для каждого порядка R и R можно найти различные элементы, удовлетворяющие соответственно R или R. Но am не может быть k-м наименьшим элементом в обоих случаях, т.к. в R элементу am предшествует на один элемент меньше, чем в R. Поэтому заключаем, что если какой-то элемент из S не связан с am отношением , то дерево T не может правильно выбрать k-й наименьший элемент из S. Случай amau исследуется аналогично. 

Теорема 9. Если T – дерево решений, выбирающее k-й наименьший элемент в множестве S, ║S║=n, то глубина любого листа дерева T не меньше n–1. Следствием данной теоремы будет утверждение, что для нахождения k-го наименьшего элемента в S требуется не меньше n–1 сравнений, как в среднем, так и в худшем случае.

Доказательство. Рассмотрим путь p в T из корня к листу. По лемме 2 либо aiam, либо amai для каждого im, где am – элемент, выбранный в качестве k-го наименьшего. Для элемента ai im, определим ключевое сравнение как первое сравнение не p, содержащее ai и такое, что выполнено одно из условий:

  1. ai сравнивается с am,

  2. ai сравнивается с aj, ai Rp aj, и ajam,

  3. ai сравнивается с aj, aj Rp ai, и amaj.

Интуитивно ключевое сравнение для ai – это первое сравнение, из которого можно определить, предшествует элемент ai элементу am или следует за ним.

Понятно, что всякий элемент ai, кроме am, обладает сравнением; иначе не выполнилось бы ни aiam, ни amai. Далее легко видеть, что ни никакое сравнение не может быть ключевым для обоих сравниваемых элементов. Так как в ключевые сравнения должны вовлекаться n–1 элементов, то длина пути p должна быть не меньше n–1.

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