Сортировка слиянием. Как рекурсия узнает, куда возвращаться, и состояние массива/списка до вызова рекурсии? (Питон)

Я понял основы рекурсии. Я понял такие функции, как последовательность Фибоначчи и факториал, и то, как значение возвращается в вызываемую функцию.

Я нашел этот код для Mergesort и модифицировал его, добавив несколько операторов печати, чтобы понять ход программы. Мне жаль, что это долго.

def mergeSort(alist):

    print("Splitting ", alist)

    if len(alist) > 1:

        mid = len(alist)//2

        lefthalf = alist[:mid]
        print("lefthalf created as a result of splitting", lefthalf)

        righthalf = alist[mid:]
        print("righthalf created as a result of splitting", righthalf)

        print("Calling mergesort on lefthalf")
        mergeSort(lefthalf)

        print("Calling mergesort on righthalf")
        mergeSort(righthalf)

        i=0
        j=0
        k=0

        print("Merging begins")
        print("lefthalf, righthalf is ", lefthalf, righthalf)

        while i < len(lefthalf) and j < len(righthalf):

            if lefthalf[i] < righthalf[j]:
                alist[k]=lefthalf[i]
                i=i+1
                k=k+1

            else:
                alist[k]=righthalf[j]
                j=j+1
                k=k+1

        while i < len(lefthalf):

            alist[k]=lefthalf[i]
            i=i+1
            k=k+1

        while j < len(righthalf):

            alist[k]=righthalf[j]
            j=j+1
            k=k+1

    else:
        print("Returning to the line after the calling function")
        return

alist = [5, 6, 7, 8, 0]
mergeSort(alist)
print(alist)

Я понял, как работает логика слияния. Что я хочу понять, так это то, что после слияния левой половины и правой половины наименьших подмассивов, как он запоминает, где остановился?

Операторы печати, сгенерированные приведенным выше кодом:

Splitting  [5, 6, 7, 8, 0]
lefthalf created as a result of splitting [5, 6]
righthalf created as a result of splitting [7, 8, 0]
Calling mergesort on lefthalf
Splitting  [5, 6]
lefthalf created as a result of splitting [5]
righthalf created as a result of splitting [6]
Calling mergesort on lefthalf
Splitting  [5]
Returning to the line after the calling function
Calling mergesort on righthalf
Splitting  [6]
Returning to the line after the calling function
Merging begins
lefthalf, righthalf is  [5] [6]
Calling mergesort on righthalf
Splitting  [7, 8, 0]
lefthalf created as a result of splitting [7]
righthalf created as a result of splitting [8, 0]
Calling mergesort on lefthalf
Splitting  [7]
Returning to the line after the calling function
Calling mergesort on righthalf
Splitting  [8, 0]
lefthalf created as a result of splitting [8]
righthalf created as a result of splitting [0]
Calling mergesort on lefthalf
Splitting  [8]
Returning to the line after the calling function
Calling mergesort on righthalf
Splitting  [0]
Returning to the line after the calling function
Merging begins
lefthalf, righthalf is  [8] [0]
Merging begins
lefthalf, righthalf is  [7] [0, 8]
Merging begins
lefthalf, righthalf is  [5, 6] [0, 7, 8]
[0, 5, 6, 7, 8]

Я хочу понять из приведенных выше операторов печати, как он вспомнил, чтобы вернуться к

Calling mergesort on righthalf
Splitting  [7, 8, 0]

после того, как он объединил [5] и [6] в [5 6]

Это подошло к концу операторов в условии if len(alist) > 1:. Как он узнал, что «у меня не расщеплена и не слита правая половина»? Может ли кто-нибудь помочь мне здесь?

Для factorial(n) и fib(n) было довольно просто понять, что значение возвращается в вызывающую функцию, и понять, как выполняется return n + calledfunction(n) в рекурсивном случае. Но внутри условия if нет оператора return.


person Abhishek    schedule 06.02.2018    source источник
comment
Если я правильно понимаю ваш вопрос, я могу ответить здесь .   -  person Christian Dean    schedule 07.02.2018
comment
да. Это то, что я хотел, интересно, может ли кто-нибудь вытянуть стек. Я не знаю, ожидаю ли я, что меня накормят с ложки, но это было трудно отследить.   -  person Abhishek    schedule 07.02.2018
comment
Да, это, вероятно, немного выходит за рамки Stack Overflow. Хорошим инструментом для изучения рекурсии будет Python Tutor. Поиграйте с ним немного и посмотрите, поможет ли это прояснить ситуацию.   -  person Christian Dean    schedule 07.02.2018
comment
@ChristianDean Я просто скопировал туда свой код... Боже мой. КАКОЙ ИНСТРУМЕНТ! ‹3 БОЛЬШОЕ СПАСИБО! МНЕ НИКОГДА НЕ НУЖНО ПОСЕЩАТЬ STACKOVERFLOW для рекурсии!   -  person Abhishek    schedule 07.02.2018
comment
Я рад, что ты нашел полезным, @Abhishek :-)   -  person Christian Dean    schedule 07.02.2018
comment
Опубликовать как ответ, чтобы я мог решить это?   -  person Abhishek    schedule 07.02.2018
comment
Я бы @Abhishek, но на самом деле я не дал ответа. Я только что разместил ссылку, которая явно не является ответом. Кроме того, ответ на такой вопрос, как ваш, как я уже говорил, немного выходит за рамки Stack Overflow. Не волнуйся, все в порядке.   -  person Christian Dean    schedule 07.02.2018
comment
Хорошо, если ты так говоришь!   -  person Abhishek    schedule 07.02.2018


Ответы (1)


Каждый вызов функции создает новый фрейм, который определяет локальную среду. Он наследует переменные, определенные вне его. Но переменные, определенные в нем, не могут перезаписывать переменные, определенные вне его.

Правая половина, которую вы создали внутри mergeSort([5, 6]), то есть [6], находится в локальном фрейме, созданном вызовом функции mergeSort([5, 6]). Он не перезапишет правую половину, определенную в верхнем фрейме, созданном вызовом функции mergeSort([5, 6, 7, 8, 0]). Правая половина в этом верхнем кадре по-прежнему [7, 8, 0].

Чтобы понять это, просто рассмотрите переменные с одинаковыми именами, определенные внутри локальной функции и вне ее.

def frame_upper():
  a = 1
  def frame_local():
    a = 2
    print("This a is in local frame:", a)
  frame_local()
  print("This a is in upper frame:", a)

frame_upper()
# output:
# This a is in local frame: 2
# This a is in upper frame: 1 

Как видите, после выполнения frame_local() a внутри верхнего кадра все еще 1. a=2, определенный в локальном фрейме, не известен верхнему фрейму. Это то же самое, что и ваш список [7, 8, 0]. Он существовал в верхнем фрейме и всегда там, он не будет перезаписан вашими вновь определенными правыми половинами [5, 6] и [6], которые находятся в локальном фрейме и подлокальном фрейме под этим локальным фреймом.

После того, как mergeSort([5, 6]) возвращает свой окончательный результат. Следующий оператор в верхнем фрейме (этот фрейм создан mergeSort([5, 6, 7, 8, 0])) готов к выполнению, что точно соответствует mergeSort([7, 8, 0]). Вот как это возвращается к тому моменту.

person englealuze    schedule 08.02.2018