체크리스트
✅ 해결 🟩 미해결 🟩 포기
풀이에 걸린 시간: 2시간
- 풀이가 떠오른 과정
🟩 문제를 읽고 주어진 시간제한과 메모리제한 내에서의 풀이가 떠올랐다.
✅ 문제를 읽고 시간제한과 메모리제한 내에서 해결이 가능한 지는 모르겠지만 풀이가 떠올랐다.
🟩 문제를 읽고 풀이가 떠오르지 않았다.
🟩 문제가 곧바로 이해되지 않았다.
- 풀이 작성 과정
✅ 풀이를 아무런 도움 없이 작성하였다.
🟩 풀이를 관련된 알고리즘 지식만을 찾아 숙지한 채 작성하였다.
🟩 풀이를 남의 정답코드를 찾아 참고하여 작성하였다.
🟩 알고리즘 개념에 대한 공부를 하다 해당 개념에 대한 문제를 찾았고, 풀었다.
모든 노드를 순회하며 들어오는 간선이 하나도 없는 노드를 루트로 지정하고, 만약 그 이후에도 들어오는 간선이 하나도 없는 노드를 발견하면, case false를 출력하고 case를 종료한다.
또한 들어오는 간선이 하나도 없는 노드가 단 한 개도 존재하지 않는다면, case false를 출력하고 case를 종료한다.
모든 노드를 순회하며 루트 이외의 노드에 대하여 들어오는 간선이 하나 밖에 존재하지 않는지, 루트 노드에 대하여 들어오는 간선이 존재하지 않는지를 확인하여 이를 하나라도 어기면 case false를 출력하고 case를 종료한다.
1번과 2번을 만족하면 '완전한 트리'와 '그래프 중 일부가 트리이고 그 트리에 간선으로 연결되지 않은 정점의 집합으로 이루어진 그래프'. 두 가지로 분류되는데, 후자를 분류하기 위하여 일단 트리를 선언해 루트부터 bfs로 트리의 모든 노드를 순회하여 만약 순회할 때 한 번이라도 방문하지 않은 노드가 존재한다면 후자라고 판단해 case false를 출력하고 case를 종료한다.
이 문제에서는 노드가 없는 빈 그래프도 트리이다. 다음은 여러 예외처리를 위해서 활용했던 테스트케이스이다.
1 2
2 3
3 2
4 5
0 0
0 0
1 2 2 1 0 0
1 2 0 0
1 1 0 0
6 8 5 3 5 2 6 4
5 6 0 0
8 1 7 3 6 2 8 9 7 5
7 4 7 8 7 6 0 0
3 8 6 8 6 4
5 3 5 6 5 2 0 0
-1 -1
Case 1 is not a tree.
Case 2 is a tree.
Case 3 is not a tree.
Case 4 is a tree.
Case 5 is not a tree.
Case 6 is a tree.
Case 7 is a tree.
Case 8 is not a tree.
from collections import deque
import sys;rl=sys.stdin.readline
caseCnt = 0
Lines = dict()
class Node():
def __init__(self,item:int):
self.item = item
self.canGoTo = []
self.canComeFrom = []
self.canFromRoot = False
class Tree():
def __init__(self,root:Node):
self.root = root
def levelorder(self,n:Node):
if n != None:
queue = deque()
queue.append(n)
while queue:
tmp = queue.popleft()
if tmp.canFromRoot == False:
tmp.canFromRoot = True
for item in tmp.canGoTo:
global nodes
queue.append(nodes[item])
while 1:
INP = rl().rstrip()
# -1 -1이 입력된 경우 입력종료
if INP == '-1 -1':
break
# 빈 줄이 입력된 경우 무시
elif INP == '':
continue
caseEnd = 0
inputLine = list(map(int,INP.split()))
# 입력으로 0 0이 들어올 때까지 간선 내용 저장
for i in range(0,len(inputLine),2):
if inputLine[i] == 0 and inputLine[i+1] == 0:
caseEnd = 1
break
elif inputLine[i] in Lines:
Lines[inputLine[i]].append(inputLine[i+1])
else:
Lines[inputLine[i]] = [inputLine[i+1]]
# 입력으로 0 0이 들어온 경우 case 결과 출력 후 입력 초기화
if caseEnd == 1:
caseCnt += 1
# key값이 node의 item인 node dict 생성
nodes = dict()
for key in Lines.keys():
#입력 내에서 간선의 출발점이 되는 정점을 노드로 추가
if not key in nodes.keys():
nodes[key] = Node(key)
for value in Lines[key]:
#입력 내에서 간선의 도착점이 되는 정점을 노드로 추가
if not value in nodes.keys():
nodes[value] = Node(value)
#정점으로 들어오는 간선 정보를 도착정점 노드에 추가
nodes[value].canComeFrom.append(key)
#정점에서 나가는 간선 정보를 출발정점 노드에 추가
nodes[key].canGoTo.append(value)
root = None
isTree = True
for nodeItem in nodes.keys():
# 1. 들어오는 간선이 하나도 없는 단 하나의 노드가 존재한다. 이를 root노드라 부른다.
if len(nodes[nodeItem].canComeFrom) == 0:
if root == None:
root = nodeItem
# 만약 들어오는 간선이 하나도 없는 노드를 찾았는데 root가 이미 있으면, case를 false로 종료한다.
else:
isTree = False
break
# 만약 들어오는 간선이 하나도 없는 노드가 없다면, case를 false로 종료한다.
if root == None:
isTree = False
if isTree:
for nodeItem in nodes.keys():
# 2. 루트 노드를 제외한 모든 노드는 반드시 단 하나의 들어오는 간선이 존재한다.
if len(nodes[nodeItem].canComeFrom) != 1 and nodeItem != root:
isTree = False
break
# 루트 노드로 들어오는 간선이 존재하면, case를 false로 종료한다.
if len(nodes[root].canComeFrom) > 0 :
isTree = False
# 3. 루트에서 다른 노드로 가는 경로는 반드시 가능하며, 유일하다. 이는 루트를 제외한 모든 노드에 성립해야 한다.
# 1번과 2번을 만족하면, 일단 트리 형태를 만들 수 있는데, 3번 형태가 만족되지 않는 것은 트리에서 연결되지 않은 노드가 존재하지 않는다는 뜻이기 때문에, 트리를 순회하여 Node의 방문하게 되면 canFromRoot을 True값으로 변경하고, 순회가 종료된 뒤에 canFromRoot가 False인 Node가 존재하면 트리가 아니라고 출력한다.
if not any(Lines):
isTree = True
if isTree and any(Lines):
tree = Tree(nodes[root])
tree.levelorder(nodes[root])
for nodeItem in nodes.keys():
if nodes[nodeItem].canFromRoot == False:
isTree = False
break
#for nodeItem in nodes.keys():
# print(nodes[nodeItem].item,nodes[nodeItem].canGoTo,nodes[nodeItem].canComeFrom,nodes[nodeItem].canFromRoot)
if isTree:
print('Case ' + str(caseCnt) + ' is a tree.')
else:
print('Case ' + str(caseCnt) + ' is not a tree.')
Lines = dict()