JavaRush /Java блог /Random /Сортировка слиянием Merge-sort в Java
vinsler
35 уровень

Сортировка слиянием Merge-sort в Java

Статья из группы Random
Каждый программист изначально должен продумать схему/план/архитектуру будущей работы, иначе все это вываливается в кашу, с полной анархией. Как и в любом своем посте, изначально нужен план, приступим.
  • 1) Возьмем тему сортировки слиянием Merge для новичков.
  • 2) Создадим архитектуру, план по дальнейшей работе.
  • 3) Проработаем и опишем все части плана.
  • 4) Проверим работоспособность и качество.
  • 2.1) Что такое сортировка Merge.
  • 2.2) Описание возможных сигнатур.
  • 2.3) Привести пример.
  • 2.4) На примере описать реализацию на Java.
  • 2.5) Что-нибудь дополнительно.
Сортировка слиянием Merge-sort - 1

Merge — сортировка слиянием в Java

Подразумевает принцип "разделяй и властвуй". В чем идея и ее смысл?
  1. Сортировка.

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

  2. Слияние.

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

Затрачиваемое время O(n log2 n). Сортировка считается достаточно быстрой. Кратко про алгоритмическую сложность, если кому-то нужно Часто можно увидеть функции типа:

Sort(A, p, q);
Merge(A, p, q, r);
Это примерно то же самое, только привязано на индексы. Переменные в них — это:

A = Array = Массив
p, q, r = индексы для массива
p - начало первого массива
q - конец первого массива
q + 1 - начало второго массива
r - конец второго массива
Если эти переменные не описаны, значит тот, кто просит сделать такую функцию сам, не знает чего хочет. А вам придется лопатить гугл в поисках, что же это такое, наверное найдете, может быть. Приведем пример к нашей сортировке. Есть массив {6, 1, 3, 5, 2, 4, 7, 8}, если его длинна больше 1, то мы делим его на 2 части и получаем левую часть {6, 1, 3, 5} и правую часть {2, 4, 7, 8}. Продолжаем действие деления на 2 части, пока его длинна будет больше 1. В итоге получим кучу массивов длинной в 1 элемент, а именно: {6} {1} {3} {5} {2} {4} {7} {8}. Реализация на Java примерно следующая:

public int [] sortArray(int[] arrayA){ // сортировка Массива который передается в функцию
        // проверяем не нулевой ли он?
        if (arrayA == null) {
            return null;
        }
        // проверяем не 1 ли элемент в массиве?
        if (arrayA.length < 2) {
            return arrayA; // возврат в рекурсию в строки ниже см комменты.
        }
        // копируем левую часть от начала до середины
        int [] arrayB = new int[arrayA.length / 2];
        System.arraycopy(arrayA, 0, arrayB, 0, arrayA.length / 2);

        // копируем правую часть от середины до конца массива, вычитаем из длины первую часть
        int [] arrayC = new int[arrayA.length - arrayA.length / 2];
        System.arraycopy(arrayA, arrayA.length / 2, arrayC, 0, arrayA.length - arrayA.length / 2);

        // рекурсией закидываем поделенные обе части обратно в наш метод, он будет крутится до тех пор,
        // пока не дойдет до 1 элемента в массиве, после чего вернется в строку и будет искать второй такой же,
        // точнее правую часть от него и опять вернет его назад
        arrayB = sortArray(arrayB); // левая часть возврат из рекурсии строкой return arrayA;
        arrayC = sortArray(arrayC); // правая часть возврат из рекурсии строкой return arrayA;

        // далее опять рекурсия возврата слияния двух отсортированных массивов
        return mergeArray(arrayB, arrayC);
    }
Далее нужно слить эти массивы в 1. Как это делается? Чтобы не проходить многократно по каждому массиву, введем индексы позиции для каждого массива. После чего один раз пройдем по циклу, равному длине суммы этих двух массивов. Берется первый массив и второй массив, и берется первый элемент, сравнивается больше элемент номер 1 в первом массиве и элемент номер 1 во втором массиве? Меньший из них кладется в результирующий массив. Тут важно, если мы взяли элемент из первого массива, то при переходе цикла, он должен ссылаться на 2 элемент первого массива и на 1 элемент второго массива. Для этого нужно увеличить индекс второго массива на +1 и при проверке вычитать его из номера цикла, аналогично и для первого массива. Понятно для чего это делать? Или вообще ничего не понятно? :-) Например есть 2 массива: {1}{4}{8} и {3}{6}{7} И есть цикл:

for (int i = 0; i < arrayA.length + arrayB.length; i++) {
	if (arrayA[i] < arrayB[i]) {
	arrayC[i] = arrayA[i];
	} else {
	arrayC[i] = arrayB[i];
	}
}
При первом проходе цикла получится что arrayC[1] = {1}: мы взяли этот элемент из первого массива. То при проходе по второму циклу, мы уже должны сравнивать элемент {4} и {3}, но чтобы это сделать нам нужны учитывать индексы позиций и смещение обоих массивов, для этого вводим их.

