3.2. Recursion - 3.2.1 Tail Recursion

2024. 1. 21. 23:05ใ†CS - Roadmap.sh/3. Common Algorithms

CS - 3. Common Algorithms - 3.2. Recursion - 3.2.1. Tail Recursion

 

 

3.2 Recursion

๐Ÿ’ก Recursion is a method of solving problems where the solution depends on solutions to smaller instances of the same problem. A recursive algorithm must have a base case. A recursive algorithm calls itself, recursively.

 

์žฌ๊ท€๋Š” ๋™์ผํ•œ ๋ฌธ์ œ์˜ ์ž‘์€ ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…์— ๋”ฐ๋ผ ํ•ด๊ฒฐ์ฑ…์ด ๋‹ฌ๋ผ์ง€๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์žฌ๊ท€ ์•Œ๊ณ ๋ฆฌ์ฆ˜์—๋Š” ๊ธฐ๋ณธ ์‚ฌ๋ก€๊ฐ€ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์žฌ๊ท€ ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ์Šค์Šค๋กœ๋ฅผ ์žฌ๊ท€์ ์œผ๋กœ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

 

3.2.1 Tail Recursion

๐Ÿ’ก Tail recursion is a special kind of recursion where the recursive call is the very last thing in the function. It’s a function that does not do anything at all after recursing.

 

๊ผฌ๋ฆฌ ์žฌ๊ท€๋Š” ์žฌ๊ท€ ํ˜ธ์ถœ์ด ํ•จ์ˆ˜์˜ ๋งจ ๋งˆ์ง€๋ง‰์— ์žˆ๋Š” ํŠน๋ณ„ํ•œ ์ข…๋ฅ˜์˜ ์žฌ๊ท€์ž…๋‹ˆ๋‹ค. ์žฌ๊ท€ ์ดํ›„์—๋Š” ์•„๋ฌด ์ž‘์—…๋„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

 

์žฌ๊ท€ (Recursion) ์ž…๋‹ˆ๋‹ค.์žฌ๊ท€๋Š” ํ•จ์ˆ˜๊ฐ€ ์ž์‹ ์„ ๋‹ค์‹œ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.์ผ๋ฐ˜์ ์ธ ์žฌ๊ท€์—์„œ๋Š” ํ•จ์ˆ˜๊ฐ€ ์ž์‹ ์„ ํ˜ธ์ถœํ•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์ด์šฉํ•ด ๊ณ„์‚ฐ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

 

Tail Recursion ๊ณผ Non-Tail Recursion์œผ๋กœ ํฌ๊ฒŒ ๋‚˜๋ˆ ๋ณผ์ˆ˜ ์žˆ๋Š”๋ฐ, Tail Recursion์€ ์žฌ๊ท€ ํ˜ธ์ถœ์ด ํ•จ์ˆ˜์˜ ๋งˆ์ง€๋ง‰ ์—ฐ์‚ฐ์ด ๋˜๋Š” ๊ฒฝ์šฐ๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค.์ฆ‰, ์žฌ๊ท€ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€๊ณตํ•˜์ง€ ์•Š๊ณ  ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฐ ํŠน์„ฑ ๋•Œ๋ฌธ์— Tail Recursion์€ ๋ฐ˜๋ณต๋ฌธ๊ณผ ์œ ์‚ฌํ•œ ์„ฑ์งˆ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

 

 

In Python

ํŒŒ์ด์ฌ์—์„œ tail recursion๊ตฌํ˜„์˜ ์˜ˆ์‹œ๋กœ ํŒฉํ† ๋ฆฌ์–ผ ๊ณ„์‚ฐ ํ•จ์ˆ˜๋ฅผ ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

def factorial(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n-1)

์œ„์˜ ํ•จ์ˆ˜๋Š” ์ผ๋ฐ˜์ ์ธ ์žฌ๊ท€๋ฅผ ํ†ตํ•œ ํŒฉํ† ๋ฆฌ์–ผ ๊ณ„์‚ฐ์ž…๋‹ˆ๋‹ค.

