• toc {:toc}

What is Recursion?

  • 재귀(Recursion) : 함수 안에서 자기 함수를 다시 호출하는 방식을 한다.

    • 직접적 : 자기 자신을 직접 부르는 재귀적 구조
    • 간접적 : 두 함수 이상이 연결되어 서로를 호출하는 재귀적인 구조
  • 함수는 호출 시 메모리의 스택 부분에 위치한다.

  • 재귀를 사용할 경우 함수가 반복되어 사용되기 때문에 Stack overflow를 주의해야 한다.

  • 재귀는 반복보다 효율적이지 않은 경우가 많지만, 간단하고 코드가 직관적이기 때문에 사용한다.

Structure of Recursion

  • Base case : 비재귀적인 상태가 될 수 있도록 하는 경우이다. 재귀의 종료, 반환 조건이 된다.
  • General (recursive) case : 자기 자신의 더 작은 버전으로 호출할 수 있는 조건이다.
  • 재귀 알고리즘은 반복적으로 함수를 호출할수록 문제가 더 작아지는 구조로 표현되어야 한다.
int Factorial(int number){
	if (number == 0)
		return 1;
	else
		return number * Factorial(number-1);
}
// 시간 복잡도 : O(N)
  • 파스칼의 공식을 사용해서 조합도 재귀적으로 표현할 수 있다.
int Combinations(int n, int k){
	if (k==1)
		return n;
	else if (n==k)
		return 1;
	else
		return Combination(n-1, k) + Combination(n-1, k-1);
}
template<class ItemType>
bool BinarySearch(ItemType)(ItemType info[], ItemType item,
							int fromLoc, int toLoc){
	int mid;
	if (fromLoc > toLoc)
		return false;
	else{
		mid = (fromLoc + toLoc) / 2;
		if (info[mid] == item)
			return true;
		else if (item < info[mid])
			return BinarySearch(info, item, fromLoc, mid-1);
		else
			return BinarySearch(info, item, mid+1, toLoc);
	}
}
  • Sorted List의 재귀적 InsertItem
template <class ItemType>
void Insert(NodeType<ItemType>* &location, ItemType item){
	if ((location==NULL) || (item < location->info)){
		NodeType<ItemType>* tempPtr = location;
		location = new NodeType<ItemType>;
		location->info = item;
		location->next = tempPtr;
	}
	else
		Insert(location->next, newItem);
}
 
template <class ItemType>
void SortedType<ItemType>::InsertItem(ItemType newItem){
	Insert(listData, newItem);
}
  • InsertItem을 하면 이전 포인터의 다음 값이 insert item, insert item의 다음 값을 현재 값으로 지정해줘야 한다. 즉, 2번 변경해야 한다.
  • 하지만 위의 경우, locationnext = tempPtr 를 통해 포인터 하나만 변경해준다. 왜 그럴까?
    • &location 포인터의 주소를 말한다.
  • locationnext가 파라미터로 들어가고, locationnext에 new로 동적할당을 받기 때문에 새로 생긴 노드가 new, new의 이전 노드가 prev라면 prevnext가 이미 new를 가리키고 있게 된다.
template <class ItemType>
void Delete(NodeType<ItemType>* &location, ItemType item){
	if(item == location->info)) {
		NodeType<ItemType>* tempPtr = location;
		location = location->next;
		delete tempPtr;
	}
	else
		Delete(location->next, item);
}
 
template <class ItemType>
void SortedType<ItemType>::DeleteItem(ItemType item){
	Delete(listData, item);
}
  • Tail Recursion : 함수의 마지막 줄에 재귀 호출이 포함된 구조
  • Tail Recursion은 반복문으로 쉽게 대체할 수 있다.
  • 재귀 호출은 함수의 파라미터와 지역 변수를 저장해야 하기 때문에 activation record로 지니고 있어야 한다. 하지만 Tail Recursion은 맨 마지막에 재귀 호출을 하고 그 이후 동작이 없기 때문에 이를 저장할 필요가 없어 반복문으로 변경하기 쉽다.
  • Tail Recursion은 하나의 반복문으로 변환할 수 있다.

Recursion vs. Iteration

  • 반복문 : looping 구조를 사용한다.

  • 재귀문 : branching 구조를 사용한다.

  • 재귀는 시간, 공간 측면에서 반복문보다 비효율적이다.

  • 하지만 문제를 간단하게, 직관적으로 풀 수 있고 코드를 쉽게 이해할 수 있다.

  • Combination과 같은 경우 중복되는 계산이 많이 발생하는데 다음과 같은 경우 중복값을 저장해 사용하는 memoization 방식을 사용하여 중복을 줄인다.

  • 재귀의 사용 시점

  • 재귀 호출의 깊이가 문제의 크기에 비해 상대적으로 얕을 경우

  • 재귀가 비재귀적 방식과 비슷한 복잡도를 가질 경

  • 재귀가 비재귀적 방식보다 더 짧고 간단한 경우