[Tistory] [알고리즘] 그래프와 트리, 전위순회, 중위순회, 후위순회& 백준(트리 순회)

원글 페이지 : 바로가기

그래프 그래프는 현상이나 사물을 정점(Vertex) 또는 노드(Node), 간선(Edge)으로 표현하기 위해 사용한다. – 노드(Node) : 위치를 말함, 정점(Vertex)라고도 함 – 간선(Edge) : 위치 간의 관계를 표시한 선으로 노드를 연결한 선을 뜻함(Link 또는 Branch라고도 함) – 인접 정점(Adjacent Vertext) : 간선으로 직접 연결된 Vertex(또는 Node) 1) 무방향 그래프(undirected graph) – 방향이 없는 그래프로 간선을 통해 노드는 양방향으로 갈 수 있음 – 보통 노드 A,B가 연결되어 있을 경우 (A,B) 또는 (B,A)로 표기 2) 방향 그래프(directed graph) – 간선에 방향이 있는 그래프로 방향이 가르키는 곳으로만 갈 수 있음 – 노드 A->B로 연결되어 있을 경우 표기 – 노드 B->A로 연결되어 있을 경우 표기 3) 가중치 그래프(weighted graph) 또는 네트워크 – 간선에 비용 또는 가중치가 할당된 그래프 4) 싸이클(cycle) – 단순 경로의 시작 노드와 종료 노드가 동일한 경우 5) 비순환 그래프(acyclic graph) – 싸이클이 없는 그래프 6) 완전 그래프(complete graph) – 그래프의 모든 노드가 서로 연결되어 있는 그래프 그래프와 트리의 차이점 그래프는 노드와 노드를 연결하는 간선으로 표현되는 자료구조이며, 트리는 그래프의 한 종류로서 방향성이 있는 비순환 그래프이다. 보통 트리는 싸이클이 없다고들 많이 얘기한다. 핵심 1. 그래프: 방향 그래프, 무방향 그래프 모두 존재 VS 트리: 방향 그래프만 존재 2. 그래프: 싸이클 가능(순환, 비순환 모두 존재) VS 트리: 싸이클 존재 X 3. 그래프: Root Node가 불필요 VS 트리: Root Node 필요 4. 그래프: 부모 자식 개념 불필요 VS 트리: 부모 자식 관계 반드시 존재 트리 트리의 구조와 관련 용어 – 루트 노드(root node): 부모가 없는 최상위 노드(트리의 가장 위쪽) – 단말 노드(leaf node): 자식이 없는 노드 – 크기(size): 트리에 포함된 모든 노드의 개수 – 깊이(depth): 루트 노드부터의 거리 – 높이(height): 깊이 중 최댓값 – 차수(degree): 각 노드의 (자식 방향) 간선 개수 – 기본적으로 트리의 크기가 N일 때, 전체 간선의 개수 N-1개이다. 트리의 순회(Tree Traversal) – 트리 자료구조에 포함된 노드를 특정한 방법으로 한 번씩 방문하는 방법을 의미한다. 1. Preorder traversal(전위 순회): 노드 자신을 방문 => 왼쪽 자식 노드 방문 => 오른쪽 자식 노드 방문 먼저 자기 자신을 처리한 다음, 왼쪽 자식 노드를 방문하고 뒤이어 오른쪽 자식 노드를 방문하는 순서로 이어진다. 자기를 처리하고 나서 왼쪽 자식 노드를 방문하면, 그 시점을 기준으로 왼쪽 자식 노드는 자기 자신이 되고 이 때의 자신을 기준으로 [노드 자신 => 왼쪽 자식 노드 => 오른쪽 자식 노드] 를 방문하는 순서가 반복된다. 즉, 재귀를 활용하면 된다. class Node:
def __init__(self, item, left, right):
# item은 본인(root node가 됨), left, right는 각각 child node가 됨
self.item = item
self.left = left
self.right = right