int positionA = 0, positionB = 0;
for (int i = 0; i < arrayA.length + arrayB.length; i++) {
	if (arrayA[i - positionA] < arrayB[i - positionB]) {
	arrayC[i] = arrayA[i - positionA];
	positionB++;
	} else {
	arrayC[i] = arrayB[i - positionB];
	positionA++;
	}
}
Но это еще не все, нужно учесть, что какой-то массив может закончиться раньше. Например есть 3 массива: {1}{3}{5} и {6}{7}{9} Первый массив закончится еще до того, как подойдет второй, для этого нужно ввести проверку и, в приципе, готова функция слияния.

public int [] mergeArray(int [] arrayА, int [] arrayB) {

int [] arrayC = int[arrayA.length + arrayB.length];
int positionA = 0, positionB = 0;

for (int i = 0; i < arrayC.length; i++) {
	if (positionA == arrayA.length){
	arrayC[i] = arrayB[i - positionB];
	positionB++;
	} else if (positionB == arrayB.length) {
	arrayC[i] = arrayA[i - positionA];
	positionA++;
	} else if (arrayA[i - positionA] < arrayB[i - positionB]) {
	arrayC[i] = arrayA[i - positionA];
	positionB++;
	} else {
	arrayC[i] = arrayB[i - positionB];
	positionA++;
	}
}
return arrayC;
Самое тяжелое в этой сортировке — это принцип перехода рекурсии. Т.е. мы закидываем в рекурсию левую часть до тех пор, пока она делится на 2, а потом раскручиваем ее обратно, на словах это очень сложно и запутанно, а когда пытаешься представить, если еще и не понятно, то совсем лес. Берем массив: {2}{1}{4}{3}. Первая рекурсия сортировки поделит его на 2 части и запустит функцию еще раз с элементами 2-1, потом еще раз с элементом 2 и 1, вернет их по очереди, таким образом в функцию слияния попадут первыми они, а выйдут уже 1-2, потом рекурсия вернется обратно и закинет в слияние уже 4-3, потом 4 и 3, после чего слияние вернет 3-4, а уже потом рекурсия раскрутится еще раз обратно и в слияние попадет уже 1-2 и 3-4, а вернется отсортированный массив 1-2-3-4. Ну вот в целом и все, сортировка состоит из двух функций.

sortArray(array); 			// кладем массив который нужно отсортировать
mergeArray(arrayA, arrayB); 	// кладем 2 массива которые нужно слить в один
Если записать какой-то мейн, получится нечто типа:

public static void main(String[] args) {
        Merge testMerge = new Merge();
        int [] result = testMerge.sortArray(new int[]{2,3,1,4});

        for (int i = 0; i < result.length ; i++) {
            System.out.print(result[i] + " ");
        }
    }
Для меня эта сортировка была полным провалом, вопросов было миллион, а ответов нет, я перекопал весь интернет, перечитал, пересмотрел кучу видео, но как и всегда, ответы нашел только сам. И только, когда начал писать решение совсем другое, нежели которое мелькает везде) А в итоге вышло похоже на все остальные))) Сортировка на самом деле простейшая, главное представить интерактивно ее в действии, и все становится на свои места, если руки дойдут, сделаю видео)))) Пока что это все, на что меня хватило: Сортировка слиянием Merge-sort Самое главное, — всегда делайте план изначально. Лучше немного подождать и подумать, прежде чем начать что-то делать. Пусть на это уйдет больше времени, но появится понимание и путь решения, чем придется переписывать пару раз и придумывать костыли. Всем спасибо, что уделили время, удачи и хорошего настроения. ) PS: Критика, хорошее и плохое, а так же вопросы очень приветствуются. )))
Комментарии (45)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Сергей Сак Уровень 3
10 апреля 2024
Автор вообще свой код проверял, прежде чем сюда его откуда-то скопипастить? Куча ошибок, логика в коде частично неверная, код нерабочий
Anonymous #3352746 Уровень 19
15 декабря 2023
Вот, для простоты понимания:

class MyClass {

    public static void main(String[] args) {
        System.out.println(Arrays.toString(split(new int[] {3,2,1})));
    }

    public static int[] split(int[] array) {
        if (array.length == 1) return array;
        if (array.length == 2) return mergeArrays(new int[] {array[0]}, new int[] {array[1]});

        int[] left = new int[array.length / 2];
        int[] right = new int[array.length - array.length / 2];

        System.arraycopy(array, 0, left, 0, left.length);
        System.arraycopy(array, left.length, right, 0, right.length);

        return mergeArrays(split(left), split(right));
    }

