Как опытные программисты в частности алгоритмики думают о рекурсии, как они её воспринимают? Разбираю быструю сортировку, выступая в качестве интерпретатора и начинаю путаться углубляясь в рекурсию. def quick_sort(alist): if len(alist) <= 1: return<br> barr = alist[0] left = [] mid = [] right = [] for i in range(len(alist)): if alist[i] < barr: left.append(alist[i]) elif alist[i] > barr: right.append(alist[i]) else: #alist[i] == bar mid.append(alist[i]) quick_sort(left) # Отсортировать левую часть quick_sort(right) # Отсортировать правую часть k=0 for x in left+mid+right: alist[k] = x k+=1 return alist alist = [2,3,4,1] print(quick_sort(alist)) Во многих видео уроках,в строке кода когда функция вызывают саму себя преподаватель относится к функции как к уже написанной. Неужели всё так просто и не стоит пытаться даже читать рекурсивные функции так как обычные. Особенно интересно как читать рекурсию в контексте этого алгоритма! PS. мне известны правила рекурсии: читать с конца, базовый случай, рекуррентное соотношение.
Ответ Любую рекурсивную функцию удобно рассматривать через ее дерево вызовов (англ. call stack tree):
Где каждая вершина - место вызова функции, а ветви, как ни странно, ветвление - рекурсивный вызов.
При этом важно в коде выделить 2 момента: точка останова место рекурсивного вызова или, другими словами, "место ветвления" В общем случае это выглядит примерно так: def rec_func(...): if(stop_condition): <-- точка останова return ...<br> rec_func(...) <-- ветвление rec_func(...) <-- ветвление<br> return ... <-- "главный" return текущей вершины <br> Теперь разберем Быструю сортировку из вопроса: Сама по себе логика достаточно проста по своей природе: делить массив по какому-то критерию, в данном алгоритме в зависимости от специального значения pivot, до тех пор, пока не будут получены массивы единичной длины - массивы единичной длины не могут быть не отсортированными. собрать массив из полученных отсортированный элементов Секрет кроется в первом пункте - нужно делить массивы пополам относительно какого-то элемента, его обычно называют pivot'ом. Точка останова в данном алгоритме тоже достаточно очевидна - if(размер текущего массива <= 1).<br>Из всего этого получаем такое дерево в случае, когда pivot - всегда последний элемент:
В вашем коде всегда используется первый элемент в кач-ве pivot'а, но это сути алгоритма не меняет. Все вершины без потомков - единичные массивы, которые мы и искали, их "конкатенация" в конечный массив слева направо и будет искомый отсортированный массив. Каждая вершина этого дерева - вызов рекурсивной функции, которая этот массив и делит. Pivot и равные им элементы записываются в "центральный" массив, который не нужно сортировать или куда-то передавать.
Ну и напоследок вернемся к Фибоначчи, с которого я начал ответ. Прямолинейный код у данного алгоритма прост, как пробка: def F(n): if n == 0: return 0 <-- точка останова elif n == 1: return 1 <-- точка останова else: return F(n-1)+F(n-2) <-- 2 точки ветвления, получаем бинарное дерево <br>Кстати, по дереву вызовов вычисления числа Фибоначчи видно, почему рекурсия для данного случая - плохо. В дерево встречается целые ветви с одинаковыми вычислениями.