def preorder(node):
print(node.item, end=””)
if node.left != ‘.’:
preorder(tree[node.left])
if node.right != ‘.’:
preorder(tree[node.right]) 2. Inorder traverse(중위 순회): 왼쪽 자식 노드 방문 => 노드 자신을 방문 => 오른쪽 자식 노드 방문 왼쪽 자식 노드를 먼저 처리한 다음, 뒤이어 자기 자신을 처리한다. 그 다음에 오른쪽 자식 노드를 방문한다. class Node:
def __init__(self, item, left, right):
# item은 본인(root node가 됨), left, right는 각각 child node가 됨
self.item = item
self.left = left
self.right = right

def inorder(node):
if node.left != ‘.’:
inorder(tree[node.left])
print(node.item, end=””)
if node.right != ‘.’:
inorder(tree[node.right]) 3. Postorder traverse(후위 순회): 오른쪽 자식 노드 방문 => 왼쪽 자식 노드 방문 => 노드 자신을 방문 마지막 순회인 후위 순회는 왼쪽 자식 노드를 방문한 다음, 오른쪽 자식 노드까지 방문을 다 마치고 자기 자신을 방문하는 순서로 순회한다. class Node:
def __init__(self, item, left, right):
# item은 본인(root node가 됨), left, right는 각각 child node가 됨
self.item = item
self.left = left
self.right = right

def postorder(node):
if node.left != ‘.’:
postorder(tree[node.left])
if node.right != ‘.’:
postorder(tree[node.right])
print(node.item, end=””) 1. 1991: 트리 순회 [풀이 1] 전체적인 구성은 똑같고 출력 위치만 바꿔주면 된다. if(left)/if(right)/print(자기자신)의 순서만 다르기 때문에 이에 유념해서 클래스를 생성해 코드는 짜면 된다. 입력값을 받을 때는 tree라는 딕셔너리를 만들고, N번만큼 for문을 돌려 node, left, right를 map 함수를 이용해서 각각 나눈다. tree의 key값에 node를 넣고, class Node를 이용해서 node, left, right를 정의해준다. # 1991: 트리 순회

import sys
sys.stdin = open(“input.txt”,”r”)

class Node:
def __init__(self, item, left, right):
# item은 본인(root node가 됨), left, right는 각각 child node가 됨
self.item = item
self.left = left
self.right = right

def preorder(node):
print(node.item, end=””)
if node.left != ‘.’:
preorder(tree[node.left])
if node.right != ‘.’:
preorder(tree[node.right])

def inorder(node):
if node.left != ‘.’:
inorder(tree[node.left])
print(node.item, end=””)
if node.right != ‘.’:
inorder(tree[node.right])

def postorder(node):
if node.left != ‘.’:
postorder(tree[node.left])
if node.right != ‘.’:
postorder(tree[node.right])
print(node.item, end=””)

if __name__ == “__main__”:
N = int(sys.stdin.readline())
tree = {}
for i in range(N):
node, left, right = map(str, sys.stdin.readline().split())
tree[node] = Node(item=node, left=left, right=right)

preorder(tree[‘A’])
print()
inorder(tree[‘A’])
print()
postorder(tree[‘A’]) [풀이 2] 전위, 중위, 후위 순회의 코드 자체는 별다를게 없지만 입력받는 부분이 살짝 다른다. 1) 먼저 nodes라는 딕셔너리를 만든다. 이는 방법 1에서 만든 tree 딕셔너리와 똑같은 역할을 한다. 2) 방법2에서는 class Node를 사용하지 않고, nodes의 키 값에 temp[0] 즉, root를 넣어주고, 리스트안에 차례대로 자식 노드들을 넣어준다. => 즉, node를 key로 한 뒤, 해당 node에 연결된 node들을 리스트로 만들어 value로 추가한다. 딕셔너리를 출력해보면 키와 키 값에 해당하는 value 값이 들어간 것을 볼 수 있다. # 1991: 트리순회

import sys
sys.stdin = open(‘input.txt’, ‘r’)
input = sys.stdin.readline
# print = sys.stdout.write

n = int(input())
nodes = dict()

