<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=KulikovM</id>
	<title>Алговики - Вклад [ru]</title>
	<link rel="self" type="application/atom+xml" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=KulikovM"/>
	<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/ru/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/KulikovM"/>
	<updated>2026-10-22T22:58:27Z</updated>
	<subtitle>Вклад</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30067</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30067"/>
		<updated>2025-11-23T23:34:26Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: /* Параллельная реализация */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
&lt;br /&gt;
== MPI код на Си с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Основные MPI операции:&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI.&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов.&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам.&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слиянии.&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
== Обоснование выбора MPI ==&lt;br /&gt;
MPI был выбран, так как «Ломоносов-2», на котором проводились прогоны, является кластером с распределенной памятью.&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30066</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30066"/>
		<updated>2025-11-23T23:34:01Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: /* MPI код с замерами времени и генерацией данных */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
&lt;br /&gt;
== MPI код на Си с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Основные MPI операции:&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI.&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов.&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам.&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слиянии.&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
== Обоснование выбора MPI ==&lt;br /&gt;
MPI был выбран, так как «Ломоносов-2», на котором проводились прогоны, является кластером с распределенной памятью.&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30065</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30065"/>
		<updated>2025-11-23T23:33:30Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: /* Параллельная реализация на С с помощью технологии MPI */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
&lt;br /&gt;
== MPI код с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Основные MPI операции:&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI.&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов.&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам.&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слиянии.&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
== Обоснование выбора MPI ==&lt;br /&gt;
MPI был выбран, так как «Ломоносов-2», на котором проводились прогоны, является кластером с распределенной памятью.&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30064</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30064"/>
		<updated>2025-11-23T23:17:33Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация на С с помощью технологии MPI ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== MPI код с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Основные MPI операции:&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI.&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов.&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам.&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слиянии.&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
== Обоснование выбора MPI ==&lt;br /&gt;
MPI был выбран, так как «Ломоносов-2», на котором проводились прогоны, является кластером с распределенной памятью.&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30063</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30063"/>
		<updated>2025-11-23T22:55:58Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: /* MPI код с замерами времени и генерацией данных */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация на С с помощью технологии MPI ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== MPI код с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Основные MPI операции:&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI.&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов.&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам.&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слиянии.&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30062</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30062"/>
		<updated>2025-11-23T22:50:59Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: /* MPI код с замерами времени и генерацией данных */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация на С с помощью технологии MPI ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== MPI код с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Основные MPI операции:&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI.&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов.&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам.&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слиянии.&lt;br /&gt;
MPI был выбран, так как «Ломоносов-2», на котором проводились прогоны(результаты приведены ниже) является кластером с распределенной памятью.&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30061</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30061"/>
		<updated>2025-11-23T22:50:02Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: /* MPI код с замерами времени и генерацией данных */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация на С с помощью технологии MPI ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== MPI код с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Основные MPI операции:&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI.&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов.&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам.&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слиянии.&lt;br /&gt;
MPI был выбран, так как «Ломоносов-2», на котором проводились прогоны(приведены ниже) является кластером с распределенной памятью.&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30060</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30060"/>
		<updated>2025-11-23T22:45:50Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: /* MPI код с замерами времени и генерацией данных */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация на С с помощью технологии MPI ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== MPI код с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Основные MPI операции:&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI.&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов.&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам.&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слияни.&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30059</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30059"/>
		<updated>2025-11-23T22:45:02Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация на С с помощью технологии MPI ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== MPI код с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Основные MPI операции:&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слияни&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30058</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30058"/>
		<updated>2025-11-23T22:41:39Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: /* MPI код с замерами времени и генерацией данных */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация на С с помощью технологии MPI ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== MPI код с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Основные MPI операции =&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слияни&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30057</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30057"/>
		<updated>2025-11-23T22:41:04Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: /* MPI код с замерами времени и генерацией данных */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация на С с помощью технологии MPI ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== MPI код с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
MPI операции:&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слияни&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30056</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30056"/>
		<updated>2025-11-23T22:40:17Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: /* MPI код с замерами времени и генерацией данных */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация на С с помощью технологии MPI ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== MPI код с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