์ด๋ฅผ Tail Recursion์œผ๋กœ ๋ฐ”๊พธ๋ ค๋ฉด, ์ถ”๊ฐ€์ ์ธ ์ธ์ž๋ฅผ ์ด์šฉํ•˜์—ฌ ์ด์ „๊นŒ์ง€์˜ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ์ธ์ž๋กœ ๋„˜๊ธฐ๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

def factorial_tail(n, acc=1):  # ์ธ์ž์˜ ๊ฐ’์— = ๋กœ ์„ค์ •ํ•ด์ฃผ๋ฉด, ์ธ์ž์˜ defautl ๊ฐ’์€ = ๋กœ ์„ค์ •ํ•ด์ค€ ๊ฐ’ ์ž…๋‹ˆ๋‹ค.
    if n == 1:
        return acc
    else:
        return factorial_tail(n-1, n*acc)

acc๋ผ๋Š” ์ถ”๊ฐ€ ์ธ์ž๋ฅผ ์ด์šฉํ•ด ์ด์ „๊นŒ์ง€์˜ ๊ฒŒ์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ๊ฒŒ์†ํ•ด์„œ ๋„˜๊ธฐ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์žฌ๊ท€ ํ˜ธ์ถœ์ด ๋งˆ์ง€๋ง‰ ์—ฐ์‚ฐ์ด ๋˜๋ฏ€๋กœ Tail Recursion์ด ๋ฉ๋‹ˆ๋‹ค.

 

 

Tail Recursion์„ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” ํฌ๊ฒŒ ๋‘๊ฐ€์ง€ ์ž…๋‹ˆ๋‹ค.

 

1. ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ 

- Tail Recursion์€ ์ผ๋ฐ˜์ ์ธ ์žฌ๊ท€๋ณด๋‹ค ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•ด์ง€๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

 

2. ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ

- ์ผ๋ฐ˜์ ์ธ ์žฌ๊ท€๋Š” ํ˜ธ์ถœํ•  ๋•Œ๋งˆ๋‹ค ์Šคํƒ์— ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์Œ“์•„๊ฐ€๊ธฐ ๋•Œ๋ฌธ์—, ํ˜ธ์ถœ ํšŸ์ˆ˜๊ฐ€ ๋งŽ์•„์ง€๋ฉด ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด Tail Recursion์€ ์ปดํŒŒ์ผ๋Ÿฌ๋‚˜ ์ธํ„ฐํ”„๋ฆฌํ„ฐ๊ฐ€ ์ตœ์ ํ™”๋ฅผ ํ†ตํ•ด ๋ฐ˜๋ณต๋ฌธ์ฒ˜๋Ÿผ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ Python์—์„œ๋Š” Tail Recursion์˜ ํ˜ธ์ถœ ์ตœ์ ํ™”๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ๋•Œ๋ฌธ์— ์žฌ๊ท€๋ฅผ ๋„ˆ๋ฌด ๊นŠ๊ฒŒ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ( appendix ์— ์ถ”๊ฐ€์„ค๋ช…)

 

 

 

Appendix (Recursion ๊ณผ memory)

 

์žฌ๊ท€ ํ˜ธ์ถœ์ด ๋ฐœ์ƒํ•˜๋ฉด, ๊ฐ ํ˜ธ์ถœ๋งˆ๋‹ค ๋ฉ”๋ชจ๋ฆฌ์— ์Šคํƒ ํ”„๋ ˆ์ž„์ด ์ƒˆ๋กญ๊ฒŒ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ด ์Šคํƒ ํ”„๋ ˆ์ž„์—๋Š” ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜, ์ง€์—ญ ๋ณ€์ˆ˜, ๋ฐ˜ํ™˜ ์ฃผ์†Œ ๋“ฑ์ด ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์žฌ๊ท€ ํ˜ธ์ถœ์ด ๊นŠ์–ด์งˆ์ˆ˜๋ก ์ด๋Ÿฌํ•œ ์Šคํƒ ํ”„๋ ˆ์ž„์ด ๊ณ„์†ํ•ด์„œ ์Œ“์ด๊ฒŒ ๋˜๋Š”๋ฐ, ์ด๋ ‡๊ฒŒ ์Šคํƒ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๊ณ„์† ์ฆ๊ฐ€ํ•˜๋‹ค๊ฐ€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์Šคํƒ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ชจ๋‘ ์†Œ์ง„ํ•˜๋ฉด '์Šคํƒ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ' ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