for _ in range(n):
temp = input().split()
# nodes라는 딕셔너리의 key 값에 root가 들어가고, value에 리스트 형태로 자식 노드들이 들어감
nodes[temp[0]] = [temp[1], temp[2]]

# 딕셔너리의 키-값 쌍으로 출력하는 방법
# for key, value in nodes.items():
# print(key, value)

def preorder(root): # 전위순회
if root == ‘.’:
return
print(f'{root}’, end=””)
preorder(nodes[root][0])
preorder(nodes[root][1])

def inorder(root): # 중위순회
if root == ‘.’:
return
inorder(nodes[root][0])
print(f'{root}’, end=””)
inorder(nodes[root][1])

def postorder(root): # 후위순회
if root == ‘.’:
return
# nodes[root][0]와 nodes[root][1]는 root의 자식노드 !
postorder(nodes[root][0]) # 왼쪽 자식 노드
postorder(nodes[root][1]) # 오른쪽 자식 노드
print(f'{root}’, end=””) # 노드 방문

# temp[0] == ‘A’ 일 때~
preorder(‘A’)
print()
inorder(‘A’)
print()
postorder(‘A’) 공부 참고 블로그: From WH: https://velog.io/@wonhee/%EC%9D%B4%EC%A7%84%EA%B2%80%EC%83%89%ED%8A%B8%EB%A6%AC-%EB%BF%8C%EC%8B%9C%EA%B8%B0 그래프 설명: https://velog.io/@jewon119/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B8%B0%EC%B4%88-%EB%84%88%EB%B9%84-%EC%9A%B0%EC%84%A0-%ED%83%90%EC%83%89BFS-%EA%B9%8A%EC%9D%B4-%EC%9A%B0%EC%84%A0-%ED%83%90%EC%83%89DFS # 2022/10/22 복습 코드
# 1991: 트리 순회
# 각 노드와 그의 왼쪽 자식 노드, 오른쪽 자식 노드

import sys
from collections import defaultdict
sys.stdin = open(“RETRY/트리순회/input.txt”,”r”)
input = sys.stdin.readline

def preorder(root): # 전위 순회: 노드 자신을 방문 => 왼쪽 자식 노드 방문 => 오른쪽 자식 노드 방문
if root == ‘.’:
return
print(f'{root}’, end=””)
preorder(graph[root][0]) # graph의 root 노드의 왼쪽 자식
preorder(graph[root][1]) # graph의 root 노드의 오른쪽 자식

def inorder(root): # 중위 순회: 왼쪽 자식 노드 방문 => 노드 자신을 방문 => 오른쪽 자식 노드 방문
if root == ‘.’:
return
inorder(graph[root][0])
print(f'{root}’, end=””)
inorder(graph[root][1])

def postorder(root): # 후위 순회: 왼쪽 자식 노드 방문 => 오른쪽 자식 노드 방문 => 노드 자신을 방문
if root == ‘.’:
return
postorder(graph[root][0])
postorder(graph[root][1])
print(f'{root}’, end=””)

# N: 이진 트리의 노드의 개수
N = int(input().rstrip())
graph = defaultdict(list)
for i in range(N):
# graph라는 딕셔너리의 key값에 root가 들어가고, value에 리스트 형태의 자식 노드들이 들어감.
tmp = list(input().split())
graph[tmp[0]].append(tmp[1])
graph[tmp[0]].append(tmp[2])

# graph를 defaultdict로 선언했기 떄문에 graph를 출력하면
# defaultdict(, {‘A’: [‘B’, ‘C’], ‘B’: [‘D’, ‘.’], ‘C’: [‘E’, ‘F’], ‘E’: [‘.’, ‘.’], ‘F’: [‘.’, ‘G’], ‘D’: [‘.’, ‘.’], ‘G’: [‘.’, ‘.’]}) 다음과 같이 나온다.

# print(graph[‘A’])를 출력하면 [‘B’, ‘C’]의 결과가 나온다. so, root의 값을 ‘A’로 준다.

preorder(‘A’)
print()
inorder(‘A’)
print()
postorder(‘A’)

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다