MPI операции:&lt;br /&gt;
* MPI_Init/MPI_Finalize - инициализация/завершение MPI&lt;br /&gt;
&lt;br /&gt;
* MPI_Comm_rank/MPI_Comm_size - получение номера процесса и общего числа процессов&lt;br /&gt;
&lt;br /&gt;
* MPI_Scatter - распределение данных от процесса 0 ко всем процессам&lt;br /&gt;
&lt;br /&gt;
* MPI_Send/MPI_Recv - передача данных между процессами при слияни&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30055</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30055"/>
		<updated>2025-11-23T22:38:21Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: /* MPI код с замерами времени генерацией данных, использовавшийся в прогонах */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация на С с помощью технологии MPI ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== MPI код с замерами времени и генерацией данных ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30054</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30054"/>
		<updated>2025-11-23T22:36:00Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Параллельная реализация на С с помощью технологии MPI ==&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== MPI код с замерами времени генерацией данных, использовавшийся в прогонах ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mpi.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Функция слияния двух отсортированных массивов&lt;br /&gt;
int* merge(int *a, int na, int *b, int nb) {&lt;br /&gt;
    int *c = malloc((na + nb) * sizeof(int));&lt;br /&gt;
    int i = 0, j = 0, k = 0;&lt;br /&gt;
    &lt;br /&gt;
    // Слияние двух массивов в один отсортированный&lt;br /&gt;
    while (i &amp;lt; na &amp;amp;&amp;amp; j &amp;lt; nb) {&lt;br /&gt;
        if (a[i] &amp;lt; b[j]) c[k++] = a[i++];&lt;br /&gt;
        else c[k++] = b[j++];&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Добиваем остатки из первого массива&lt;br /&gt;
    while (i &amp;lt; na) c[k++] = a[i++];&lt;br /&gt;
    // Добиваем остатки из второго массива&lt;br /&gt;
    while (j &amp;lt; nb) c[k++] = b[j++];&lt;br /&gt;
    &lt;br /&gt;
    return c;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Рекурсивная сортировка слиянием&lt;br /&gt;
int* msort(int *arr, int n) {&lt;br /&gt;
    if (n &amp;lt;= 1) {&lt;br /&gt;
        int *r = malloc(n * sizeof(int));&lt;br /&gt;
        if (n == 1) r[0] = arr[0];&lt;br /&gt;
        return r;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // Делим массив пополам&lt;br /&gt;
    int mid = n / 2;&lt;br /&gt;
    // Сортируем левую часть рекурсивно&lt;br /&gt;
    int *left = msort(arr, mid);&lt;br /&gt;
    // Сортируем правую часть рекурсивно&lt;br /&gt;
    int *right = msort(arr + mid, n - mid);&lt;br /&gt;
    // Сливаем отсортированные части&lt;br /&gt;
    int *result = merge(left, mid, right, n - mid);&lt;br /&gt;
    &lt;br /&gt;
    // Освобождаем временные массивы&lt;br /&gt;
    free(left);&lt;br /&gt;
    free(right);&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(int argc, char *argv[]) {&lt;br /&gt;
    MPI_Init(&amp;amp;argc, &amp;amp;argv);&lt;br /&gt;
    &lt;br /&gt;
    int rank, size;&lt;br /&gt;
    MPI_Comm_rank(MPI_COMM_WORLD, &amp;amp;rank);  // Получаем номер процесса&lt;br /&gt;
    MPI_Comm_size(MPI_COMM_WORLD, &amp;amp;size);  // Получаем общее количество процессов&lt;br /&gt;
    &lt;br /&gt;
    // Проверка аргументов командной строки&lt;br /&gt;
    if (argc &amp;lt; 2 &amp;amp;&amp;amp; rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Usage: %s &amp;lt;array_size&amp;gt;\n&amp;quot;, argv[0]);&lt;br /&gt;
        MPI_Finalize();&lt;br /&gt;
        return 1;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    int n = atoi(argv[1]);          // Общий размер массива&lt;br /&gt;
    int local_n = n / size;         // Размер части на процесс&lt;br /&gt;
    int *data = NULL;               // Буфер для всего массива (только в процессе 0)&lt;br /&gt;
    int *local = malloc(local_n * sizeof(int));  // Локальная часть массива&lt;br /&gt;
    &lt;br /&gt;
    int i;&lt;br /&gt;
    int step;&lt;br /&gt;
    &lt;br /&gt;
    // Генерация данных в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        data = malloc(n * sizeof(int));&lt;br /&gt;
        for (i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
            data[i] = rand() % 1000000;  // Заполнение случайными числами&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double start = MPI_Wtime();  // Замер времени начала&lt;br /&gt;
    &lt;br /&gt;
    // Распределение данных между процессами (Scatter)&lt;br /&gt;
    MPI_Scatter(data, local_n, MPI_INT, local, local_n, MPI_INT, 0, MPI_COMM_WORLD);&lt;br /&gt;
    &lt;br /&gt;
    if (rank == 0) free(data);  // Освобождаем исходный массив в процессе 0&lt;br /&gt;
    &lt;br /&gt;
    // Локальная сортировка - каждый процесс сортирует свою часть&lt;br /&gt;
    int *sorted_local = msort(local, local_n);&lt;br /&gt;
    free(local);&lt;br /&gt;
    &lt;br /&gt;
    int *result = sorted_local;  // Результирующий массив процесса&lt;br /&gt;
    int result_size = local_n;   // Размер результата&lt;br /&gt;
    &lt;br /&gt;
    // Иерархическое слияние - кооперативная операция между процессами&lt;br /&gt;
    for (step = 1; step &amp;lt; size; step *= 2) {&lt;br /&gt;
        if (rank % (2 * step) == 0) {&lt;br /&gt;
            // Процесс-получатель: получает данные от партнера и сливает&lt;br /&gt;
            if (rank + step &amp;lt; size) {&lt;br /&gt;
                int partner_size;&lt;br /&gt;
                // Получаем размер данных от партнера&lt;br /&gt;
                MPI_Recv(&amp;amp;partner_size, 1, MPI_INT, rank + step, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                int *partner_data = malloc(partner_size * sizeof(int));&lt;br /&gt;
                // Получаем сами данные от партнера&lt;br /&gt;
                MPI_Recv(partner_data, partner_size, MPI_INT, rank + step, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);&lt;br /&gt;
                &lt;br /&gt;
                // Сливаем свой результат с данными партнера&lt;br /&gt;
                int *merged = merge(result, result_size, partner_data, partner_size);&lt;br /&gt;
                free(result);&lt;br /&gt;
                free(partner_data);&lt;br /&gt;
                &lt;br /&gt;
                result = merged;&lt;br /&gt;
                result_size += partner_size;&lt;br /&gt;
            }&lt;br /&gt;
        } else if (rank % step == 0) {&lt;br /&gt;
            // Процесс-отправитель: отправляет свои данные и завершает работу&lt;br /&gt;
            MPI_Send(&amp;amp;result_size, 1, MPI_INT, rank - step, 0, MPI_COMM_WORLD);&lt;br /&gt;
            MPI_Send(result, result_size, MPI_INT, rank - step, 1, MPI_COMM_WORLD);&lt;br /&gt;
            free(result);&lt;br /&gt;
            break;  // Завершаем цикл для этого процесса&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    double end = MPI_Wtime();  // Замер времени окончания&lt;br /&gt;
    &lt;br /&gt;
    // Вывод результата в процессе 0&lt;br /&gt;
    if (rank == 0) {&lt;br /&gt;
        printf(&amp;quot;Processes: %d, Elements: %d, Time: %.6f seconds\n&amp;quot;, size, n, end - start);&lt;br /&gt;
        free(result);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    MPI_Finalize();&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30053</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30053"/>
		<updated>2025-11-23T22:31:32Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
= Параллельная реализация на С с помощью технологии MPI =&lt;br /&gt;
Иерархия параллелизма:&lt;br /&gt;
# Распределение данных между MPI-процессами&lt;br /&gt;
&lt;br /&gt;
# Независимая локальная сортировка в каждом процессе&lt;br /&gt;
&lt;br /&gt;
# Рекурсивное слияние внутри локальной сортировки&lt;br /&gt;
Оценка сложности:&lt;br /&gt;
* Вычисления: O((n/p)log(n/p)) на процесс + O(n) на слияния&lt;br /&gt;
&lt;br /&gt;
* Коммуникации: O(log p) шагов, O(n) общий объем пересылок&lt;br /&gt;
&lt;br /&gt;
* Память: O(n/p) на процесс + временные буферы&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30052</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=30052"/>
		<updated>2025-11-23T22:29:51Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
= Параллельная реализация на С с помощью технологии MPI =&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=29800</id>
		<title>Участник:KulikovM</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:KulikovM&amp;diff=29800"/>
		<updated>2025-10-28T00:36:49Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: Новая страница: « = Свойства и структура алгоритма =  == Общее описание == Сортировка слиянием упорядочивает...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= Свойства и структура алгоритма =&lt;br /&gt;
&lt;br /&gt;
== Общее описание ==&lt;br /&gt;
Сортировка слиянием упорядочивает набор элементов вида &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; (&#039;&#039;&#039;1 ≤ i ≤ n&#039;&#039;&#039;) путём рекурсивного деления набора на две части и последующего слияния отсортированных половин.&lt;br /&gt;
В последовательно-параллельном варианте две рекурсивные ветви (левая и правая) выполняются параллельно, а операция &#039;&#039;&#039;merge&#039;&#039;&#039; в каждом узле выполняется последовательно.&lt;br /&gt;
&lt;br /&gt;
== Математическое описание ==&lt;br /&gt;
Пусть n ≥ 2. Набор элементов &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; делится на две части:&lt;br /&gt;
* левая часть: элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039;, где &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* правая часть: элементы &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;, где &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Затем рекурсивно сортируются обе части и выполняется операция слияния.&lt;br /&gt;
&lt;br /&gt;
Базовый случай:&lt;br /&gt;
* если n = 1, набор считается уже отсортированным.&lt;br /&gt;
&lt;br /&gt;
=== Операция слияния (merge) ===&lt;br /&gt;
Операция &#039;&#039;&#039;merge&#039;&#039;&#039; объединяет два упорядоченных диапазона элементов в один общий отсортированный набор.&lt;br /&gt;
&lt;br /&gt;
В процессе слияния используются три индекса:&lt;br /&gt;
* &#039;&#039;&#039;i&#039;&#039;&#039; — пробегает левую часть, &#039;&#039;&#039;1 ≤ i ≤ ⌊n/2⌋&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;j&#039;&#039;&#039; — пробегает правую часть, &#039;&#039;&#039;⌊n/2⌋ + 1 ≤ j ≤ n&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;k&#039;&#039;&#039; — позиция результирующего набора, &#039;&#039;&#039;1 ≤ k ≤ n&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Алгоритм слияния (по умолчанию — &#039;&#039;&#039;неубывание&#039;&#039;&#039;):&lt;br /&gt;
# сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039;;&lt;br /&gt;
# меньший из них записывается в &#039;&#039;&#039;Aₖ&#039;&#039;&#039;;&lt;br /&gt;
# затем увеличивается индекс (&#039;&#039;i&#039;&#039; или &#039;&#039;j&#039;&#039;) и индекс &#039;&#039;k&#039;&#039;;&lt;br /&gt;
# когда один диапазон завершается, оставшиеся элементы второго дописываются в конец результирующего набора.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Невозрастание:&#039;&#039;&#039; в шагах сравнения выше используйте знак &#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039; (то есть выбирайте больший элемент).  &lt;br /&gt;
*&#039;&#039;&#039;Стабильность:&#039;&#039;&#039; при равенстве значений сначала берите элемент из левой части — это сохраняет порядок равных элементов.&lt;br /&gt;
&lt;br /&gt;
== Вычислительное ядро ==&lt;br /&gt;
Вычислительное ядро алгоритма состоит из последовательных слияний на каждом шаге рекурсии, в которых поочерёдно сравниваются элементы &#039;&#039;&#039;Aᵢ&#039;&#039;&#039; и &#039;&#039;&#039;Aⱼ&#039;&#039;&#039; и формируется результирующая упорядоченная последовательность в позиции &#039;&#039;&#039;Aₖ&#039;&#039;&#039;.&lt;br /&gt;
Для невозрастания меняется только знак в сравнении (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Макроструктура ==&lt;br /&gt;
Макроструктурно алгоритм представляется в виде бинарного дерева рекурсивных вызовов:&lt;br /&gt;
* на каждом уровне дерево делит исходный набор элементов на две части,&lt;br /&gt;
* каждая ветвь обрабатывается независимо (возможен параллелизм),&lt;br /&gt;
* на обратном ходе рекурсии пары отсортированных фрагментов объединяются посредством слияния.&lt;br /&gt;
&lt;br /&gt;
Таким образом:&lt;br /&gt;
* последовательность вычислений на нисходящем этапе — разбиение набора по индексам,&lt;br /&gt;
* последовательность вычислений на восходящем этапе — поэлементное слияние через сравнение Aᵢ и Aⱼ с записью результата в Aₖ.&lt;br /&gt;
&lt;br /&gt;
== Схема последовательной реализации ==&lt;br /&gt;
В последовательной реализации рекурсивная сортировка левой части выполняется полностью,&lt;br /&gt;
затем сортируется правая часть, и лишь после этого вызывается операция merge.&lt;br /&gt;
Для невозрастания меняется только знак сравнения в merge (&#039;&#039;&#039;≥&#039;&#039;&#039; вместо &#039;&#039;&#039;≤&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Последовательная сложность ==&lt;br /&gt;
Общая сложность определяется затратами на:&lt;br /&gt;
* разбиение на две части (O(1)),&lt;br /&gt;
* сортировку левой части O((n/2) log(n/2)),&lt;br /&gt;
* сортировку правой части O((n/2) log(n/2)),&lt;br /&gt;
* слияние (линейный проход: k от 1 до n ⇒ O(n)).&lt;br /&gt;
&lt;br /&gt;
Итого:&lt;br /&gt;
&#039;&#039;&#039;T(n) = O(n log n)&#039;&#039;&#039; по времени и &#039;&#039;&#039;O(n)&#039;&#039;&#039; дополнительная память под результирующую перестановку.&lt;br /&gt;
&lt;br /&gt;
== Информационный граф ==&lt;br /&gt;
Информационный граф представляет собой дерево:&lt;br /&gt;
[[Файл:Граф сортировки слиянием.png|мини|центр|Информационный граф сортировки слиянием (n=8)]]&lt;br /&gt;
&lt;br /&gt;
== Ресурс параллелизма ==&lt;br /&gt;
Параллелизм достигается за счёт независимой обработки двух половин набора.&lt;br /&gt;
На уровне слияния параллелизм отсутствует (последовательное сравнение Aᵢ и Aⱼ → Aₖ).&lt;br /&gt;
Высота параллельной части определяется глубиной рекурсии: O(log n).&lt;br /&gt;
&lt;br /&gt;
== Вход/выход ==&lt;br /&gt;
# Вход: массив &#039;&#039;&#039;A&#039;&#039;&#039; из &#039;&#039;&#039;n&#039;&#039;&#039; сравнимых элементов.  &lt;br /&gt;
# Выход: тот же массив, отсортированный по &#039;&#039;&#039;неубыванию&#039;&#039;&#039; (по умолчанию) либо по &#039;&#039;&#039;невозрастанию&#039;&#039;&#039; (если в merge поменять &#039;&#039;&#039;≤&#039;&#039;&#039; на &#039;&#039;&#039;≥&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
== Свойства ==&lt;br /&gt;
* Стабильный алгоритм (равные элементы сохраняют порядок при выборе элемента из левой части при равенстве).&lt;br /&gt;
* Хорошо подходит для больших массивов.&lt;br /&gt;
* Естественно поддерживает распараллеливание рекурсивной части.&lt;br /&gt;
= Программная реализация алгоритма =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Последовательная реализация (псевдокод) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure MergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
    MergeSort(A, l, m)&lt;br /&gt;
    MergeSort(A, m+1, r)&lt;br /&gt;
    Merge(A, l, m, r)            // слияние двух отсортированных фрагментов&lt;br /&gt;
&lt;br /&gt;
// Слияние по неубыванию (для невозрастания заменить ≤ на ≥)&lt;br /&gt;
procedure Merge(A, l, m, r):&lt;br /&gt;
    i := l           // индекс в левой части&lt;br /&gt;
    j := m + 1       // индекс в правой части&lt;br /&gt;
    k := 0           // индекс во временном буфере B[0 .. r-l]&lt;br /&gt;
    B := new array of size (r - l + 1)&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m and j ≤ r:&lt;br /&gt;
        if A[i] ≤ A[j]:          // &amp;lt;-- для невозрастания здесь использовать A[i] ≥ A[j]&lt;br /&gt;
            B[k] := A[i]; i := i + 1&lt;br /&gt;
        else&lt;br /&gt;
            B[k] := A[j]; j := j + 1&lt;br /&gt;
        k := k + 1&lt;br /&gt;
&lt;br /&gt;
    while i ≤ m:  B[k] := A[i]; i := i + 1; k := k + 1&lt;br /&gt;
    while j ≤ r:  B[k] := A[j]; j := j + 1; k := k + 1&lt;br /&gt;
&lt;br /&gt;
    // копирование результата назад&lt;br /&gt;
    for t := 0 .. (r - l):&lt;br /&gt;
        A[l + t] := B[t]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Параллельная реализация (последовательно-параллельная) ==&lt;br /&gt;
Псевдокод:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedure PMergeSort(A, l, r):&lt;br /&gt;
    if r - l + 1 &amp;lt;= 1: return&lt;br /&gt;
    m := (l + r) // 2&lt;br /&gt;
&lt;br /&gt;
    parallel:&lt;br /&gt;
        PMergeSort(A, l,   m)     // ветвь 1&lt;br /&gt;
        PMergeSort(A, m+1, r)     // ветвь 2&lt;br /&gt;
    join&lt;br /&gt;
&lt;br /&gt;
    Merge(A, l, m, r)             // слияние — последовательное&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Результаты прогонов (типичное поведение) ==&lt;br /&gt;
&lt;br /&gt;
= Литература и источники =&lt;br /&gt;
* Н. Вирт. &#039;&#039;Алгоритмы и структуры данных&#039;&#039;. § 2.4 «Сортировка последовательностей» (с. 97). [[:File:Wirth_2010_Алгоритмы_и_структуры_данных.pdf#page=97|PDF (стр. 97)]]&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D1%80%D0%B0%D1%84_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8_%D1%81%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5%D0%BC.png&amp;diff=29799</id>
		<title>Файл:Граф сортировки слиянием.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D1%80%D0%B0%D1%84_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8_%D1%81%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5%D0%BC.png&amp;diff=29799"/>
		<updated>2025-10-27T22:29:12Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Это граф&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D1%80%D0%B0%D1%84.jpg&amp;diff=29798</id>
		<title>Файл:Граф.jpg</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%93%D1%80%D0%B0%D1%84.jpg&amp;diff=29798"/>
		<updated>2025-10-27T22:11:21Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Граф&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D1%8B%D0%B9_%D0%B3%D1%80%D0%B0%D1%84_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8_%D1%81%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5%D0%BC_(n%3D8).png&amp;diff=29797</id>
		<title>Файл:Информационный граф сортировки слиянием (n=8).png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D1%8B%D0%B9_%D0%B3%D1%80%D0%B0%D1%84_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8_%D1%81%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5%D0%BC_(n%3D8).png&amp;diff=29797"/>
		<updated>2025-10-27T22:05:57Z</updated>

		<summary type="html">&lt;p&gt;KulikovM: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Информационный граф сортировки слиянием (n=8)&lt;/div&gt;</summary>
		<author><name>KulikovM</name></author>
	</entry>
</feed>