Итак, все знают что есть такой вид сортировки, как пузырьковая. Меня интересует какие еще есть алгоритмы в этом виде сортировки кроме: последовательного и чет-нечетной перестановки? И если есть информация о них поделиться ей, если не жалко.
http://en.wikipedia.org/wiki/Odd-even_sort (так называемая "параллельная пузырьковая") интересует? Или ты ее и называешь чет-нечетной перестановкой? http://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D1%80%D0%B0%D1%81%D1%87%D1%91%D1%81%D0%BA%D0%BE%D0%B9 (вроде как тоже усовершенствование пузырька)?
Первая, действительно чет-нечет, а вот вторую не видел, спасибо. Нашел еще шейкер-сортировку
Просто нужно для курсового проекта рассмотреть варианты алгоритмов пузырьковой ,выбрать наилучший по трудоемкости для дальнейшего распараллеливания вычисления данным алгоритмом)))
Сортировка расческой (вроде как тоже усовершенствование пузырька) - что-то у меня она прооигрывает обычной сортировке. Вот как сделал
void combsort( double *pData, int DataSize )
{ double tmp;
int jump = DataSize;
bool swapped = true;
while (jump > 0 || swapped)
{
if (jump > 0)
jump = (jump / 1.25);
swapped = false;
for (int i = 0; i + jump < DataSize-1; i++)
if(pData[i] > pData[i + 1]) {
tmp = pData[i];
pData[i] = pData[i + 1];
pData[i + 1] = tmp;swapped = true;}
}
}
Ты б лучше обе реализации привел, чтоб сравнить можно было... Лучше всего - целиком программу, которая показывает, где больше, а где - меньше времени. А то так знаешь, все может зависеть от многих факторов...
P.S. Вот http://en.wikipedia.org/wiki/Comb_sort#C лежит реализация "расчески на С", на кой велосипед придумывать опять?
IUnknown, велосипед не придумывал выдернул код представленный с первой твоей ссылки))), но он оказался медленнее представленного тобой по последней ссылке.Походу по первой не совсем верный алгоритм.
Вот прога целиком
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <ctime>
using namespace std;
void RandomDataInitialization(double *&pData, int& DataSize) ;
void ProcessInitialization(double *&pData, int& DataSize) ;
void ProcessTermination(double *pData);
void DummyDataInitialization(double*& pData, int& DataSize);
void Shaker(double *pData, int DataSize);
void combsort( double *pData, int DataSize );
void PrintData(double *pData, int DataSize);
const double RandomDataMultiplier = 1000.0;
int main(int argc, char *argv[]) {
double *pData = 0;
int DataSize = 0;
time_t start, finish;
double duration = 0.0;
printf("Serial bubble sort program\n");
// Process initialization // В этой функции можно менять вид заполнения массива
ProcessInitialization(pData, DataSize);
//printf("Data before sorting\n");
//PrintData(pData, DataSize); // Для проверки исходных данных
// Serial bubble sort // Виды сортировки, обычная пузырьковая, расческой, Шейкера активация на выбор
start = clock();
SerialBubble(pData, DataSize);
//combsort(pData, DataSize);
//Shaker(pData, DataSize);
finish = clock();
//printf("Data after sorting\n");
//PrintData(pData, DataSize); //Для проверки результата
duration = (finish - start) / double(CLOCKS_PER_SEC);
printf("Time of execution: %f\n", duration);
// Process termination
ProcessTermination(pData);
return 0;
}
// Function for allocating the memory and setting the initial values
void ProcessInitialization(double *&pData, int& DataSize) {
do {
printf("Enter the size of data to be sorted: ");
scanf("%d", &DataSize);
if(DataSize <= 0)
printf("Data size should be greater than zero\n");
}
while(DataSize <= 0);
printf("Sorting %d data items\n", DataSize);
pData = new double[DataSize];
// Simple setting the data
//DummyDataInitialization(pData, DataSize);
// Setting the data by the random generator
RandomDataInitialization(pData, DataSize);
}
// Function for computational process termination
void ProcessTermination(double *pData) {
delete []pData;
}
// Function for simple setting the initial data
void DummyDataInitialization(double*& pData, int& DataSize) {
for(int i = 0; i < DataSize; i++)
pData[i] = DataSize - i;
}
// Function for initializing the data by the random generator
void RandomDataInitialization(double *&pData, int& DataSize) {
srand( (unsigned)time(0) );
for(int i = 0; i < DataSize; i++)
pData[i] = double(rand()) / RAND_MAX * RandomDataMultiplier;
}
// Serial bubble sort algorithm
void SerialBubble(double *pData, int DataSize) {
double Tmp;
for(int i = 1; i < DataSize; i++)
for(int j = 0; j < DataSize - i; j++)
if(pData[j] > pData[j + 1]) {
Tmp = pData[j];
pData[j] = pData[j + 1];
pData[j + 1] = Tmp;
}
}
void PrintData(double *pData, int DataSize) {
for(int i = 0; i < DataSize; i++)
printf("%7.4f ", pData[i]);
printf("\n");
}
void Shaker(double *pData, int DataSize){
double t;
bool exchange;
do {
exchange = false;
for(int i=DataSize-1; i > 0; --i) {
if(pData[i-1] > pData[i]) {
t = pData[i-1];
pData[i-1] = pData[i];
pData[i] = t;
exchange = true;
}
}
for(int i=1; i < DataSize; ++i) {
if(pData[i-1] > pData[i]) {
t = pData[i-1];
pData[i-1] = pData[i];
pData[i] = t;
exchange = true;
}
}
} while(exchange);
}
void combsort(double *pData, int DataSize) {
float shrink_factor = 1.247330950103979;
int gap = DataSize, swapped = 1, i; double swap;
while ((gap > 1) || swapped) {
if (gap > 1)
gap = gap / shrink_factor;
swapped = 0;
i = 0;
while ((gap + i) < DataSize) {
if (pData[i] - pData[i + gap] > 0) {
swap = pData[i];
pData[i] = pData[i + gap];
pData[i + gap] = swap;
swapped = 1;
}
++i;
}
}
}
Добавил чет-нечет и проводил эксперимент на сортировку 25000 элементов. Комбинированная всех уделала, разница во времени просто обалдеть можно.
Никак до конца не пойму применения чет-нечет алгоритма для параллельной обработки.
1 - сортировка на всех узлах
2-обмен соседних узлов (Везде приводиться один и тот же пример причем после 2 пункта в соседних процессах хранятся уже упорядоченный данные, а не просто обмененные.)
вот второй пункт не до конца понимаю, нигде в подробностях не описано. Как я понял, это объединения данных соседних узлов, сортировка этого объединенного массива и разбиение опять на две части.
Правильно ли я понимаю и где можно почитать в подробностях данного параллельного алгоритма?
Вот по http://siber.cankaya.edu.tr/ozdogan/GraduateParallelComputing.old/ceng505/node132.html разобраться сможешь?
Основа понятна вот этот участок не очень особенно по распределению элементов
/* This is the CompareSplit function */
74 CompareSplit(int nlocal, int *elmnts, int *relmnts, int *wspace,
75 int keepsmall)
76 {
77 int i, j, k;
78
79 for (i=0; i<nlocal; i++)
80 wspace[i] = elmnts[i]; /* Copy the elmnts array into the wspace array */
81
82 if (keepsmall) { /* Keep the nlocal smaller elements */
83 for (i=j=k=0; k<nlocal; k++) {
84 if (j == nlocal || (i < nlocal && wspace[i] < relmnts[j])) //<------Не въеду что то, как в словах это правильно озвучить
85 elmnts[k] = wspace[i++];
86 else
87 elmnts[k] = relmnts[j++];
88 }
89 }
90 else { /* Keep the nlocal larger elements */
91 for (i=k=nlocal-1, j=nlocal-1; k>=0; k--) {
92 if (j == 0 || (i >= 0 && wspace[i] >= relmnts[j])) //<--------------Естественно здесь тоже
93 elmnts[k] = wspace[i--];
94 else
95 elmnts[k] = relmnts[j--];
96 }
97 }
98 }
if (keepsmall) { /* Keep the nlocal smaller elements */
// Этот кусок упорядочивает в массив elmnts минимальные значения из elmnts и
// relmnts (которые получены от соседнего процесса) по возрастанию
// (сколько поместится в elmnts) Скажем, если было:
// elmnts = (27 39 54 74 90)
// relmnts = (22 38 50 65 69)
// то после выполнения нижеследующего куска кода
// elmnts = (22 27 38 39 50)
for (i=j=k=0; k<nlocal; k++) {
if (j == nlocal || (i < nlocal && wspace[i] < relmnts[j]))
elmnts[k] = wspace[i++];
else
elmnts[k] = relmnts[j++];
}
}
else { /* Keep the nlocal larger elements */
// Соответственно, этот кусок делает то же самое, но сохраняет nlocal максимальных
// элементов из совокупности elmnts и relmnts. Т.е., если
// elmnts = (16 18 47 62 70)
// relmnts = ( 1 13 23 29 92), то в результате
// elmnts = (29 47 62 70 92)
// в упорядоченном виде
for (i=k=nlocal-1, j=nlocal-1; k>=0; k--) {
if (j == 0 || (i >= 0 && wspace[i] >= relmnts[j]))
elmnts[k] = wspace[i--];
else
elmnts[k] = relmnts[j--];
}
}
if (keepsmall) { /* Keep the nlocal smaller elements */
// Этот кусок упорядочивает в массив elmnts минимальные значения из elmnts и
// relmnts (которые получены от соседнего процесса) по возрастанию
// (сколько поместится в elmnts) Скажем, если было:
// elmnts = (27 39 54 74 90)
// relmnts = (22 38 50 65 69)
// то после выполнения нижеследующего куска кода
// elmnts = (22 27 38 39 50)
for (i=j=k=0; k<nlocal; k++) {
if (j == nlocal || (i < nlocal && wspace[i] < relmnts[j]))
elmnts[k] = wspace[i++];
else
elmnts[k] = relmnts[j++];
}
}
else { /* Keep the nlocal larger elements */
// Соответственно, этот кусок делает то же самое, но сохраняет nlocal максимальных
// элементов из совокупности elmnts и relmnts. Т.е., если
// elmnts = (16 18 47 62 70)
// relmnts = ( 1 13 23 29 92), то в результате
// elmnts = (29 47 62 70 92)
// в упорядоченном виде
for (i=k=nlocal-1, j=nlocal-1; k>=0; k--) {
if (j == 0 || (i >= 0 && wspace[i] >= relmnts[j]))
elmnts[k] = wspace[i--];
else
elmnts[k] = relmnts[j--];
}
}
wspace[i--]//<---совокупность и elmnts и relmnts
В Сях можно явно передавать то, что Ада передаёт неявно - т.е. указатель на начало и границы подмассива.
Многозадачность на скольки ядрах смотрел?
Array_Size : constant Integer := Total_Elements / Processes_Count;Сможешь написать аналог на С без потерь на копирование?
type Int_Array is array (Natural range <>) of Integer;
subtype Sub_Array is Int_Array(0 .. Array_Size - 1);
-- ...
task body Sorter is
A : Int_Array(0 .. 2 * Array_Size - 1);
begin
accept Data (velmnts : Sub_Array; vrelmnts : Sub_Array) do
A(0 .. Array_Size - 1) := velmnts; -- Особенно интересует это ...
A(Array_Size .. 2 * Array_Size - 1) := vrelmnts; -- вот это ...
end Data;
if Keep_Small then -- Это дискриминант задачи ...
Sort(A);
else
Rev_Sort(A);
end if;
accept Request (Arr : out Sub_Array) do
Arr := A(0 .. Array_Size - 1); -- ... и вот это ...
end Request;
end Sorter;
Я так понял, тут фишка в том, что velmnts и vrelmnts могут храниться в совсем разных областях памяти и всё равно компилятор умеет убирать копирование?
Ну грубо говоря, можно написать класс "располовиненный массив" с перегруженным оператором []. Правда, это уже не совсем чистый Си, но на Си принцип тот же. А вообще, чё там Си, да хоть на ассемблере. Дизассембелирую твой код и посмотрю.
Другое дело, что одна и та же конструкция языка высокого уровня может в зависимости от контекста скомпилироваться в принципиально разные вещи. То есть если переписать этот пример на Си именно для располовиненного массива, то алгоритм пузырька изуродуется до неузнаваемости, и если мне понадобится подредактировать уже изуродованный алгоритм, то я тупо задолбаюсь. А если это "уродование" делает компилятор, сохраняя исходник в чистоте, то всё в порядке.
Я вот что не пойму. Ты хочешь сказать, что твой компилятор умеет анализировать всё, что будет делаться над массивом и сделать вывод о том, что его вообще есть смысл хранить по частям (отдельные половинки в разных областях памяти)? Что-то я не заметил у ГНАТа высокий уровень интеллекта (я даже не смог заставить заменить вызов функции подсчёта суммы элементов константного массива на значение). Или я не заметил какой-то простой нюанс? Или у тебя не ГНАТ, а коммерческий мегакомпилятор?
Вот что меня еще смущает , погонял сортировку алгоритмом расческой, так на моем ноуте Dual-Core 2.1 GHz . 2 гига ОЗУ, 100000 элементов сортирует показывает за 0.062 с. 30000000 примерно за 21 секунду.
Это реально? Просто что то сомнения гложат в этом направлении.
а так еще надумал что тогда при таких результатах можно для распараллеливания использовать чет-нечет алгоритм, а для сортировки на узлах расческой.
То есть у тебя получился ЛИНЕЙНЫЙ рост времени? Забавно. Думаю, дело в погрешности измерений.
IUnknown, случаем не знаешь иностранных ресурсов(в наших путного не нашел ничего), где можно посмотреть графическое ( в виде графиков) отражение трудоемкости методов сортировки и их алгоритмов?
У меня где-то был даже PDF, в котором сведены графики для некоторых алгоритмов сортировок (именно в параллельном варианте), вечером поищу...
IUnknown, заранее благодарю.
Еще вопрос такой. Ранее ты (если лучше на Вы, то скажи) давал ссылку на вот этот http://siber.cankaya.edu.tr/ozdogan/GraduateParallelComputing.old/ceng505/node132.html. не пойму вот что где тут цикл ,ведь по сути дела не один раз ведь нужно пройтись по четным и нечетным процессам или процессорам. Вижу только один раз сортировку(локальную), которая выполняется я так понимаю на всех узлах
qsort(elmnts, nlocal, sizeof(int), IncOrder);
for (i=0; i<npes-1; i++) {
58 if (i%2 == 1) /* Odd phase */
59 MPI_Sendrecv(elmnts, nlocal, MPI_INT, oddrank, 1, relmnts,
60 nlocal, MPI_INT, oddrank, 1, MPI_COMM_WORLD, &status);
61 else /* Even phase */
62 MPI_Sendrecv(elmnts, nlocal, MPI_INT, evenrank, 1, relmnts,
63 nlocal, MPI_INT, evenrank, 1, MPI_COMM_WORLD, &status);
А количесвто пересылок, выражается трудоемкостью алгоритма чет-нечет(n в квадрате) ?
Каждый процесс посылает свою часть данных всем остальным процессам, и получает в ответ их данные. Это было не совсем верно: каждый с каждым не взаимодействует, скажем, если работают 6 процессов - то смысла связывать 2 и 5-ый нет: второй связывается только с теми, с кем он граничит, т.е., с 1-ым и с 3-им. Также, как пятый - только с 4-ым и с 6-ым. Но вот эти действия повторяются по количеству запущенных процессов, чтобы изменения в старших процессах (тех, которые контролируют максимальные индексы исходного массива) через промежуточные процессы достигли младших (контролирующих минимальные индексы), и наоборот. То есть, количество пересылок зависит от количества параллельно выполняемых процессов.
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.1604&rep=rep1&type=pdf лежит PDF (165 Кб, англ.), о котором я говорил. Пока больше ничего вменяемого не нашлось.
IUnknown, огромное тебе спасибо за все. Очень помог.
Кстати, а вот с какого процесса начинается объединение блоков и их разделение.
например если если заполнить массив к убыванию 24 23 22 21 и так далее, а отсортировать естественно по возрастанию. Допустим имеем 24 числа, и 4 процессора. каждому по 6 чисел, 1 и 4 процессоры могут только взаимодействовать с 2 и 3 соответственно, тогда как 2 и 3 как с друг другом так и с ними, получается что будет 6 операций объединений и разделений или нет?
Во-первых, я чуть-чуть поправил свой предыдущий пост, см. выше.
Теперь касаемо твоего вопроса: да, будет 6 сообщений между процессами. И 12 пересортировок (после каждого обмена сообщениями каждый процесс пересортировывает данные у себя). Вот, я прогнал тут свой код на этих данных (добавил служебные сообщения, кто кому что передал и кто от кого что получил):Array :
24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
ID = 0 Even_Rank = 1 Accepted_From = 1
ID = 2 Even_Rank = 3 Accepted_From = 3
Sorter : ID = 0 Accepted_From = 1
Sorter : ID = 2 Accepted_From = 3
Sorter : ID = 1 Accepted_From = 0
Sorter : ID = 0 Accepted_From = -1
Sorter : ID = 3 Accepted_From = 2
ID = 1 Odd_Rank = 2 Accepted_From = 2
Sorter : ID = 2 Accepted_From = 1
Sorter : ID = 1 Accepted_From = 2
Sorter : ID = 3 Accepted_From = -1
ID = 0 Even_Rank = 1 Accepted_From = 1
Sorter : ID = 0 Accepted_From = 1
ID = 2 Even_Rank = 3 Accepted_From = 3
Sorter : ID = 0 Accepted_From = -1
Sorter : ID = 1 Accepted_From = 0
Sorter : ID = 2 Accepted_From = 3
Sorter : ID = 3 Accepted_From = 2
ID = 2 Odd_Rank = 1 Accepted_From = 1
Sorter : ID = 2 Accepted_From = 1
Sorter : ID = 1 Accepted_From = 2
Sorter : ID = 3 Accepted_From = -1
Result :
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24
При получении от (-1) пересортировки не производится, потому что фактически не было изменения данных, (-1) у меня - аналог MPI_PROC_NULL, так что его можно не считать. Остальных Sorter-ов ровно 12 штук. С какого процесса всё начинается - это без разницы: все процессы начинают работать одновременно, и пары процессов пересылают сообщения друг другу тоже одновременно (если ты запускаешь программу на машине с достаточным количеством ядер/процессоров).
Еще раз спасибо, разжевал и в рот положил можно сказать, теперь все ясно.