Яка різниця між рекурсією та основною струмою?


55

Яка різниця між ними?

У Вікіпедії мало інформації та чіткого коду, що пояснює ці терміни.

Назвіть кілька дуже простих прикладів, що пояснюють ці терміни?

Яким чином ядро ​​рекурсії є подвійним?

Чи є класичні основні алгоритми?


45
Дивіться відповідь на SO stackoverflow.com/questions/10138735/… (вибачте, я не міг зупинити себе)
Висока ефективність Марк

7
@HighPerformanceMark, це не пояснює, що таке основний струм, нам потрібне ще одне питання
Abyx

5
Але серйозно, що не так з поясненням цих термінів у Вікіпедії?
Марка високої продуктивності

5
Пояснення основної дискусії у Вікіпедії є жахливим. Сумніваюсь, це має сенс для тих, хто вже не знає, що таке основна струя.
Марцін

9
@High Performance Mark: Я тричі натиснув на посилання, думаючи, що сталася помилка, перш ніж я зрозумів каламбур. LOL
Джорджіо

Відповіді:


24

Існує ряд хороших способів розглянути це. Найпростіше для мене - подумати про співвідношення "індуктивного" та "індуктивного визначень"

Індуктивне визначення набору йде так.

Набір "Nat" визначається як найменший набір таким чином, що "Нуль" знаходиться в Nat, а якщо n - у Nat, "Succ n" - у Nat.

Що відповідає наступним Ocaml

type nat = Zero | Succ of nat

Одне, що слід зазначити щодо цього визначення, - це число

omega = Succ(omega)

НЕ є учасником цього набору. Чому? Припустимо, що це було, тепер розглянемо множину N, яка має всі ті ж елементи, що і Nat, за винятком того, що вона не має омеги. Очевидно, що Нуль є в N, а якщо y - в N, Succ (y) - у N, але N менше, ніж Nat, що суперечить. Отже, омеги немає в Нат.

Або, можливо, корисніше для вченого-комп’ютера:

Враховуючи деякий набір "a", набір "Список a" визначається як найменший набір таким чином, що "Nil" є у списку a, а якщо xs є у списку a, а x є у "мінусах x xs" знаходиться в Списку a.

Що відповідає чомусь подібному

type 'a list = Nil | Cons of 'a * 'a list

Оперативне слово тут «найменший». Якби ми не сказали "найменшого", ми б не мали жодного способу сказати, чи є в Наті банан!

Знову ж таки,

zeros = Cons(Zero,zeros)

не є правильним визначенням списку нац, так само як омега не була дійсною Nat.

Індуктивне визначення даних на зразок цього дозволяє нам визначати функції, які працюють на них за допомогою рекурсії

let rec plus a b = match a with
                   | Zero    -> b
                   | Succ(c) -> let r = plus c b in Succ(r)

Тоді ми можемо довести факти про це, наприклад "плюс нуль = а", використовуючи індукцію (конкретно, структурну індукцію)

Наше доведення випливає структурною індукцією на a.
Для базового випадку нехай буде нульовим. plus Zero Zero = match Zero with |Zero -> Zero | Succ(c) -> let r = plus c b in Succ(r)тому ми знаємо plus Zero Zero = Zero. Нехай aбуде нац. Припустимо індуктивну гіпотезу, що plus a Zero = a. Тепер ми покажемо, що plus (Succ(a)) Zero = Succ(a)це очевидно, оскільки, plus (Succ(a)) Zero = match a with |Zero -> Zero | Succ(a) -> let r = plus a Zero in Succ(r) = let r = a in Succ(r) = Succ(a) таким чином, шляхом індукції plus a Zero = aдля всіх aу фіз

Звичайно, ми можемо довести більш цікаві речі, але це загальна ідея.

Поки ми мали справу з індуктивно визначеними даними, які ми отримали, дозволяючи це бути "найменшим" набором. Отже, зараз ми хочемо працювати з кодифікованими кодатами, які ми отримуємо, дозволяючи їм бути найбільшим набором.

Тому

Нехай множина. Множина "Потік a" визначається як найбільша множина така, що для кожного x у потоці a, x складається з упорядкованої пари (голова, хвіст), така, що голова знаходиться у a, а хвіст - у Stream of a

У Haskell ми б це виразили як

data Stream a = Stream a (Stream a) --"data" not "newtype"

Насправді в Haskell ми зазвичай використовуємо вбудовані списки, які можуть бути впорядкованою парою або порожнім списком.

data [a] = [] | a:[a]

Банан також не є членом цього типу, оскільки це не упорядкована пара чи порожній список. Але, зараз ми можемо сказати

ones = 1:ones

і це абсолютно правильне визначення. Більше того, ми можемо виконати спільну рекурсію на цих спільних даних. Власне, функція може бути одночасно рекурсивною та рекурсивною. У той час як рекурсія визначалася функцією, що має домен, що складається з даних, ко-рекурсія просто означає, що вона має спільний домен (також званий діапазоном), який є спільними даними. Примітивна рекурсія означала завжди "викликати себе" на менших даних, поки не досягнуть деяких найменших даних. Примітивна спільна рекурсія завжди "називає себе" на даних, більших або рівних тим, що ви мали раніше.

ones = 1:ones

є первісно ко-рекурсивним. У той час як функція map(на кшталт «передчуття» в імперативних мовах) є як первісно рекурсивною (свого роду), так і примітивно корекурсивною.

map :: (a -> b) -> [a] -> [b]
map f []     = []
map f (x:xs) = (f x):map f xs

те саме стосується функції, zipWithяка приймає функцію та пару списків та поєднує їх разом, використовуючи цю функцію.

zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f (a:as) (b:bs) = (f a b):zipWith f as bs
zipWith _ _ _           = [] --base case

класичний приклад функціональних мов - послідовність Фібоначчі

fib 0 = 0
fib 1 = 1
fib n = (fib (n-1)) + (fib (n-2))

що є первісно рекурсивним, але може бути викладене більш елегантно як нескінченний список

fibs = 0:1:zipWith (+) fibs (tail fibs)
fib' n = fibs !! n --the !! is haskell syntax for index at

цікавий приклад індукції / співавтори - це доведення, що ці два визначення обчислюють одне і те ж. Це залишається як вправа для читача.


1
@ user1131997 Дякую Я планую перекласти частину коду на Java, будьте в курсі
Philip JF

@PhilipJF: Я почуваюся дурним, але не розумію, чому "... Ясно, що Нуль є в N, а якщо y - в N, Succ (y) - у N ...". Що станеться, якщо y - річ, що задовольняє Succ (y) = омега? (тому що ви не використовуєте жодної властивості Zero і Succ, я можу замінити Succ = кореневий квадрат і Zero = 2)
Ta Thanh Dinh

... і тоді я бачу омегу = 1.
Та Тхань Дінь

мета - показати, що омега не в нац. Ми робимо це протиріччям. Якби омега була в nat, то безліч N = nat - {omega} задовольнив би закони. Це тому, що nat відповідає законам. Якщо y в N, 1. y - не омега, а 2. y - у фіз. З 2 ми знаємо Succ (y) у nat, а на 1 y - не омега Succ (y) - не омега. Таким чином, Succ (y) в N. N також включає нуль. Але, N менший за фіз. Це суперечність. Таким чином, нат не включає омегу.
Philip JF

Це все трохи брехні, оскільки ocaml має значення рекурсії, я дійсно повинен був використовувати SML, який є єдиною "основною" мовою, яка підтримує індуктивне міркування.
Philip JF

10

В основному, основна реакція є стилем рекурсії акумулятора , будуючи її результат на шляху вперед від вихідного випадку, тоді як регулярна рекурсія будує свій результат на зворотному шляху від базового випадку.

(зараз говорить Хаскелл). Ось чому foldr(з суворою поєднує функцією) висловлює рекурсию, і foldl'(із суворим гребенем. Е.) / scanl/ until/ iterate/ unfoldr/ І т.д. висловити корекурсію. Корекція є скрізь. foldrз не суворим гребінцем. f. виражає хід хвороби рекурсії за модулем .

І охороняли рекурсії в Haskell просто як хвіст рекурсії по модулю мінуси .

Це рекурсія:

fib n | n==0 = 0
      | n==1 = 1
      | n>1  = fib (n-1) + fib (n-2)

fib n = snd $ g n
  where
    g n | n==0 = (1,0)
        | n>0  = let { (b,a) = g (n-1) } in (b+a,b)

fib n = snd $ foldr (\_ (b,a) -> (b+a,b)) (1,0) [n,n-1..1]

(читати $як "із"). Це основнийкурс:

fib n = g (0,1) 0 n where
  g n (a,b) i | i==n      = a 
              | otherwise = g n (b,a+b) (i+1)

fib n = fst.snd $ until ((==n).fst) (\(i,(a,b)) -> (i+1,(b,a+b))) (0,(0,1))
      = fst $ foldl (\(a,b) _ -> (b,a+b)) (0,1) [1..n]
      = fst $ last $ scanl (\(a,b) _ -> (b,a+b)) (0,1) [1..n]
      = fst (fibs!!n)  where  fibs = scanl (\(a,b) _ -> (b,a+b)) (0,1) [1..]
      = fst (fibs!!n)  where  fibs = iterate (\(a,b) -> (b,a+b)) (0,1)
      = (fibs!!n)  where  fibs = unfoldr (\(a,b) -> Just (a, (b,a+b))) (0,1)
      = (fibs!!n)  where  fibs = 0:1:map (\(a,b)->a+b) (zip fibs $ tail fibs)
      = (fibs!!n)  where  fibs = 0:1:zipWith (+) fibs (tail fibs)
      = (fibs!!n)  where  fibs = 0:scanl (+) 1 fibs
      = .....

Складки: http://en.wikipedia.org/wiki/Fold_(higher-order_function)


4

Перевірте це у блозі Вітоміра Ковановича . Я знайшов це до речі:

Ледача оцінка в одній дуже приємній особливості, знайденій в мовах програмування з функціональними можливостями програмування, такими як lisp, haskell, python тощо. Це означає, що оцінка значення змінної затримується до фактичного використання цієї змінної.

Це означає, що, наприклад, якщо ви хотіли створити список мільйонів елементів з чимось подібним, (defn x (range 1000000))він насправді не створений, але він просто вказаний і коли ви дійсно використовуєте цю змінну вперше, наприклад, коли ви хочете 10-й елемент перекладач цього списку створює лише перші 10 елементів цього списку. Отже, перший запуск (взяти 10 х) фактично створює ці елементи, а всі наступні виклики тієї ж функції працюють з уже наявними елементами.

Це дуже корисно, оскільки ви можете створювати нескінченні списки без помилок пам'яті. Список буде великим, скільки саме ви просили. Звичайно, якщо ваша програма працює з великими колекціями даних, це може призвести до обмеження пам'яті у використанні цих нескінченних списків.

З іншого боку, основний струм є подвійним для рекурсії. Що це означає? Так само, як і рекурсивні функції, які виражаються в термінах самих себе, основні струмові змінні виражаються в термінах самих себе.

Найкраще це виражено на прикладі.

Скажімо, ми хочемо список усіх простих чисел ...


1
сайт блогу мертвий.
Джейсон Ху
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.