반복문(Iteration)은 프로그램 내에서 특정 코드 블록을 조건이 만족하는 동안 반복적으로 실행하기 위한 프로그래밍 구조다. 반복문은 코드의 재사용을 통해 작업을 간소화하고, 프로그램의 효율성을 높이는 데 중요한 역할을 한다. 주로 데이터의 집합을 처리하거나, 특정 조건을 만족할 때까지 반복 작업을 수행할 때 사용된다.
반복문은 조건이 항상 참이 되어 반복문이 종료되지 않는 상황. 프로그램의 의도하지 않은 동작이나 성능 문제를 일으킬 수 있다. 그리고 break를 사용하여 반복을 즉시 종료하거나, continue를 사용하여 현재 반복을 건너뛰고 다음 반복으로 넘어갈 수 있다.
for 반복문은 정해진 범위나 순서가 있는 데이터 집합을 순회할 때 사용된다. 반복의 시작과 끝이 명확할 때 주로 사용한다. 리스트의 각 요소에 대해 반복하거나, 정해진 횟수만큼 반복 작업을 수행한다.
while 반복문은 조건이 참(True)인 동안 코드 블록을 반복 실행한다. 반복 횟수가 불명확하거나, 특정 조건에 따라 반복을 종료해야 할 때 유용하다. 사용자로부터 입력을 받아 처리하는 동안 계속 실행하거나, 특정 상태가 변경될 때까지 반복한다. while 반복문 내에서는 조건이 결국 거짓이 될 수 있도록 조건에 영향을 미치는 변수를 적절히 변경해야 한다.
재귀(Recursion)는 함수가 자기 자신을 호출하는 방식을 말한다. 재귀를 사용하면 복잡한 문제를 간단한 부분 문제로 나누어 해결할 수 있으며, 특히 반복적인 계산을 간결하게 표현할 수 있다. 재귀는 컴퓨터 과학에서 매우 중요한 개념으로, 다양한 알고리즘과 데이터 구조(예: 정렬 알고리즘, 트리와 그래프의 탐색)에서 널리 사용된다.
기저 조건(Base Case)은 재귀 호출을 중단하고, 함수가 자기 자신을 더 이상 호출하지 않도록 하는 조건. 이는 재귀의 종료 조건이다. 기저 조건을 제대로 설정하지 않으면 무한 재귀 호출로 이어져 스택 오버플로우(Stack Overflow)가 발생할 수 있다.
재귀는 문제를 더 작은 단위로 나누어 접근하기 때문에, 분할 정복(Divide and Conquer) 알고리즘을 구현할 때 자연스럽게 적용된다. 꼬리 재귀 최적화(Tail Recursion Optimization)와 같은 기법을 사용하여 재귀 함수의 성능을 개선할 수 있다.
꼬리 재귀 최적화(Tail Recursion Optimization, TRO)는 재귀 호출을 효율적으로 처리하는 컴파일러 또는 인터프리터의 기능이다. 이 최적화는 재귀 함수의 마지막 동작이 함수 자신의 호출일 때, 즉 재귀 호출이 꼬리 위치(tail position)에 있을 때 적용할 수 있다. 꼬리 재귀 함수는 현재의 함수 프레임을 재사용하여 새로운 함수 호출을 수행함으로써, 호출 스택의 크기를 증가시키지 않고 재귀 호출을 실행한다. 이로 인해 메모리 사용량이 감소하고, 성능이 향상될 수 있다.
# 꼬리 재귀를 사용하지 않는 팩토리얼 함수
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n - 1)
# 꼬리 재귀를 사용하는 팩토리얼 함수
def factorial_tail_recursive(n, accumulator=1):
if n == 1:
return accumulator
else:
return factorial_tail_recursive(n - 1, n * accumulator)
위 예시에서 factorial_tail_recursive 함수는 꼬리 재귀를 사용한다. 계산 결과를 누적하는 accumulator 인자를 추가하여, 마지막 호출에서 바로 결과를 반환하도록 설계되었다. 이러한 방식으로 함수가 자기 자신을 호출할 때마다 새로운 스택 프레임을 생성하지 않고 기존의 스택 프레임을 재사용할 수 있어, 메모리 사용량이 줄어든다.
깊은 복사(deep copy)는 객체와 그 객체가 포함하는 모든 자식 객체들까지 완전히 새로운 복사본을 생성하는 방식이다. 이는 원본 객체와 복사본 객체 사이에 서로 영향을 주지 않으려 할 때 사용한다. 반면, 얕은 복사(shallow copy)는 객체의 최상위 레벨만 복사하여 새 객체를 생성하고, 내부의 객체들은 원본 객체와 동일한 참조를 공유한다. 따라서 내부 객체가 변경되면 얕은 복사본도 영향을 받는다.
깊은 복사는 복잡한 객체 구조를 완전히 독립적으로 복제할 때 사용한다. 예를 들어, 객체 내의 리스트나 딕셔너리 같은 다른 객체를 포함하는 경우가 해당한다.
얕은 복사는 객체 구조가 간단하거나, 내부 객체들을 공유해도 문제가 없을 때 유용하다.
import copy
# 깊은 복사 예시
obj = [1, [2, 3], 4]
deep_copied_obj = copy.deepcopy(obj)
deep_copied_obj[1][0] = '변경'
# 원본 obj는 변경되지 않음
print(obj)
# 얕은 복사 예시
shallow_copied_obj = copy.copy(obj)
shallow_copied_obj[1][0] = '변경'
# 원본 obj의 내부 리스트도 변경됨
print(obj)
#출력 :
#[1, [2, 3], 4]
#[1, ['변경', 3], 4]
위 코드에서 deepcopy() 함수는 obj 객체의 깊은 복사본을 생성하여 deep_copied_obj에 할당한다. 내부 리스트를 변경해도 원본 obj는 영향을 받지 않는다. 반면, copy() 함수로 생성한 얕은 복사본 shallow_copied_obj에서 내부 리스트를 변경하면, 원본 obj의 해당 리스트도 함께 변경된다.
'CS > 메모리 관리' 카테고리의 다른 글
Page table, Swapping, Page Fault (1) | 2024.03.27 |
---|---|
Cache, TLB, MMU, 가상메모리 (0) | 2024.03.27 |
Register, RAM, ROM, Disk (0) | 2024.03.26 |
Call by Value vs Call by Reference, Garbage Collection (1) | 2024.03.23 |
Malloc-lab 구현(first-fit, next-fit, best-fit) (1) | 2024.03.23 |