    public static int[] mergeArrays(int[] a1, int[] a2) {
        int[] a = new int[a1.length + a2.length];
        int i = 0, i1 = 0, i2 = 0;

        while (i1 < a1.length && i2 < a2.length) a[i++] = a1[i1] < a2[i2] ? a1[i1++] : a2[i2++];
        while (i1 < a1.length) a[i++] = a1[i1++];
        while (i2 < a2.length) a[i++] = a2[i2++];
        return a;
    }
}


Тут не в щепки раскалываем (до одного элемента в массиве), а до одного или двух. Сам пока не пойму зачем до одного колоть, если (когда осталось два) их можно вручную отправить на сравнение, не вызывая лишнюю итерацию рекурсии. Кому интересна суть работы рекурсии: допустим массив {5, 4, 3, 2, 1}, работать будет так: merge (merge (5, 4), merge (3, merge (2, 1))); merge (2, 1) дает в результате 1, 2 merge (3 и 1, 2) дает 1, 2, 3 merge (5, 4) - соотв 4,5 ну и главный merdge (4, 5 и 1, 2, 3) дает 1,2,3,4,5 - вот и весь принцип размотки рекурсии.
Jknock Garon Уровень 2
10 июня 2023
Мне попалось задание реализовать метод Merge с такими параметрами, но я хоть убей не могу понять как это сделать. Вроде для слияния нужны 2 массива? У меня не получается придумать, может кто подсказать реализацию такого метода слияния, когда передается 1 массив или я что то не понимаю? Sort(A, p, q); Merge(A, p, q, r);
Shahin Уровень 0
30 октября 2022
Имеется 2 Массива: A={1,4,8} и B={3,6,7}. В функции mergeArray во время прохода по циклу for при i = 5 остаётся один элемент массива A,а именно "8". Какая строка кода функции mergeArray закидывает оставшуюся 8-ку массива A в массив С???
22 декабря 2021
public static int[] mergeArrays(int[] a1, int[] a2) { int[] res = new int[a1.length + a2.length]; int n = a1.length; int m = a2.length; int i = 0, j = 0, k = 0; while (i < n && j < m) { if (a1[i] <= a2[j]) { res[k] = a1[i]; i++; } else { res[k] = a2[j]; j++; } k++; } while (i < n) { res[k] = a1[i]; i++; k++; } while (j < m) { res[k] = a2[j]; j++; k++; } return res; }
Богдан Мельник Уровень 0
6 мая 2021
Вот, что у меня получилось: https://github.com/sldstrst/sort_merge Писал самостоятельно. Сначала попробовал вставить код из этой статьи и разобрать, потом понял, что это просто не запускается. Быстрее всё самому разобрать и понять. В моей реализации вам надо: 1. Создать главный класс Main, 2. Создать интерфейс Sorted 3. Запихнуть туда всё, что лежит у меня из кода. Останется только разобрать что за чем идёт. Код полностью рабочий.
Maksim Уровень 19
7 марта 2021
public static int[] sortArray(int[] array) { if (array == null) { return null; } if (array.length < 2) { return array; } int[] arrayB = new int[array.length / 2]; System.arraycopy(array, 0, arrayB, 0, array.length / 2); int[] arrayC = new int[array.length - arrayB.length]; System.arraycopy(array, arrayB.length, arrayC, 0, array.length - arrayB.length); sortArray(arrayB); sortArray(arrayC); mergeArray(array, arrayB, arrayC); return array; } private static void mergeArray(int[] array, int[] arrayB, int[] arrayC) { int positionB = 0; int positionC = 0; for (int c = 0; c < array.length; c++) { if (positionB == arrayB.length) { array[c] = arrayC[positionC]; positionC++; } else if (positionC == arrayC.length) { array[c] = arrayB[positionB]; positionB++; } else if (arrayB[positionB] < arrayC[positionC]) { array[c] = arrayB[positionB]; positionB++; } else { array[c] = arrayC[positionC]; positionC++; } } }
Alexey Katachigov Уровень 22
17 октября 2020
Вот статья на хабре с рабочим кодом https://habr.com/ru/post/281675/
Dmitriy Уровень 9
3 сентября 2020
не работает... возьмем два массива {1,3,4} и {2,5} на третьем прогоне PositionB сравняется с длинной массива, и начнет добавлять только из первого массива,тогда как во втором еще осталась 5ка...
18 марта 2020
int[] mergeArrays(int[] a1, int[] a2) { int[] b = new int[a1.length + a2.length]; int positionA1 = 0; int positionA2 = 0; for(int i = 0; i < b.length; i++) { if(positionA1 == a1.length){ b[i] = a2[positionA2]; positionA2++; } else if(positionA2 == a2.length){ b[i] = a1[positionA1]; positionA1++; } else if(a1[positionA1] < a2[positionA2]){ b[i] = a1[positionA1]; positionA1++; } else { b[i] = a2[positionA2]; positionA2++; } } return b; }