Визуализировать сортировку слиянием

30

Сортировка слиянием - это алгоритм сортировки, который работает, разделяя заданный список пополам, рекурсивно сортируя оба меньших списка и объединяя их обратно в один отсортированный список. Базовый случай рекурсии приходит к одноэлементному списку, который не может быть разделен далее, но по определению уже отсортирован.

Выполнение алгоритма в списке [1,7,6,3,3,2,5]можно визуализировать следующим образом:

       [1,7,6,3,3,2,5]
       /             \      split
   [1,7,6,3]       [3,2,5]
    /     \        /    \   split
 [1,7]   [6,3]   [3,2]  [5]
 /   \   /   \   /   \   |  split
[1] [7] [6] [3] [3] [2] [5]
 \   /   \   /   \   /   |  merge
 [1,7]   [3,6]   [2,3]  [5]
    \     /         \   /   merge
   [1,3,6,7]       [2,3,5]
       \             /      merge
       [1,2,3,3,5,6,7]

Задание

Напишите программу или функцию, которая принимает список целых чисел любым разумным способом в качестве входных данных и визуализирует различные разделы этого списка, сортируя их с помощью алгоритма сортировки слиянием. Это означает, что вам не нужно выводить график, как указано выше, но только списки в порядке:

[1,7,6,3,3,2,5]
[1,7,6,3][3,2,5]
[1,7][6,3][3,2][5]
[1][7][6][3][3][2][5]
[1,7][3,6][2,3][5]
[1,3,6,7][2,3,5]
[1,2,3,3,5,6,7]

Кроме того, любая разумная нотация списка подходит, поэтому следующие данные также могут быть действительными:

1 7 6 3 3 2 5
1 7 6 3|3 2 5
1 7|6 3|3 2|5
1|7|6|3|3|2|5
1 7|3 6|2 3|5
1 3 6 7|2 3 5
1 2 3 3 5 6 7

Наконец, способ разбить список на два меньших списка зависит от вас, если длина обоих результирующих списков отличается не более чем на один. Это означает, что вместо разделения [3,2,4,3,7]на [3,2,4]и [3,7]вы также можете разделять, беря элементы по четным и нечетным индексам ( [3,4,7]и [2,3]) или даже случайным образом разбивая разделение каждый раз.

Это , поэтому выигрывает самый короткий код на любом языке, измеряемый в байтах.

Контрольные примеры

Как отмечалось выше, фактический формат и способ разделения списков пополам зависит от вас.

[10,2]
[10][2]
[2,10]

[4,17,1,32]
[4,17][1,32]
[4][17][1][32]
[4,17][1,32]
[1,4,17,32]

[6,5,4,3,2,1]
[6,5,4][3,2,1]
[6,5][4][3,2][1]
[6][5][4][3][2][1]
[5,6][4][2,3][1] <- Important: This step cannot be [5,6][3,4][1,2], because 3 and 4 are on different branches in the the tree
[4,5,6][1,2,3]
[1,2,3,4,5,6]
Laikoni
источник
5
@dylnan Вы можете использовать другой алгоритм сортировки или встроенную функцию сортировки, чтобы выполнить сортировку ...
flawr
5
Некоторая идея игры в гольф: нижняя половина результата может быть получена путем сортировки каждого подсписка в первой половине и изменения порядка.
JungHwan Мин
2
@Arnauld [[1,2],[3],[4,5],[6]]Этап на самом деле является правильным решением, так как сортировка слиянием работает рекурсивно. То есть, если мы начнем с [1,2,3,4,5,6]и разделим его на [1,2,3]и [4,5,6], то эти списки будут обрабатываться независимо, пока они не будут объединены на последнем этапе.
Лайкони
2
@ l4m2 Хорошо, окончательная попытка получить ответ: 1. вам нужны разделители, потому что также должны поддерживаться целые числа> 9. 2. Это недействительно по той же причине, что указана в моем комментарии выше. Если мы разбиваемся на [3]и [2,1], то они находятся в разных ветвях, поэтому мы не можем объединиться, [3]а [2]после [2,1]делим на [2]и [1].
Лайкони
1
На самом деле предложение после этого точно отвечает на мой вопрос. Извините, что пропустил это. : P
Zgarb

Ответы:

8

Haskell , 137 128 127 125 121 109 106 байт

(-2) + (- 4) = (- 6) байт благодаря nimi ! Изменение его для сбора всех шагов в списке (опять же из-за nimi) экономит еще 12 байтов.

Еще 3 байта из-за Лайкони с привязкой к шаблону и умным использованием списка для кодирования защиты.

