← Назад к вопросам
Конкурентный Merge Sort
2.7 Senior🔥 111 комментариев
#Конкурентность и горутины#Производительность и оптимизация
Условие
Реализуйте алгоритм сортировки слиянием (Merge Sort) с использованием горутин для параллельной сортировки.
Сигнатура
func concurrentMergeSort(arr []int) []int
Требования
- Использовать горутины для параллельной сортировки подмассивов
- Использовать каналы для синхронизации
- Корректно объединять отсортированные части
Пример
Вход: []int{38, 27, 43, 3, 9, 82, 10}
Выход: []int{3, 9, 10, 27, 38, 43, 82}
Подсказка
Разделяйте массив на две части, сортируйте их параллельно в горутинах, затем сливайте результаты.
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Анализ задачи
Требования:
- Реализовать Merge Sort с параллелизацией через горутины
- Использовать каналы для синхронизации
- Корректно объединять отсортированные части
Стратегия:
- Разделить массив пополам
- Сортировать каждую половину в отдельной горутине
- Объединить результаты
Решение
func concurrentMergeSort(arr []int) []int {
if len(arr) <= 1 {
return arr
}
// Базовый случай для малых массивов
if len(arr) <= 16 {
return simpleSort(arr)
}
// Делим массив пополам
mid := len(arr) / 2
// Каналы для результатов
left := make(chan []int)
right := make(chan []int)
// Сортируем левую половину в горутине
go func() {
left <- concurrentMergeSort(arr[:mid])
}()
// Сортируем правую половину в горутине
go func() {
right <- concurrentMergeSort(arr[mid:])
}()
// Получаем отсортированные половины из каналов
leftSorted := <-left
rightSorted := <-right
// Объединяем два отсортированных массива
return merge(leftSorted, rightSorted)
}
// Простая сортировка для малых массивов
func simpleSort(arr []int) []int {
for i := 0; i < len(arr)-1; i++ {
for j := i + 1; j < len(arr); j++ {
if arr[i] > arr[j] {
arr[i], arr[j] = arr[j], arr[i]
}
}
}
return arr
}
// Объединение двух отсортированных массивов
func merge(left, right []int) []int {
result := make([]int, 0, len(left)+len(right))
i, j := 0, 0
for i < len(left) && j < len(right) {
if left[i] <= right[j] {
result = append(result, left[i])
i++
} else {
result = append(result, right[j])
j++
}
}
// Добавляем оставшиеся элементы
result = append(result, left[i:]...)
result = append(result, right[j:]...)
return result
}
Пояснения реализации
1. Базовый случай
- Массивы из одного элемента уже отсортированы
- Для малых массивов (≤16 элементов) используем простую сортировку, чтобы не создавать горутины
2. Разделение
- Делим массив ровно пополам в точке
mid - Левая часть:
arr[:mid], правая:arr[mid:]
3. Параллельная сортировка
- Каждую половину сортируем в отдельной горутине
- Результаты отправляются в каналы
leftиright - Главная горутина ждёт результатов через
<-leftи<-right
4. Объединение (merge)
- Два указателя
iиjидут по обоим массивам - На каждом шаге берём меньший элемент
- После цикла добавляем оставшиеся элементы
Пример выполнения
Входящий массив: [38, 27, 43, 3, 9, 82, 10]
Уровень 1:
Левая горутина: [38, 27, 43, 3]
Правая горутина: [9, 82, 10]
Уровень 2:
[38, 27] и [43, 3] — сортируются параллельно
[9] и [82, 10] — сортируются параллельно
Уровень 3:
[27, 38] + [3, 43] = [3, 27, 38, 43]
[9] + [10, 82] = [9, 10, 82]
Итоговое слияние:
[3, 27, 38, 43] + [9, 10, 82] = [3, 9, 10, 27, 38, 43, 82]
Полный пример с тестированием
package main
import (
"fmt"
"sort"
)
func concurrentMergeSort(arr []int) []int {
if len(arr) <= 1 {
return arr
}
if len(arr) <= 16 {
return simpleSort(arr)
}
mid := len(arr) / 2
left := make(chan []int)
right := make(chan []int)
go func() {
left <- concurrentMergeSort(arr[:mid])
}()
go func() {
right <- concurrentMergeSort(arr[mid:])
}()
leftSorted := <-left
rightSorted := <-right
return merge(leftSorted, rightSorted)
}
func simpleSort(arr []int) []int {
for i := 0; i < len(arr)-1; i++ {
for j := i + 1; j < len(arr); j++ {
if arr[i] > arr[j] {
arr[i], arr[j] = arr[j], arr[i]
}
}
}
return arr
}
func merge(left, right []int) []int {
result := make([]int, 0, len(left)+len(right))
i, j := 0, 0
for i < len(left) && j < len(right) {
if left[i] <= right[j] {
result = append(result, left[i])
i++
} else {
result = append(result, right[j])
j++
}
}
result = append(result, left[i:]...)
result = append(result, right[j:]...)
return result
}
func main() {
arr := []int{38, 27, 43, 3, 9, 82, 10}
sorted := concurrentMergeSort(arr)
fmt.Println("Sorted:", sorted) // [3, 9, 10, 27, 38, 43, 82]
// Проверка
expected := []int{3, 9, 10, 27, 38, 43, 82}
fmt.Println("Correct:", sort.IntsAreSorted(sorted))
}
Сложность
- Временная сложность: O(n log n) в среднем и худшем случаях
- Пространственная сложность: O(n) для временных массивов + O(log n) для стека рекурсии
- Параллелизм: использует 2^(log n) = n горутин на максимальной глубине рекурсии
Оптимизация
Настройка порога 16 может зависеть от размера массива:
- Для очень больших массивов увеличить порог (например, 1024)
- Для малых увеличить параллелизм