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์์๋ ์ฌ๊ท ํจ์์ ์ ํ์ ํผํ ์ ์์ต๋๋ค.
์ด๋ฐ ๊ฐ๋จํ ์์ด ์๋ ๋ณต์กํ ์ฝ๋๋ผ๋ฉด, ์ฝ๋๊ฐ ์ฝ๊ฐ ๋ ๊ธธ์ด์ง๊ณ , ์ดํดํ๊ธฐ ์ด๋ ค์ธ ์ ์์ต๋๋ค.
'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 |