import Data.List
g x|y<-x>>=h=x:[z|x/=y,z<-g y++[sort<$>x]]
h[a]=[[a]]
h x=foldr(\x[b,a]->[x:a,b])[[],[]]x

Попробуйте онлайн!

Разделение списков на нечетные и четно расположенные элементы является более коротким кодом, чем на две последовательные половины, потому что тогда мы избавлены от необходимости измерять их length.

Работает, «печатая» списки, затем возвращаясь к разделенным спискам ( x >>= h), если на самом деле было какое-либо разделение, и «печатая» отсортированные списки; начиная с одного списка ввода; при условии непустого ввода. И вместо фактической печати, просто собирая их в список.

Список g[[6,5..1]], выводимый построчно:

[[6,5,4,3,2,1]]
[[6,4,2],[5,3,1]]
[[6,2],[4],[5,1],[3]]
[[6],[2],[4],[5],[1],[3]]
[[2,6],[4],[1,5],[3]]
[[2,4,6],[1,3,5]]
[[1,2,3,4,5,6]]
Уилл Несс
источник
1
... p=printи три раза p. Попробуйте онлайн!
Ними
@nimi здорово, еще раз, и большое спасибо! теперь это действительно выглядит в гольфе . :)
Уилл Несс
Вместо печати внутри функции gвы можете собрать все шаги в списке и вернуть его. Попробуйте онлайн!
Ними
3
Я не думаю, что у нас есть правильное определение «визуализации». Более общие задачи требуют «вывода» списков, и в соответствии с нашими значениями по умолчанию это можно сделать с помощью возвращаемого значения функции . Другие ответы, например, 1 , 2, делают это тоже так. - Я не думаю, что мое предложение сильно отличается, оно просто собирает промежуточные списки, а не печатает их. Не стесняйтесь редактировать это.
nimi
3
g x|y<-x>>=h=x:[z|x/=y,z<-g y++[sort<$>x]]сохраняет еще несколько байтов.
Лайкони
7

Wolfram Language (Mathematica) , 146 127 111 102 байта

