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 апреля, 10:10
Автор вообще свой код проверял, прежде чем сюда его откуда-то скопипастить? Куча ошибок, логика в коде частично неверная, код нерабочий
Сергей Сак
Уровень 3
10 апреля, 10:22
Чтобы сортировка заработала, нужно подправить цикл for в методе mergeArray:
for (int i = 0; i < arrayC.length; i++) {
            if (positionA == arrayA.length) {
                arrayC[i] = arrayB[positionB];
                positionB++;
            } else if (positionB == arrayB.length) {
                arrayC[i] = arrayA[positionA];
                positionA++;
            } else if (arrayA[positionA] < arrayB[positionB]) {
                arrayC[i] = arrayA[positionA];
                positionA++;
            } else {
                arrayC[i] = arrayB[positionB];
                positionB++;
            }
        }
Anonymous #3352746
Уровень 19
15 декабря 2023, 01:00
Вот, для простоты понимания:
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, 13:11
Мне попалось задание реализовать метод Merge с такими параметрами, но я хоть убей не могу понять как это сделать. Вроде для слияния нужны 2 массива? У меня не получается придумать, может кто подсказать реализацию такого метода слияния, когда передается 1 массив или я что то не понимаю? Sort(A, p, q); Merge(A, p, q, r);
Jknock Garon
Уровень 2
10 июня 2023, 13:12
Если что стандартную реализацию я сделал, но вот с этим не получается, прошу помощи)
Anonymous #3352746
Уровень 19
15 декабря 2023, 01:16
Вот один массив передается на сортировку, просто он в процессе делится на части (до двух элементов, а можно и до одного - удалить строчку кода #9 - ничего не изменится, кроме появлении лишней итерации рекурсии, как мне кажется) и они сортируются, пока не останется двух отсортированных по возрастанию массивов, которые сортируются слиянием
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;
    }
}
Shahin
Уровень 0
30 октября 2022, 18:47
Имеется 2 Массива: A={1,4,8} и B={3,6,7}. В функции mergeArray во время прохода по циклу for при i = 5 остаётся один элемент массива A,а именно "8". Какая строка кода функции mergeArray закидывает оставшуюся 8-ку массива A в массив С???
22 декабря 2021, 08:35
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, 16:50
Вот, что у меня получилось: https://github.com/sldstrst/sort_merge Писал самостоятельно. Сначала попробовал вставить код из этой статьи и разобрать, потом понял, что это просто не запускается. Быстрее всё самому разобрать и понять. В моей реализации вам надо: 1. Создать главный класс Main, 2. Создать интерфейс Sorted 3. Запихнуть туда всё, что лежит у меня из кода. Останется только разобрать что за чем идёт. Код полностью рабочий.
Viktoria Bazorova
Уровень 31
19 января 2022, 12:02
Спасибо!
Alexey Pavlovsky from в Krasnodar
6 сентября 2022, 09:38
Не заходите на его гит и не тратьте время на подобную чушь. Нужно изначально смотреть на правильную реализацию, правильное именование переменных и прочих. Человек, который не слышал о CamelCase в именовании Классов - учить сортировкам, тем более слиянием (это далеко не самый простой алгоритм) не может и не должен. Тем более на гит не пушат код, в котором ху*** тонна не нужных комментариев.
Maksim
Уровень 19
7 марта 2021, 21:32
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++; } } }
MisterMisix
Уровень 36
23 ноября 2021, 19:38
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 Full Stack Developer в https://omegabot.ru
17 октября 2020, 05:57
Вот статья на хабре с рабочим кодом https://habr.com/ru/post/281675/
Dmitriy
Уровень 9
3 сентября 2020, 21:03
не работает... возьмем два массива {1,3,4} и {2,5} на третьем прогоне PositionB сравняется с длинной массива, и начнет добавлять только из первого массива,тогда как во втором еще осталась 5ка...
18 марта 2020, 05:31
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; }
Artem Kirillov безработный мужчина в самом расцвете сил
6 октября 2021, 08:28
Спасибо добрый человек!
MisterMisix
Уровень 36
23 ноября 2021, 19:38
nt[] 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;
	}