트리, 재귀(recursion), 검색
[warm-up 영상 - 트리 자료 구조의 종류에 대한 설명이 좋다.]
배열
, 연결 리스트
, 스택
, 큐
등은 모두 일직선에 놓인 데이터 구조, 즉 선형 자료 구조
이다. 반면, 노드
와 노드를 연결하는 간선
을 가진 트리는 노드들이 나무가지처럼 연결된 계층적 자료 구조, 즉 비선형 자료 구조
이다. Root
: 가장 위쪽에 있는 노드로, 트리별로 하나만 있다. 루트와 부모를 헷갈릴 수도 있을 것 같으니 주의! 레벨
: 루트 노드에서 얼마나 떨어져있는지를 나타낸다. root가 레벨 0이고, 그 아래는 1인 식으로 아래로 내려갈 때마다 증가한다.높이
: 나무 높이! 루트에서 가장 멀리 떨어진 리프 노드까지의 거리를 말한다. 위 그림으로 따지면 높이는 3이다. 차수
: 각 노드들이 가진 자식의 수를 차수라고 한다. [Bianary Tree (이진 트리)]
Complete BT
:만약 왼쪽 아래 부모노드의 자식노드가 그림처럼 왼쪽에만 있는게 아니라 오른쪽에 있다면 이건 CBT라고 할 수 없다. 왼쪽에서 순서에 맞게 차있는게 중요하다.Full BT
: 자식 노드 둘 다 있으려면 다 있던가! 하나만 있을거면 아예 없던가! 이다.Perfect BT
: [Bianary Search Tree (이진 탐색 트리)]
이진 탐색 트리 구현은 오늘 과제에서 해봤으니 그걸 참조하면 된다. 신기해..
[검색]
[재귀]
def star_print(num):
if num != 0:
star_print(num-1)
print(num * "★")
star_print(7)
'''
[출력]
★
★★
★★★
★★★★
★★★★★
★★★★★★
★★★★★★★
'''
else
일때가 될 것이다. (num=0이 될 때)star_print(num-1)
으로 num과 차이를 두었다. 재호출이 가능하도록 만들어주는 조건으로 생각해도 될듯? # 재귀함수를 사용하여 구구단 2단을 출력해보시오.
def multi_table_2(n):
if n != 0:
multi_table_2(n-1)
print(f'2 * {n} = ', 2*n)
else:
print('구구단 2단')
multi_table_2(9)
'''
[출력]
구구단 2단
2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
2 * 4 = 8
2 * 5 = 10
2 * 6 = 12
2 * 7 = 14
2 * 8 = 16
2 * 9 = 18
[해설]
-재귀함수는 stack 구조를 가지고 있어 값이 0이 될때까지 결과를 가지고 있다가 Last In First Out 해준다는 점을 모르면 풀기 어려울 듯.
- 위 식을 n = 9일때를 적용해 쭉 개념적으로 풀어보면,
a(9) = a(8) + print(2 * 9)
a(8) = a(7) + print(2*8)
a(7) = a(6) + print(2*7)
....
a(2) = a(1) + print(2*2)
a(1) = a(0) + print(2*1)
이 되는데, 여기서 a(0)은 print('구구단 2단')이다.
스택 구조를 가지기 때문에 모든 값을 가지고 있다가 print('구구단 2단')부터 2*1, 2*2 ... 로 꺼내서 보여주는 것.
'''
# 실습3
def count_down(num):
if num != 1:
print(num)
count_down(num-1)
else:
print(num)
print('발사')
count_down(5)
'''
5
4
3
2
1
발사
'''
재귀 개념에 대해선 이 정도로 하고, 다음으로 고고~
[재귀함수 만들기]
"""
Bare Minimum Requirements
재귀에 대한 개념과 내장함수의 내부로직을 이해한다.
요구사항:
파이썬에서는 개발자 편의성을 위해 다양한 내장함수를 제공하고 있습니다.
개발복잡도와 사이드이펙트로 인해 내장함수를 사용하지 못 하는 경우도 있습니다.
내장함수는 간편한 기능이긴 하지만 문제가 확장되는 경우
예상되는 결과를 도출해주지 못 하는 경우도 있습니다.
내장함수의 원리와 자신이 직접 구현해보는 기능의 적합성을 판단해봅니다.
아래 코드에 작성된 요구사항을 확인하여 문제를 해결해주세요.
각 문제 코드 위에 작성된 '@counter'는 변경하지 마세요.
"""
class counter:
"""
해당 코드를 수정하지 마세요!
"""
def __init__(self, function):
self.function = function
self.cnt = 0
def __call__(self, *args, **kwargs):
self.cnt += 1
return self.function(*args, **kwargs)
@counter # 삭제하거나 변경하지 마세요!
def oneto100(num):
"""
# 문제1
다음 문제는 1 ~ num 까지 합을 구하는 문제입니다.
결과값이 제대로 구해지도록 코드를 완성하세요.
아래 예시입력값과 출력값을 참조하며 문제를 해결해봅니다.
예시 1
입력값:
10
출력값:
55
예시 2
입력값:
100
출력값:
5050
"""
if num == 1:
return 1
return oneto100(num-1)+num
'''
[기록]
- 데코레이터 reference => https://choice-life.tistory.com/42 => 반복되는 구문의 경우 계속 반복하는 것이 아니라 재사용 가능.
> 오늘 과제에선 왜 썼을까? 채점할 때 cnt를 활용하는건가? 질문 예정.
- 나중에 기억하기 쉽게 위 로직을 간단히 남겨둔다.
oneto100(10) = oneto100(9) + 10
oneto100(9) = oneto100(8) + 9
oneto100(8) = oneto100(7) + 8
....
oneto100(2) = oneto100(1) + 2
oneto100(1) = 1
차례로 올라가며 더해나가서 최종 출력값을 55가 나오는 것.
'''
@counter
def recursion_advanced(str_data):
"""
# 문제2
재귀를 활용하여 주어진 문자를 뒤집어주는 프로그래밍을 진행합니다.
문자열에 직접 영향을 주는 reverse와 같은 내장함수를 사용하면 안됩니다.
# 예상 입출력 1
input : 'testing...'
output: '...gnitset'
# 예상 입출력 2
input : 'Codestates'
output: 'setatsedoC'
"""
if len(str_data) != 0:
return str_data[-1] + str(recursion_advanced(str_data[:(len(str_data)-1)]))
else:
return ''
recursion_advanced('Codestates')
'''
[기록]
- 초반 아이디어: 재귀함수를 이용한다면 아래 구조를 생각할 수 있는데 이걸 어떻게 구현해볼 수 있을까?
str_data = 'test'
a(?_3) = a(?_2) + 't'
a(?_2) = a(?_1) + 'e'
a(?_1) = a(?_0) + 's'
a(?_0) = 't'
- 실제 구현은 덧셈을 먼저 하는 식으로 했음.
str_data = 'abcd' 라고 하면,
a('abcd') = 'd' + a('abc')
a('abc') = 'c' + a('ab')
a('ab') = 'b' + a('a')
a('a') = 'a' + a('')
a('') = ""
이런 구조로 생각하면 됨. 신기하다!!
'''
[이진 탐색 트리 구현하기]
"""
Bare Minimum Requirements
연결리스트의 참조개념을 생각해보면서 트리개념에 접근해봅니다.
요구사항:
추상화된 트리는 제공된 그림처럼 설명되지만
실제 컴퓨터 내부에서는 다르게 동작된다는 것을 인지하셔야 합니다.
코딩을 하며 노드를 검색하고 추가하는 것을 넘어서서
트리구조로 동작하는 소프트웨어에 대해 생각해봅니다.
트리의 모양은 'if __name__ == "__main__":' 아래 작성된 그림을 참조하세요.
기능에 적합하도록 코드를 구현하시길 바랍니다.
오늘 강의노트에서 학습한 트리 소스코드를 기준으로해야 코드를 작성해야 합니다.
노드검색에 대한 결과가 동일하게 나올 수 있도록 완성하세요.
'if __name__ == "__main__":' 구문 아래 코드와 출력값을 참조하며 문제를 해결해봅니다.
"""
class node:
def __init__(self, value):
"""
# 문제 1
bst에서 사용할 수 있는 node 클래스를 작성해주세요
"""
self.value = value
self.left = None
self.right = None
class binary_search_tree:
def __init__(self, head):
"""
문제 2.
bst의 생성자 메소드를 작성해주세요
"""
self.head = head # root node!
def insert_node(self, value):
"""
문제 3.
bst의 동작에 맞게 값을 집어넣을 수 있도록 메소드를 작성해주세요
"""
self.base_node = self.head # head를 base_node로 해서 아래로 한 단계씩 찾아 내려가기.
while True:
if value < self.base_node.value: # 부모 노드의 값보다 작다면 왼쪽으로 들어가야 한다.
if self.base_node.left is not None: # 왼쪽에 노드가 이미 있다면
self.base_node = self.base_node.left # 해당 노드를 base_node로 해서 한 층 아래로 내려가서 다시 while문으로 돌아간다.
else: # 왼쪽에 노드가 없다면
self.base_node.left = node(value) # base_node 왼쪽에 노드를 추가하라. (예. 맨 처음 head가 base_node였다면 왼쪽에 자식노드 생성)
break # 추가 되었으므로 반복문 break
else: # bst는 중복값이 없어야 하므로 'value > self.base_node.value'로 생각하면 됨. 즉, 오른쪽에 들어가야 함.
if self.base_node.right is not None: # 위와 설명 동일.
self.base_node = self.base_node.right
else:
self.base_node.right = node(value)
break
def search_node(self, value):
"""
문제 4.
bst 내부에 value값이 있는지 True / False값을 반환하는 메소드를 작성해주세요
"""
self.base_node = self.head
while self.base_node:
if self.base_node.value == value: # 만약 찾는 값이 base_node에 있다면
return True
elif value < self.base_node.value: # 만약 찾는 값이 base_node 값보다 작다면?
self.base_node = self.base_node.left # 왼쪽에 있는 자식노드를 base_node로 변경하여 한 층 아래로 내려가기
elif value > self.base_node.value:
self.base_node = self.base_node.right
# 찾는 값이 없으면 false 반환 (찾는 값이 있다면 while문에서 return True 되었을 것.)
return False
if __name__ == "__main__":
"""
아래 코드를 통해 문제의 예상 입출력을 한 번 확인해주세요.
[아래 코드의 트리 형태]
16
/ \
12 19
/ \ / \
11 13 18 20
/
9
/ \
8 10
"""
head = node(16)
bt = binary_search_tree(head)
bt.insert_node(12)
bt.insert_node(19)
bt.insert_node(11)
bt.insert_node(13)
bt.insert_node(18)
bt.insert_node(20)
bt.insert_node(9)
bt.insert_node(8)
bt.insert_node(10)
print(bt.search_node(5)) #False
print(bt.search_node(-2)) #False
print(bt.search_node(18)) #True