Join[u=Most[#/.a:{_,b=__Integer}:>TakeDrop[a,;;;;2]&~FixedPointList~#],Reverse@Most@u/.a:{b}:>Sort@a]&

Попробуйте онлайн!

Возвращает, Listкоторый содержит шаги.

объяснение

#/.a:{_,b=__Integer}:>TakeDrop[a,;;;;2]&

Во входных данных разбить все Lists, содержащие 2 или более целых чисел пополам. Первый подсписок имеет элементы с нечетным индексом (1-индексированный), а второй имеет элементы с четным индексом.

u=Most[... &~FixedPointList~#]

Повторяйте это, пока ничего не изменится (т.е. все подсписки имеют длину 1). Сохраните все промежуточные результаты. Сохраните это ( Listвсех шагов) в u.

Reverse@Most@u

Оставьте последний элемент uи переверните его.

... /.a:{b}:>Sort@a

Из приведенного выше результата отсортируйте все вхождения списка целых чисел.

Юнг Хван Мин
источник
6

Чистый , 228 206 168 157 140 121 104 байта

Создает список этапов от концов внутрь, используя тот факт, что n-ый элемент с конца является отсортированной версией n-ого элемента с самого начала.

Идея из комментария Юнг Хван Мин

import StdEnv
@u#(a,b)=splitAt(length u/2)u
=if(a>[])[[u]:[x++y\\y<- @b&x<- @a++ @a++ @a]][]++[[sort u]]

Попробуйте онлайн!

Οurous
источник
4

Древесный уголь , 145 133 123 122 байта

≔⟦⪪S ⟧θW⊖L§θ⁰«⟦⪫Eθ⪫κ ¦|⟧≔EE⊗Lθ§θ÷λ²✂κ﹪λ²Lκ²θ»⟦⪫ΦEθ⪫ι ι|⟧W⊖Lθ«≔⮌θη≔⟦⟧θWη«≔⊟ηε≔⟦⟧ζF⊟η«≔εδ≔⟦⟧εFδ⊞⎇‹IμIλζεμ⊞ζλ»⊞θ⁺ζε»⟦⪫Eθ⪫κ ¦|

Попробуйте онлайн! Ссылка на подробную версию кода. По-прежнему приходится обходить ошибки с древесным углем ... Редактировать: Сохранено 5 байт с использованием двойного Mapв качестве понимания массива для бедняков. Сохранено 4 байта с помощью Popдвойной итерации по массиву. Сохранено 3 байта с использованием конкатенации вместо Push. Сэкономьте 10 байт, придумав whileусловие для игры в гольф, которое также позволяет избежать ошибки древесного угля. Сэкономил 1 байт, обнаружив, что Charcoal действительно имеет оператор фильтра. Объяснение:

≔⟦⪪S ⟧θ

Разбейте ввод на пробелы, а затем оберните результат во внешний массив, сохранив его в q.

W⊖L§θ⁰«

Повторите, пока первый элемент qимеет более одного элемента. (Первый элемент qвсегда содержит наибольшее количество элементов из-за способа разделения списков на два.)

⟦⪫Eθ⪫κ ¦|⟧

Распечатать элементы qобъединенных с пробелами и вертикальными линиями. (Массив приводит к тому, что результат печатается в отдельной строке. Существуют и другие способы достижения этого при том же количестве байтов.)

≔EE⊗Lθ§θ÷λ²✂κ﹪λ²Lκ²θ»

Создайте список, продублировав каждый элемент q, затем сопоставьте его с этим списком и возьмите половину каждого списка (используя подход альтернативного элемента), сохранив результат обратно q.

⟦⪫ΦEθ⪫ι ι|⟧

Распечатать элементы qобъединенных с пробелами и вертикальными линиями. На самом деле, на данный момент элементы qявляются либо пустыми, либо одноэлементными списками, поэтому объединение их просто превращает их в пустые строки или их элементы. Пустые строки добавят ненужные конечные строки, чтобы они отфильтровывались. Сглаженный оператор был бы гольфистом (что-то вроде ⟦⪫Σθ|⟧).

W⊖Lθ«

Повторите, пока qимеет более одного элемента. (Следующий код требует четного количества элементов.)

≔⮌θη≔⟦⟧θ

Копировать qв h, но обратное (см ниже), и сброс qв пустой список.

Wη«

Повторите, пока hне пусто.

≔⊟ηε

Извлеките следующий элемент hв e. ( Popвыдержки из конца, поэтому я должен повернуть вспять q.)

≔⟦⟧ζ

Инициализируйте zпустой список.

F⊟η«

Зацикливание на элементах следующего элемента h.

≔εδ≔⟦⟧ε

Копировать eв dи сбросить eв пустой список.

Fδ

Зацикливаться на элементах d.

⊞⎇‹IμIλζεμ

Нажмите их zили в eзависимости от того, меньше ли они текущего элемента следующего элемента h.

⊞ζλ»

Нажмите текущий элемент следующего элемента hв z.

⊞θ⁺ζε»

Объедините zвсе оставшиеся элементы eи нажмите на них q. Это завершает слияние двух элементов h.

⟦⪫Eθ⪫κ ¦|

Распечатать элементы qобъединенных с пробелами и вертикальными линиями.

Нил
источник
Подожди. Есть еще одна ошибка? : /
ASCII-only
@ Только для ASCII Нет, об этой while (...Map(...)...)ошибке я уже говорил вам.
Нил
2

JavaScript (ES6), 145 байт

f=a=>a.join`|`+(a[0][1]?`
${f([].concat(...a.map(b=>b[1]?[b.slice(0,c=-b.length/2),b.slice(c)]:[b])))}
`+a.map(b=>b.sort((x,y)=>x-y)).join`|`:``)

Принимает входные данные как массив в массиве, то есть f([[6,5,4,3,2,1]]). Работает, генерируя первую и последнюю строки выходных данных, затем разделяясь и вызывая себя снова, пока каждый вложенный массив не будет иметь длину 1. Вот базовая демонстрация того, как он работает:

f([[6,5,4,3,2,1]]):
  6,5,4,3,2,1
  f([[6,5,4],[3,2,1]]):
    6,5,4|3,2,1
    f([[6,5],[4],[3,2],[1]]):
      6,5|4|3,2|1
      f([[6],[5],[4],[3],[2],[1]]):
        6|5|4|3|2|1
      end f
      5,6|4|2,3|1
    end f
    4,5,6|1,2,3
  end f
  1,2,3,4,5,6
end f
ETHproductions
источник
2
Итак, был ли момент, когда три ответа были связаны с 145 байтами?
Нил
2

Шелуха , 14 байт

S+ȯ†O↔hUmfL¡ṁ½

Принимает массив, содержащий один массив. Попробуйте онлайн!

объяснение

S+ȯ†O↔hUmfL¡ṁ½  Implicit input, say A = [[4,17,32,1]].
           ¡    Iterate this function on A:
            ṁ½   Split each array in two, concatenate results: [[4,17],[32,1]]
                Result is [[[4,17,32,1]],
                           [[4,17],[32,1]],
                           [[4],[17],[32],[1]],
                           [[4],[],[17],[],[32],[],[1],[]],
                           ...
        mfL     Map filter by length, removing empty arrays.
                Result is [[[4,17,32,1]],
                           [[4,17],[32,1]],
                           [[4],[17],[32],[1]],
                           [[4],[17],[32],[1]],
                           ...
       U        Longest prefix of unique elements:
                       P = [[[4,17,32,1]],[[4,17],[32,1]],[[4],[17],[32],[1]]]
      h         Remove last element: [[[4,17,32,1]],[[4,17],[32,1]]]
     ↔          Reverse: [[[4,17],[32,1]],[[4,17,32,1]]]
   †O           Sort each inner array: [[[4,17],[1,32]],[[1,4,17,32]]]
S+ȯ             Concatenate to P:
                           [[[4,17,32,1]],
                            [[4,17],[32,1]],
                            [[4],[17],[32],[1]],
                            [[4,17],[1,32]],
                            [[1,4,17,32]]]
                Implicitly print.

Встроенный ½берет массив и разбивает его посередине. Если его длина нечетная, первая часть длиннее на один элемент. В [a]результате получается одноэлементный массив, [[a],[]]а пустой массив []дает [[],[]], поэтому перед применением необходимо удалить пустые массивы U.

Zgarb
источник
1

Stax , 116 (÷ 3>) 38 29 байт CP437

-9 байтов за комментарий @recursive. Теперь входные данные задаются как синглтон, единственным элементом которого является массив чисел, подлежащих сортировке.

ƒ3s}óºE/ßB╢↕êb∩áαπüµrL╞¶è,te+

Попробуйте онлайн!

Распакованная версия с 35 байтами:

{c{Jm'|*Pc{2Mm:f{fc{Dm$wW{{eoJm'|*P

объяснение

Код можно разбить на две части. Первая часть визуализирует разделение, а вторая визуализирует слияние.

Код для визуализации разбиения:

{                      w    Do the following while the block generates a true value
 c                          Copy current nested array for printing
  {Jm                       Use spaces to join elements in each part
     '|*                    And join different parts with vertical bar 
        P                   Pop and print

         c                  Copy current nested array for splitting
          {2Mm              Separate each element of the array to two smaller parts with almost the same size
                                That is, if the number of elements is even, partition it evenly.
                                Otherwise, the first part will have one more element than the second.
              :f            Flatten the array once
                {f          Remove elements that are empty arrays

                  c         Copy the result for checking 
                   {Dm$     Is the array solely composed of singletons?
                            If yes, ends the loop.

Код для визуализации слияния:

W              Execute the rest of the program until the stack is empty
 {{eoJm        For each part, sort by numeric value, then join with space
       '|*     Join the parts with vertical bar
          P    Pop and print the result

Старая версия, фактически строящая структуру вложенного списка.

{{cc0+=!{x!}Mm',*:}}Xd;%v:2^^{;s~{c^;<~sc%v,*{2M{s^y!svsm}M}YZ!x!Q,dmU@e;%v:2^{;%v:2^-N~0{c;={scc0+=Cc%v!C:f{o}{scc0+=C{s^y!svsm}?}Y!cx!P,dcm

cc0+= используется трижды в коде, чтобы проверить, является ли вершина стека скаляром или массивом.

{{cc0+=!{x!}Mm',*:}}Xстроит блок, который рекурсивно вызывает себя для правильного вывода вложенного массива чисел. (Вывод по умолчанию в Stax векторизует вложенный массив перед печатью).

{                  }X    Store the code block in X
 {           m           Map each element in the list with block
  cc                     Make two copies of the element
    0+                   + 0. If the element is a scalar, nothing will change
                              If the element is an array, the element 0 will be appended
      =!{  }M            If the element has changed after the `0+` operation
                             Which means it is an array
         x!              Recursively execute the whole code block on the element

              ',*        Join the mapped elements with comma
                 :}      Bracerizes the final result

Есть два других блока, которые выполняют разбиение и объединение соответственно. Они слишком многословны, и я не хочу объяснять (в исторической версии этого поста есть немного больше информации, но не ожидайте слишком многого).

Вейцзюнь Чжоу
источник
Очень хорошее улучшение. Я еще не совсем понимаю, но думаю, что cH!может быть использован вместо cH%!.
рекурсивный
{Nd}Mможет быть заменено Tтакже.
рекурсивный
Я сделал еще несколько адаптаций. staxlang.xyz/… Ответ Husk принимает входные данные как массив в массиве, так что я думаю, что это законно.
рекурсивный
Я нашел решение, которое должно быть на 2 символа ASCII короче, но я обнаружил ошибку в массиве транспонирования. В частности, он мутирует строки массива. Я добавлю это в отставание для 1.0.4
рекурсивный
ХОРОШО. Я с нетерпением жду обновления.
Вейцзюнь Чжоу,