์ผ๋ฐ˜์ ์œผ๋กœ ๊ผฌ๋ฆฌ ์žฌ๊ท€(Tail Recursion)๋Š” ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.

ํ•จ์ˆ˜์˜ ์žฌ๊ท€ ํ˜ธ์ถœ์ด ๋งˆ์ง€๋ง‰ ์—ฐ์‚ฐ์ด ๋˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ˜•ํƒœ์˜ ์žฌ๊ท€๋ฅผ ๊ผฌ๋ฆฌ ์žฌ๊ท€๋ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ํŠน์„ฑ ๋•๋ถ„์—, ํ…Œ์ผ ์žฌ๊ท€๋ฅผ ์ง€์›ํ•˜๋Š” ์–ธ์–ด์˜ ์ปดํŒŒ์ผ๋Ÿฌ๋‚˜ ์ธํ„ฐํ”„๋ฆฌํ„ฐ๋Š” ์Šคํƒ์„ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜๋Š” ๋Œ€์‹  ๊ธฐ์กด์˜ ์Šคํƒ์„ ์žฌํ™œ์šฉํ•˜๋Š” ์ตœ์ ํ™”๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ 'ํ…Œ์ผ ํ˜ธ์ถœ ์ตœ์ ํ™”(Tail Call Optimization)'๋ผ๊ณ  ๋ถ€๋ฅด๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ์Šคํƒ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ Python์€ ์ด๋Ÿฌํ•œ ํ…Œ์ผ ํ˜ธ์ถœ ์ตœ์ ํ™”๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” Python์˜ ์„ค๊ณ„ ์ฒ ํ•™ ์ค‘ ํ•˜๋‚˜์ธ '์ฝ๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ'์— ๋ถ€ํ•ฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ํ…Œ์ผ ํ˜ธ์ถœ ์ตœ์ ํ™”๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๊ฐ€ ์‹ค์ œ ์‹คํ–‰ ํ๋ฆ„์„ ์ •ํ™•ํžˆ ๋ฐ˜์˜ํ•˜์ง€ ์•Š๊ฒŒ ๋˜์–ด ๋””๋ฒ„๊น…์ด ์–ด๋ ค์›Œ์ง€๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

 

def sum(n):
    if n == 0:
        return 0
    else:
        return n + sum(n - 1)

sum(4) # 10

 

์ด๋Ÿฐ ์ผ๋ฐ˜์  ์žฌ๊ท€ ํ•จ์ˆ˜๋กœ ํ”ผ๋ณด๋‚˜์น˜ ์ˆ˜์—ด์„ ๊ตฌํ•œ๋‹ค๊ณ  ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

 

์œ„ ํ•จ์ˆ˜๋Š” 

sum(4)
4 + sum(3)
4 + (3 + sum(2))
4 + (3 + (2 + sum(1)))
4 + (3 + (2 + (1 + sum(0))))
4 + (3 + (2 + (1 + 0)))
4 + (3 + (2 + 1))
4 + (3 + 3)
4 + 6
10

์ด๋Ÿฌํ•œ ๊ณผ์ •์œผ๋กœ ์—ฐ์‚ฐ์ด ๋˜๋ฉฐ stack frame 

ํ•จ์ˆ˜ ๋‚ด์—์„œ ๋‘ ๋ฒˆ์˜ ํ˜ธ์ถœ์ด ์ผ์–ด๋‚˜์„œ 2^n๊ฐœ์˜ stack fram์ด ์Œ“์ž…๋‹ˆ๋‹ค.

 

