문제
이진트리를 다음의 규칙에 따라 행과 열에 번호가 붙어있는 격자 모양의 틀 속에 그리려고 한다. 이때 다음의 규칙에 따라 그리려고 한다.
- 이진트리에서 같은 레벨(level)에 있는 노드는 같은 행에 위치한다.
- 한 열에는 한 노드만 존재한다.
- 임의의 노드의 왼쪽 부트리(left subtree)에 있는 노드들은 해당 노드보다 왼쪽의 열에 위치하고, 오른쪽 부트리(right subtree)에 있는 노드들은 해당 노드보다 오른쪽의 열에 위치한다.
- 노드가 배치된 가장 왼쪽 열과 오른쪽 열 사이엔 아무 노드도 없이 비어있는 열은 없다.
이와 같은 규칙에 따라 이진트리를 그릴 때 각 레벨의 너비는 그 레벨에 할당된 노드 중 가장 오른쪽에 위치한 노드의 열 번호에서 가장 왼쪽에 위치한 노드의 열 번호를 뺀 값 더하기 1로 정의한다. 트리의 레벨은 가장 위쪽에 있는 루트 노드가 1이고 아래로 1씩 증가한다.
아래 그림은 어떤 이진트리를 위의 규칙에 따라 그려 본 것이다. 첫 번째 레벨의 너비는 1, 두 번째 레벨의 너비는 13, 3번째, 4번째 레벨의 너비는 각각 18이고, 5번째 레벨의 너비는 13이며, 그리고 6번째 레벨의 너비는 12이다.
우리는 주어진 이진트리를 위의 규칙에 따라 그릴 때에 너비가 가장 넓은 레벨과 그 레벨의 너비를 계산하려고 한다. 위의 그림의 예에서 너비가 가장 넓은 레벨은 3번째와 4번째로 그 너비는 18이다. 너비가 가장 넓은 레벨이 두 개 이상 있을 때는 번호가 작은 레벨을 답으로 한다. 그러므로 이 예에 대한 답은 레벨은 3이고, 너비는 18이다.
임의의 이진트리가 입력으로 주어질 때 너비가 가장 넓은 레벨과 그 레벨의 너비를 출력하는 프로그램을 작성하시오
입력
첫째 줄에 노드의 개수를 나타내는 정수 N(1 ≤ N ≤ 10,000)이 주어진다. 다음 N개의 줄에는 각 줄마다 노드 번호와 해당 노드의 왼쪽 자식 노드와 오른쪽 자식 노드의 번호가 순서대로 주어진다. 노드들의 번호는 1부터 N까지이며, 자식이 없는 경우에는 자식 노드의 번호에 -1이 주어진다.
출력
첫째 줄에 너비가 가장 넓은 레벨과 그 레벨의 너비를 순서대로 출력한다. 너비가 가장 넓은 레벨이 두 개 이상 있을 때에는 번호가 작은 레벨을 출력한다.
예제 입력 1 복사
19 1 2 3 2 4 5 3 6 7 4 8 -1 5 9 10 6 11 12 7 13 -1 8 -1 -1 9 14 15 10 -1 -1 11 16 -1 12 -1 -1 13 17 -1 14 -1 -1 15 18 -1 16 -1 -1 17 -1 19 18 -1 -1 19 -1 -1
예제 출력 1 복사
3 18
어찌저찌 풀다보니 코드가 130줄이 나와버렸다.
문제는 네 가지 조건에 의해 트리를 행과 열로 나뉜 격자에 그릴 때, 너비가 최대가 되는 레벨을 구하는 것이다.
열의 번호는 트리의 left 서브트리 - current 노드 - right 서브트리 순으로 부여한다.
이 순서를 보면 딱 떠오르는 순회방식이 있다. 바로 중위 순회(Inorder)다.
중위 순회란 트리의 여러 순회 방식 중 하나로 leftChild - rootNode - rightChild 순으로 방문하는 방식이다.
이제 우리가 할 일은, 주어진 입력대로 트리를 생성한 후, 중위순회를 하면서 각 노드에 열의 번호를 부여하는 것이다.
이 문제 해결에 매우 중요한 주의할 점이 있다.
1. 루트 노드는 1번 노드가 아니다. 즉 3번 노드가 루트일 수도 있고 랜덤이다.
2. 노드 입력 순서는 1 -> 2 -> 3 -> .. 순으로 주어지지 않는다. 단말 노드가 먼저 나오고 루트 노드가 나올 수도 있다.
2번 때문에 다 푼 문제의 입력부를 다 고쳤다. ㅠㅠ
아무튼 입력을 잘 받고 트리를 제대로 생성하면, 중위 순회만 하면 끝이다.
순회를 마치면 각 레벨별로 최소 열, 최대 열의 번호를 알 수 있다.
따라서 레벨별 최대 너비를 구해 출력하면 끝이다.
다른 블로그의 풀이를 보니 직접 트리를 만들진 않고, 배열을 이용해 잘 풀었던데, 난 왠지 트리는 class를 이용해 제대로 만들고 싶어서 그렇게 풀다보니 풀이가 길어졌다. 다른 풀이를 참고해도 상관없다.
chosh95/STUDY
프로그래밍 문제 및 알고리즘 정리. Contribute to chosh95/STUDY development by creating an account on GitHub.
github.com
C++ 코드
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
int N;
int minLevel[10001]; // 해당 레벨에서 최소 열
int maxLevel[10001]; // 해당 레벨에서 최대 열
int cur = 1; // inorder할 때 열을 기록해줄 변수
int levelCnt; // 최대 레벨을 기록하는 변수
int leftChild[10001]; // i번 노드의 왼쪽 자식
int rightChild[10001]; // i번 노드의 오른쪽 자식
int parentNum[10001]; // i번 노드의 부모 노드
struct Node {
int data;
int level;
Node* leftNode;
Node* rightNode;
Node(int data, int level, int left, int right) { //생성자
this->data = data;
this->level = level;
levelCnt = max(level, levelCnt);
if (left == -1) this->leftNode = NULL;
else this->leftNode = new Node(left, level + 1, -1 , -1);
if (right == -1) this->rightNode = NULL;
else this->rightNode = new Node(right, level + 1, -1, -1);
}
};
class Tree {
Node* root;
public:
Tree(int parent, int left, int right) { // 트리 생성자
this->root = new Node(parent, 1, left, right);
}
void insert(int parent, int left, int right) { // insert의 helper 함수
insert(root, parent, left, right);
}
void insert(Node* currentNode, int parent, int left, int right) { // 트리에 노드 삽입하는 함수
if (currentNode == NULL) return;
if (currentNode->data == parent) { // 삽입할 노드를 찾았으면 left, right 생성
if (left == -1) currentNode->leftNode = NULL;
else currentNode->leftNode = new Node(left, currentNode->level + 1, -1, -1);
if (right == -1) currentNode->rightNode = NULL;
else currentNode->rightNode = new Node(right, currentNode->level + 1, -1, -1);
}
else { // 삽입할 곳을 찾아 재귀 호출
insert(currentNode->leftNode, parent, left, right);
insert(currentNode->rightNode, parent, left, right);
}
}
void inorder() { //inorder의 helper 함수
inorder(root);
}
void inorder(Node* currentNode) { // 중위순회
if (currentNode == NULL) return;
inorder(currentNode->leftNode); // left 방문
minLevel[currentNode->level] = min(minLevel[currentNode->level], cur); // 현재 방문
maxLevel[currentNode->level] = max(maxLevel[currentNode->level], cur); // 현재 레벨의 최소, 최대 열을 기록한다.
cur++; // 열 증가
inorder(currentNode->rightNode); // right 방문
}
};
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> N;
for (int i = 1; i <= N; i++) {
minLevel[i] = 987654321; // min배열 최대값으로 초기화
maxLevel[i] = 0;
}
for (int a, b, c, i = 1; i <= N; i++) {
cin >> a >> b >> c;
leftChild[a] = b; //a의 left, rightChild 기록
rightChild[a] = c;
if (b != -1) parentNum[b] = a; // b, c가 NULL이 아니면 a가 부모노드
if (c != -1) parentNum[c] = a;
}
int rootNum = 0; // root 노드가 몇번인지 검색
for (int i = 1; i <= N; i++) {
if (parentNum[i] == 0) {
rootNum = i;
break;
}
}
queue<int> q; // 트리에 삽입할 노드를 차례대로 저장하는 큐
Tree t(rootNum, leftChild[rootNum], rightChild[rootNum]); //루트 노드를 이용해 트리 생성
if (leftChild[rootNum] != -1) q.push(leftChild[rootNum]); // left, right가 NULL이 아니면 삽입
if (rightChild[rootNum] != -1) q.push(rightChild[rootNum]);
while (!q.empty()) {
int cur = q.front();
q.pop();
t.insert(cur, leftChild[cur], rightChild[cur]); // left, right를 cur에 삽입
if (leftChild[cur] != -1) q.push(leftChild[cur]); // 다음에 삽입할 노드 삽입
if (rightChild[cur] != -1) q.push(rightChild[cur]);
}
t.inorder(); // 중위 순회
int resLevel = 1, resWidth = 1; // 결과 레벨, 너비
for (int i = 1; i <= levelCnt; i++) {
if (resWidth < maxLevel[i] - minLevel[i] + 1) {
resWidth = maxLevel[i] - minLevel[i] + 1;
resLevel = i;
}
}
cout << resLevel << " " << resWidth;
}
'알고리즘 > 백준' 카테고리의 다른 글
[백준] 좋은 단어 (3986번) (0) | 2020.08.21 |
---|---|
[백준] 수들의 합 (2268번) (0) | 2020.08.20 |
[백준] ACM Craft (1005번) (0) | 2020.08.20 |
[백준] 음악프로그램 (2623번) (0) | 2020.08.20 |
[백준] 저울 (10159번) (0) | 2020.08.19 |