Сортировка слиянием для фа-диеза

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

//
// merge two sorted lists into one:
//
let rec merge L1 L2 = 
  if L1 = [] && L2 = [] then
    []
  else if L1 = [] then
    L2
  else if L2 = [] then
    L1
  else if L1.Head <= L2.Head then
    L1.Head :: merge L1.Tail L2
  else
    L2.Head :: merge L1 L2.Tail

//
// mergesort:
//
let rec mergesort L = 
  match L with
 | []    -> []
 | E::[] -> L
 | _     -> 
   let mid = List.length L / 2
   let (L1, L2) = List.splitAt mid L
   merge (mergesort L1) (mergesort L2)

person JimBob101    schedule 24.02.2016    source источник
comment
В вопросе укажите свой код, а не ссылку   -  person John Palmer    schedule 24.02.2016
comment
Извините, я отредактировал сообщение   -  person JimBob101    schedule 24.02.2016
comment
Все еще не завершено - прочтите stackoverflow.com/help/mcve   -  person John Palmer    schedule 24.02.2016
comment
Я не уверен, почему вы заставили меня прочитать это idk, что еще не завершено   -  person JimBob101    schedule 24.02.2016
comment
ну, когда я разместил ссылку, функция merge отсутствовала, но теперь она выглядит нормально   -  person John Palmer    schedule 24.02.2016
comment
вы получаете исключение переполнения стека, потому что ваш код не является хвостовым рекурсивным (последний вызов merge)   -  person Random Dev    schedule 24.02.2016
comment
Хорошо, да, спасибо, я это вижу, я просто не очень хорошо знаю синтаксис, так как язык для меня новый, вы можете мне помочь?   -  person JimBob101    schedule 24.02.2016


Ответы (2)


В обеих ваших функциях у вас была проблема, заключающаяся в том, что последний шаг, который вы делаете, - это не рекурсивный вызов, а что-то другое:

  • в merge это :: операция
  • в mergesort это merge

Итак, вам нужно добраться до точки, где самым последним будет рекурсивный вызов!

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

это хвостовая рекурсивная версия mergesort, использующая эту технику:

let mergesort xs = 
    let rec msort xs cont = 
        match xs with
        | []    -> cont []
        | [x]   -> cont xs
        | _     -> 
            let mid = List.length xs / 2
            let (xs', xs'') = List.splitAt mid xs
            msort xs' (fun ys' -> msort xs'' (fun ys'' -> cont (merge ys' ys'')))
    msort xs id

как вы можете видеть, идея не такая уж и сложная - вместо первого вызова обоих рекурсивных путей он начинается только с одной половины, но добавляет продолжение, которое в основном говорит:

как только у меня будет результат mergesort xs', я беру результат ys' и продолжаю mergesorting xs'', а затем объединяю эти

конечно, второй шаг выполняется точно так же (вставьте merge в продолжение)

самое первое продолжение обычно является идентификатором, как вы можете видеть в самой последней строке;)


и вот что-то подобное для вашего merge:

let merge xs ys = 
    let rec mrg xs ys cont =
        match (xs, ys) with
        | ([], ys) -> cont ys
        | (xs, []) -> cont xs
        | (x::xs', y::ys') ->
            if x < y 
            then mrg xs' ys (fun rs -> cont (x::rs))
            else mrg xs ys' (fun rs -> cont (y::rs))
    mrg xs ys id

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

person Random Dev    schedule 24.02.2016
comment
Могу ли я сделать так, чтобы мой код использовал хвостовую рекурсию, моя цель - научиться этому? - person JimBob101; 24.02.2016
comment
те есть! - посмотрите, где происходят рекурсивные вызовы - это всегда последнее, что будет делать путь оценки внутри функции - person Random Dev; 24.02.2016
comment
просто чтобы сделать их хвостовой рекурсией - person Random Dev; 24.02.2016
comment
Итак, я пытаюсь здесь использовать только сопоставление, ожидающий оператор, конкатенацию, разделение на функцию и хвостовую рекурсию. Так что без продолжения. Это вообще возможно? - person JimBob101; 24.02.2016
comment
Я не скажу, что вы не можете найти какой-то способ с аккумуляторами или чем-то еще, но AFAIK я не думаю, что есть (хотя я не могу показать вам никаких доказательств прямо сейчас) - person Random Dev; 24.02.2016
comment
Ну, мой профессор сказал использовать это как вызов, мы хотели попробовать, и мне было интересно, он сказал, что есть несколько способов сделать это, я пробовал и потерпел неудачу, и теперь хочу знать, как лол - person JimBob101; 24.02.2016
comment
Что ж, единственное, что я могу показать вам, - это как это сделать в этом стиле - возможно, вы сможете найти способ, используя аккумуляторы (подумав об этом, вы, вероятно, сможете), но это будет уродливо IMO - person Random Dev; 24.02.2016
comment
PS: это очень помогло бы, если бы вы упомянули, что это домашнее задание - теперь вы потеряли возможность, чтобы мы привели вас к вашему собственному решению :( - person Random Dev; 24.02.2016
comment
Это не домашнее задание, лол, это вызов для солнца, мы ничего не получаем за его выполнение. - person JimBob101; 24.02.2016
comment
Я не это имел в виду - это упражнение для вас. У меня нет проблем с людьми, которые спрашивают их домашнее задание / упражнения, но я обычно отвечаю по-другому, так как целью должно быть выучить что-то, а не получить быстрый ответ - person Random Dev; 24.02.2016
comment
где? Чтобы понять продолжение? - person Random Dev; 24.02.2016
comment
Я не хочу использовать продолжение продолжения); строгое соответствие, ожидающий оператор, конкатенация, разделение на - person JimBob101; 24.02.2016
comment
удачи ^^ - я бы не стал тратить на это много времени, хотя - person Random Dev; 24.02.2016

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

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

В вашем случае это сложно сделать, потому что вам придется дважды вызывать слияние. Только один из них может быть последним. Решение - использовать продолжение. . Вы вызываете слияние только один раз, но передаете ему функцию для вызова, которая вызовет слияние во второй раз.

Поищите в Интернете примеры F # сортировки слиянием, в которой используются продолжения.

person Tony Lee    schedule 24.02.2016
comment
Это не мой код, и я не хочу дублировать здесь Интернет. - person Tony Lee; 24.02.2016
comment
Как я могу отредактировать свой код, чтобы использовать хвостовую рекурсию, поскольку цель этого - изучить хвостовую рекурсию? - person JimBob101; 24.02.2016