150 likes | 313 Views
Функциональное программирование. Факультет инноваций и высоких технологий Московский физико-технический институт. Лекция 10. Хвостовая рекурсия. Порядковое представление списков и матриц. Хвостовая рекурсия.
E N D
Функциональное программирование Факультет инноваций и высоких технологий Московский физико-технический институт
Лекция 10 Хвостовая рекурсия. Порядковое представление списков и матриц.
Хвостовая рекурсия • Во многих случаях (особенно при обработке списков) рекурсивные алгоритмы могут быть сведены к итерационным • Такая рекурсия называется хвостовой • Линейная • Рекурсивный вызов – в конце тела функции • т.е. вызов совершается сразу после выполнения тела функции • Не выделяется промежуточная память
length • После рекурсивного вызова выполняется операция сложения • При каждом погружении выделяется память на промежуточные вычисления и на адрес возврата – O(n) • В случае итерационного алгоритма выделение памяти бы не потребовалось 1+2=3 let rec length = function [] -> 0 | _::t -> 1+length(t);; length [a;b;c] 1+1=2 length [b;c] 1+0=1 length [c] 0 length []
Приведение к хвостовой рекурсии • Вводим промежуточную функцию len : int → T list → int • При каждом откусывании головы числовой параметр (разностный счетчик) увеличивается, затем делается рекурсивный вызов • В конце значение счетчика возвращается как результат let length L = let rec len a = function [] -> a | _::t -> len (a+1) t in len 0 L;;
Хвостовая рекурсия 3 • Сложение выполняется перед рекурсивным вызовом • Результат возвращается напрямую в вызывающую функцию => не требуется выделения памяти • Требуемая память – O(1) len 0 [a;b;c] let rec len a = function [] -> a |_::t -> len (a+1) t 3 len 1 [b;c] 3 3 len 2 [c] 3 len 3 []
Реверсирование списка rev: T list → T list let rec rev = function [] -> [] | h::t -> (rev t)@[h];; • Не хвостовая рекурсия • Сложность: O(n2), поскольку append имеет линейную сложность
Приведение к хвостовой рекурсии • Вводим аналог счетчика – списковый • Начиная с [], каждое откусывание головы присоединяется к этому списку let rev L = let rec rv s = function [] -> s | h::t -> rv (h::s) t in rv [] L;;
Порядковое представление списков Возможно предложить другое, более «функциональное» определения списка как последовательности чисел list : int -> A type 'a nlist = int->'a option ;; • Возможно работать с таким списком привычным образом, определив основные операции let nhd l = l 0;; let ntl l = fun x -> if x>=0 then l (x+1) else None;; let nempty l = (l 0) = None;; let mempty = fun _ -> None;; let ncons a l = fun x -> (if x=0 then Some(a) else l(x-1));;
Например • Другие примеры – на семинаре и в рамках лабораторной работы №1 • Порядковое представление неэффективно (представление функции в памяти), но любопытно с теоретической точки зрения • Может использоваться для представления матриц (в особенности разреженных) let rec to_list l = if nempty l then [] else (nhd l)::to_list (ntl l) let rec from_list = function [] -> mempty | h::t -> ncons h (from_list t) let rec map f l = match nhd l with None -> mempty | Some(x) -> ncons (f x) (map f (ntl l));;
Представление матриц • Удобно для разреженных матриц, при этом надо отдельно хранить размерность: • type ‘a rmatrix = int*int*(int->int->’a option);; • Когда использовать то или иное представление?
Транспонирование [[1 2 3] [[1 4 7] [4 5 6] [2 5 8] [7 8 9]] [3 6 9]] • Списковое транспонирование оказывается менее очевидной операцией, чем определение aij=bji • А соответственно и другие операции, использующие столбцы (умножение), будут сложнее (хотя могут быть сведены к транспонированию) let rec slice = function [] -> ([],[]) | h::t -> let (u,v) = slice t in (hd h::u, tl h::v);; let rec trans = function []::_ -> [] | x -> let (h,r) = slice x in h::(trans r);;
Транспонирование порядковой матрицы • Мораль: • Для различных задач оказываются удобными различные представления абстрактных типов данных let transp (n,m) = (n, fun i j -> m j i);;
Встроенные линейно-алгебраические типы • Matrix<T>, Vector<T>, RowVector<T> • matrix = Matrix<float>, rowvec, vector #r "FSharp.PowerPack.dll" open Microsoft.FSharp.Core;; open Microsoft.FSharp.Math;; let M = Matrix.of_list [[1.0;2.0;3.0];[4.0;5.0;6.0];[7.0;8.0;9.0]];; M.Transpose;; M.Row(1);; (Vector.of_list [1.0;2.0;3.0]) * (RowVector.of_list [1.0;2.0;3.0]);; M*M.Transpose;;