๋ฐ˜๋ฉด tail recursion์œผ๋กœ ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•ด ๋ด…๋‹ˆ๋‹ค.

def sum_tail_recursion(n, acc):
    return acc if n == 0 else sum_tail_recursion(n - 1, acc + n)

sum_tail_recursion(4, 0) # ๊ฒฐ๊ณผ๊ฐ’ 10

 

์œ„ ํ•จ์ˆ˜๋Š”

sum_tail_recursion(4, 0)
sum_tail_recursion(3, 4)
sum_tail_recursion(2, 7)
sum_tail_recursion(1, 9))
sum_tail_recursion(0, 10)

์ด๋Ÿฐ์‹์œผ๋กœ ์ „๊ฐœ๋˜์–ด์ง‘๋‹ˆ๋‹ค.

 

 javascript engine์ด๋‚˜ jvm, cpython์€ ๊ธฐ๋ณธ์ ์œผ๋กœ tail recursion optimization์„ ์ง€์›ํ•˜์ง€ ์•Š์•„ stack-frame์ด 4๊ฐœ ๊ทธ๋Œ€๋กœ ์ƒ์„ฑ๋˜์ง€๋งŒ gcc๋‚˜ functional programming language๋Š” tail recursion optimization์„ ์ง€์›ํ•˜์—ฌ return ๊ฐ’์„ ๋ฐ›์€ ํ›„ ์ถ”๊ฐ€์ ์œผ๋กœ ํ•ด์•ผํ•  operation์ด ์—†๋Š” ๊ฒฝ์šฐ stack-frame์„ ๋ฎ์–ด์”Œ์›Œ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ  recursion์„ iteration์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.


๊ฒฐ๋ก ์€ Python์—์„œ๋Š” ์žฌ๊ท€๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊นŠ์ด๊ฐ€ ๋„ˆ๋ฌด ๊นŠ์–ด์ง€์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•˜๋ฉฐ, ๊นŠ์ด๊ฐ€ ๊นŠ์€ ์žฌ๊ท€๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” ๋Œ€์ฒด์ ์œผ๋กœ ๋ฐ˜๋ณต๋ฌธ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

 

๋ฐ˜๋ณต๋ฌธ์œผ๋กœ ์œ„์˜ tail_recursion ํ•จ์ˆ˜๋ฅผ ๋ฐ”๊พธ๋ฉด ์ด๋ ‡์Šต๋‹ˆ๋‹ค.

def sum_loop(n, acc):
    while n != 0:
        acc += n
        n -= 1
    return acc

 

sum_loop๋Š” while ๋ฃจํ”„๋ฅผ ๋Œ๋ฉฐ, n์ด 0์ด ๋  ๋•Œ๊นŒ์ง€ acc์— n์„ ๋”ํ•˜๊ณ , n์„ 1์”ฉ ๊ฐ์†Œ์‹œํ‚ต๋‹ˆ๋‹ค.

์ด ๋ฐฉ์‹์€ ์žฌ๊ท€์— ๋น„ํ•ด ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ด ์ข‹๊ณ , Python์—์„œ๋Š” ์žฌ๊ท€ ํ•จ์ˆ˜์˜ ์ œํ•œ์„ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฐ ๊ฐ„๋‹จํ•œ ์‹์ด ์•„๋‹Œ ๋ณต์žกํ•œ ์ฝ”๋“œ๋ผ๋ฉด, ์ฝ”๋“œ๊ฐ€ ์•ฝ๊ฐ„ ๋” ๊ธธ์–ด์ง€๊ณ , ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

728x90

'CS - Roadmap.sh > 3. Common Algorithms' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

3.3. Search - 3.3.1 Binary Search  (0) 2024.01.23
3.2. Recursion - 3.2.2 Non-Tail Recursion  (0) 2024.01.22
3.1. Sorting - 3.1.6 Merge Sort  (0) 2024.01.20
3.1 Sorting - 3.1.5 Quick Sort  (0) 2024.01.16
3.1 Sorting - 3.1.4 Heap Sort  (0) 2024.01.15