정글/알고리즘

Data-Structures Binary Search Tree Q1 - Level Order Traversal 풀이

nkdev 2025. 4. 16. 21:43

* 4/17 발표를 위한 자료

문제 해석 :

트리가 주어졌을 때 노드들을 level별로 출력하라.

 

이 문제를 풀기 전에 두 가지 생각이 들었다.

1. 왜 큐를 사용하라고 했나?

2. 재귀호출로도 구현 가능?

 

이전 문제를 풀 때는 현재 노드의 자식 노드를 방문할 때 재귀 호출을 주로 사용했다.

-> 자식 노드의 자식 노드로 깊이 탐색을 하여 base case에 다다르면 재귀 호출을 종료하는 식으로 top-down 구현

 

그런데 이번 문제에서는 너비 탐색을 해야 해서 재귀를 쓰면 안 된다.

그럼 현재 노드의 자식 노드를 어떻게 방문?

-> 큐에 같은 레벨의 노드들을 한꺼번에 넣어두고

-> 큐에서 하나씩 꺼내면서 그 노드의 자식 노드를 또 다시 큐에 넣기

-> 이 과정을 반복하면 큐의 FIFO 성질에 의해 같은 레벨의 노드들을 먼저 방문할 수 있게 된다.

 

구현 과정 :

작성한 코드 :

void levelOrderTraversal(BSTNode* root)
{
	Queue* q = (Queue*)malloc(sizeof(Queue)); //큐 생성
	enqueue(q->head, q->tail, root); //큐에 root 삽입

	while(q->head!=NULL){ //큐가 빌 때까지 실행
		BSTNode* node = dequeue(q->head, q->tail); //큐에서 노드 하나 꺼내기
		printf("%d ", node->item);
		
		//꺼낸 노드의 자식 노드를 left, right 순서대로 큐에 넣기
		if(node->left != NULL) enqueue(q->head, q->tail, node->left);
		if(node->right != NULL) enqueue(q->head, q->tail, node->right);
	}

}

 

런타임 에러?

 

원인 분석 :

enqueue(), dequeue() 호출부에서 에러가 발생

인자 전달에 문제가 있는 것으로 판단되어 구조체 형태와 함수 프로토타입을 확인함

typedef struct _QueueNode {
	BSTNode *data;
	struct _QueueNode *nextPtr;
}QueueNode; 

typedef struct _queue
{
	QueueNode *head;
	QueueNode *tail;
}Queue;


void enqueue(QueueNode **headPtr, QueueNode **tailPtr, BSTNode *node);
BSTNode* dequeue(QueueNode **headPtr, QueueNode **tailPtr);

q->head, q->tail은 포인터 타입이므로 이러한 오류가 발생

따라서 &(q->head), &(q->tail)로 수정해야 한다.

 

완성된 코드 :

void levelOrderTraversal(BSTNode* root)
{
	Queue* q = (Queue*)malloc(sizeof(Queue)); //큐 생성
	enqueue(&(q->head), &(q->tail), root); //큐에 root 삽입

	while(q->head!=NULL){ //큐가 빌 때까지 실행
		BSTNode* node = dequeue(&(q->head), &(q->tail)); //큐에서 노드 하나 꺼내기
		printf("%d ", node->item);
		
		//꺼낸 노드의 자식 노드를 left, right 순서대로 큐에 넣기
		if(node->left != NULL) enqueue(&(q->head), &(q->tail), node->left);
		if(node->right != NULL) enqueue(&(q->head), &(q->tail), node->right);
	}

}

 

재귀 호출이 안 되는 이유?

void levelOrderTraversal(BSTNode* root){

	printf("%d ", root->item);
	
	levelOrderTraversal(root->left);
	levelOrderTraversal(root->right);

}

재귀 호출은 코드가 이런 식으로 짜여지는데, 왼쪽 노드 탐색이 리프노드에 도달할 때까지 계속 재귀호출한다.

-> 깊이 탐색을 하게 되므로 같은 level에 있는 노드들을 먼저 읽지 못함

 

즉 이 함수는 BFS 탐색을 요구하고 있고 너비 우선 탐색을 할 때는 큐를 사용해야 하는 이유를 알게 되었다.

반면 DFS 탐색 즉 깊이 탐색을 할 때는 재귀 함수 또는 스택을 사용해야 한다는 사실을 알 수 있다.

 

Note:

문제에서 요구하는 것이 level order traversal이었음에도 바로 bfs 탐색이고 큐를 사용해야겠다는 생각이 들지 않은 걸 보면

내가 알고리즘주차 때 완전탐색을 깊이 있게 이해하지 않았구나 하는 생각이 들었다.

 

bfs는 인접한 곳 부터 방문이니까 큐, dfs는 깊이 탐색이니까 스택이라는 정도만 어렴풋이 이해하고 있었고

구현할 때는 그냥 기계적으로 bfs는 큐, dfs는 스택을 사용했다는 것을 깨달았다.

 

이번 문제 풀이를 통해 트리(또는 그래프)에서 완전탐색을 할 때 왜 스택(재귀)과 큐를 써야 하는지에 대해 와닿았다.

'정글 > 알고리즘' 카테고리의 다른 글

calloc에 대해서,,  (3) 2025.04.25
AVL Tree  (2) 2025.04.22
[백준/Java] 11049번 : 행렬 곱셈  (0) 2025.04.10
[백준/Java] 9084번 : 동전  (3) 2025.04.09
[백준/Java] 1904번 : 01타일  (0) 2025.04.08