feat(ko): add the Korean online edition (#2006)

* feat(ko): add the complete Korean online edition

* Use UTF-8 for Python CI output on Windows

* Remove print-book promotion from the Korean site

* fix(ko): preserve emphasis before Korean particles
This commit is contained in:
Yudong Jin
2026-10-29 03:46:43 +08:00
committed by GitHub
parent 71a773df64
commit 2f561d3d51
2072 changed files with 110884 additions and 102 deletions
@@ -0,0 +1,119 @@
"""
File: array_binary_tree.py
Created Time: 2023-07-19
Author: krahets ([email protected])
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, list_to_tree, print_tree
class ArrayBinaryTree:
"""배열로 표현한 이진 트리 클래스"""
def __init__(self, arr: list[int | None]):
"""생성자"""
self._tree = list(arr)
def size(self):
"""리스트 용량"""
return len(self._tree)
def val(self, i: int) -> int | None:
"""인덱스가 i인 노드의 값 반환"""
# 인덱스가 범위를 벗어나면 빈칸을 나타내는 None을 반환한다
if i < 0 or i >= self.size():
return None
return self._tree[i]
def left(self, i: int) -> int | None:
"""인덱스가 i인 노드의 왼쪽 자식 노드 인덱스 반환"""
return 2 * i + 1
def right(self, i: int) -> int | None:
"""인덱스가 i인 노드의 오른쪽 자식 노드 인덱스 반환"""
return 2 * i + 2
def parent(self, i: int) -> int | None:
"""인덱스가 i인 노드의 부모 노드 인덱스 반환"""
return (i - 1) // 2
def level_order(self) -> list[int]:
"""레벨 순서 순회"""
self.res = []
# 배열을 직접 순회한다
for i in range(self.size()):
if self.val(i) is not None:
self.res.append(self.val(i))
return self.res
def dfs(self, i: int, order: str):
"""깊이 우선 순회"""
if self.val(i) is None:
return
# 전위 순회
if order == "pre":
self.res.append(self.val(i))
self.dfs(self.left(i), order)
# 중위 순회
if order == "in":
self.res.append(self.val(i))
self.dfs(self.right(i), order)
# 후위 순회
if order == "post":
self.res.append(self.val(i))
def pre_order(self) -> list[int]:
"""전위 순회"""
self.res = []
self.dfs(0, order="pre")
return self.res
def in_order(self) -> list[int]:
"""중위 순회"""
self.res = []
self.dfs(0, order="in")
return self.res
def post_order(self) -> list[int]:
"""후위 순회"""
self.res = []
self.dfs(0, order="post")
return self.res
"""실행 코드"""
if __name__ == "__main__":
# 이진 트리 초기화
# 여기서는 배열로부터 이진 트리를 바로 생성하는 함수를 사용한다
arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
root = list_to_tree(arr)
print("\n이진 트리 초기화\n")
print("이진 트리의 배열 표현:")
print(arr)
print("이진 트리의 연결 리스트 표현:")
print_tree(root)
# 배열로 표현한 이진 트리 클래스
abt = ArrayBinaryTree(arr)
# 노드 접근
i = 1
l, r, p = abt.left(i), abt.right(i), abt.parent(i)
print(f"\n현재 노드의 인덱스는 {i}, 값은 {abt.val(i)}이다")
print(f"왼쪽 자식 노드의 인덱스는 {l}, 값은 {abt.val(l)}이다")
print(f"오른쪽 자식 노드의 인덱스는 {r}, 값은 {abt.val(r)}이다")
print(f"부모 노드의 인덱스는 {p}, 값은 {abt.val(p)}이다")
# 트리 순회
res = abt.level_order()
print("\n레벨 순서 순회:", res)
res = abt.pre_order()
print("전위 순회:", res)
res = abt.in_order()
print("중위 순회:", res)
res = abt.post_order()
print("후위 순회:", res)
+200
View File
@@ -0,0 +1,200 @@
"""
File: avl_tree.py
Created Time: 2022-12-20
Author: a16su ([email protected])
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
class AVLTree:
"""AVL 트리"""
def __init__(self):
"""생성자"""
self._root = None
def get_root(self) -> TreeNode | None:
"""이진 트리의 루트 노드 반환"""
return self._root
def height(self, node: TreeNode | None) -> int:
"""노드 높이 반환"""
# 빈 노드의 높이는 -1이고, 리프 노드의 높이는 0이다
if node is not None:
return node.height
return -1
def update_height(self, node: TreeNode | None):
"""노드 높이를 갱신한다"""
# 노드 높이는 가장 높은 서브트리의 높이 + 1이다
node.height = max([self.height(node.left), self.height(node.right)]) + 1
def balance_factor(self, node: TreeNode | None) -> int:
"""균형 인수 반환"""
# 빈 노드의 균형 인수는 0이다
if node is None:
return 0
# 노드의 균형 인수 = 왼쪽 서브트리 높이 - 오른쪽 서브트리 높이
return self.height(node.left) - self.height(node.right)
def right_rotate(self, node: TreeNode | None) -> TreeNode | None:
"""오른쪽 회전 연산"""
child = node.left
grand_child = child.right
# child를 중심으로 node를 오른쪽으로 회전한다
child.right = node
node.left = grand_child
# 노드 높이를 갱신한다
self.update_height(node)
self.update_height(child)
# 회전한 서브트리의 루트 노드를 반환한다
return child
def left_rotate(self, node: TreeNode | None) -> TreeNode | None:
"""왼쪽 회전 연산"""
child = node.right
grand_child = child.left
# child를 중심으로 node를 왼쪽으로 회전한다
child.left = node
node.right = grand_child
# 노드 높이를 갱신한다
self.update_height(node)
self.update_height(child)
# 회전한 서브트리의 루트 노드를 반환한다
return child
def rotate(self, node: TreeNode | None) -> TreeNode | None:
"""회전 연산을 수행하여 해당 서브트리의 균형 복원"""
# 노드 node의 균형 인수를 구한다
balance_factor = self.balance_factor(node)
# 왼쪽으로 치우친 트리
if balance_factor > 1:
if self.balance_factor(node.left) >= 0:
# 오른쪽 회전
return self.right_rotate(node)
else:
# 왼쪽으로 회전한 후 오른쪽으로 회전한다
node.left = self.left_rotate(node.left)
return self.right_rotate(node)
# 오른쪽으로 치우친 트리
elif balance_factor < -1:
if self.balance_factor(node.right) <= 0:
# 왼쪽 회전
return self.left_rotate(node)
else:
# 오른쪽으로 회전한 후 왼쪽으로 회전한다
node.right = self.right_rotate(node.right)
return self.left_rotate(node)
# 균형 트리이므로 회전할 필요 없이 바로 반환한다
return node
def insert(self, val):
"""노드 삽입"""
self._root = self.insert_helper(self._root, val)
def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode:
"""노드의 재귀적 삽입(보조 메서드)"""
if node is None:
return TreeNode(val)
# 1. 삽입 위치를 찾아 노드를 삽입한다
if val < node.val:
node.left = self.insert_helper(node.left, val)
elif val > node.val:
node.right = self.insert_helper(node.right, val)
else:
# 중복 노드는 삽입하지 않고 바로 반환한다
return node
# 노드 높이를 갱신한다
self.update_height(node)
# 2. 회전 연산을 수행하여 해당 서브트리의 균형을 복원한다
return self.rotate(node)
def remove(self, val: int):
"""노드 삭제"""
self._root = self.remove_helper(self._root, val)
def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None:
"""노드의 재귀적 삭제(보조 메서드)"""
if node is None:
return None
# 1. 노드를 찾아 삭제한다
if val < node.val:
node.left = self.remove_helper(node.left, val)
elif val > node.val:
node.right = self.remove_helper(node.right, val)
else:
if node.left is None or node.right is None:
child = node.left or node.right
# 자식 노드 수 = 0이면 node를 바로 삭제하고 반환한다
if child is None:
return None
# 자식 노드 수 = 1이면 node를 바로 삭제한다
else:
node = child
else:
# 자식 노드 수 = 2이면 중위 순회의 다음 노드를 삭제하고, 그 노드로 현재 노드를 대체한다
temp = node.right
while temp.left is not None:
temp = temp.left
node.right = self.remove_helper(node.right, temp.val)
node.val = temp.val
# 노드 높이를 갱신한다
self.update_height(node)
# 2. 회전 연산을 수행하여 해당 서브트리의 균형을 복원한다
return self.rotate(node)
def search(self, val: int) -> TreeNode | None:
"""노드 탐색"""
cur = self._root
# 반복하여 탐색하다 리프 노드를 지나면 종료한다
while cur is not None:
# 목표 노드는 cur의 오른쪽 서브트리에 있다
if cur.val < val:
cur = cur.right
# 목표 노드는 cur의 왼쪽 서브트리에 있다
elif cur.val > val:
cur = cur.left
# 목표 노드를 찾았으므로 반복문을 종료한다
else:
break
# 목표 노드를 반환한다
return cur
"""실행 코드"""
if __name__ == "__main__":
def test_insert(tree: AVLTree, val: int):
tree.insert(val)
print("\n노드 {} 삽입 후의 AVL 트리".format(val))
print_tree(tree.get_root())
def test_remove(tree: AVLTree, val: int):
tree.remove(val)
print("\n노드 {} 삭제 후의 AVL 트리".format(val))
print_tree(tree.get_root())
# 빈 AVL 트리 초기화
avl_tree = AVLTree()
# 노드 삽입
# 노드를 삽입한 후 AVL 트리가 어떻게 균형을 유지하는지 살펴본다
for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]:
test_insert(avl_tree, val)
# 중복 노드 삽입
test_insert(avl_tree, 7)
# 노드 삭제
# 노드를 삭제한 후 AVL 트리가 어떻게 균형을 유지하는지 살펴본다
test_remove(avl_tree, 8) # 차수가 0인 노드 삭제
test_remove(avl_tree, 5) # 차수가 1인 노드 삭제
test_remove(avl_tree, 4) # 차수가 2인 노드 삭제
result_node = avl_tree.search(7)
print("\n찾은 노드 객체는 {}, 노드 값 = {}".format(result_node, result_node.val))
@@ -0,0 +1,146 @@
"""
File: binary_search_tree.py
Created Time: 2022-12-20
Author: a16su ([email protected])
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
class BinarySearchTree:
"""이진 탐색 트리"""
def __init__(self):
"""생성자"""
# 빈 트리를 초기화한다
self._root = None
def get_root(self) -> TreeNode | None:
"""이진 트리의 루트 노드 반환"""
return self._root
def search(self, num: int) -> TreeNode | None:
"""노드 탐색"""
cur = self._root
# 반복하여 탐색하다 리프 노드를 지나면 종료한다
while cur is not None:
# 목표 노드는 cur의 오른쪽 서브트리에 있다
if cur.val < num:
cur = cur.right
# 목표 노드는 cur의 왼쪽 서브트리에 있다
elif cur.val > num:
cur = cur.left
# 목표 노드를 찾았으므로 반복문을 종료한다
else:
break
return cur
def insert(self, num: int):
"""노드 삽입"""
# 트리가 비어 있으면 루트 노드를 초기화한다
if self._root is None:
self._root = TreeNode(num)
return
# 반복하여 탐색하다 리프 노드를 지나면 종료한다
cur, pre = self._root, None
while cur is not None:
# 중복 노드를 찾으면 바로 반환한다
if cur.val == num:
return
pre = cur
# 삽입 위치는 cur의 오른쪽 서브트리에 있다
if cur.val < num:
cur = cur.right
# 삽입 위치는 cur의 왼쪽 서브트리에 있다
else:
cur = cur.left
# 노드 삽입
node = TreeNode(num)
if pre.val < num:
pre.right = node
else:
pre.left = node
def remove(self, num: int):
"""노드 삭제"""
# 트리가 비어 있으면 바로 반환하여 종료한다
if self._root is None:
return
# 반복하여 탐색하다 리프 노드를 지나면 종료한다
cur, pre = self._root, None
while cur is not None:
# 삭제할 노드를 찾았으므로 반복문을 종료한다
if cur.val == num:
break
pre = cur
# 삭제할 노드는 cur의 오른쪽 서브트리에 있다
if cur.val < num:
cur = cur.right
# 삭제할 노드는 cur의 왼쪽 서브트리에 있다
else:
cur = cur.left
# 삭제할 노드가 없으면 바로 반환한다
if cur is None:
return
# 자식 노드 수 = 0 또는 1
if cur.left is None or cur.right is None:
# 자식 노드 수 = 0 / 1이면 child = null / 해당 자식 노드이다
child = cur.left or cur.right
# 노드 cur를 삭제한다
if cur != self._root:
if pre.left == cur:
pre.left = child
else:
pre.right = child
else:
# 삭제할 노드가 루트 노드이면 루트 노드를 다시 지정한다
self._root = child
# 자식 노드 수 = 2
else:
# 중위 순회에서 cur의 다음 노드를 구한다
tmp: TreeNode = cur.right
while tmp.left is not None:
tmp = tmp.left
# 노드 tmp를 재귀적으로 삭제한다
self.remove(tmp.val)
# tmp로 cur를 덮어쓴다
cur.val = tmp.val
"""실행 코드"""
if __name__ == "__main__":
# 이진 탐색 트리 초기화
bst = BinarySearchTree()
nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]
# 삽입 순서에 따라 서로 다른 이진 트리가 생성된다. 이 순서는 포화 이진 트리를 생성할 수 있다
for num in nums:
bst.insert(num)
print("\n초기화한 이진 트리\n")
print_tree(bst.get_root())
# 노드 탐색
node = bst.search(7)
print("\n찾은 노드 객체: {}, 노드 값 = {}".format(node, node.val))
# 노드 삽입
bst.insert(16)
print("\n노드 16을 삽입한 후의 이진 트리\n")
print_tree(bst.get_root())
# 노드 삭제
bst.remove(1)
print("\n노드 1을 삭제한 후의 이진 트리\n")
print_tree(bst.get_root())
bst.remove(2)
print("\n노드 2를 삭제한 후의 이진 트리\n")
print_tree(bst.get_root())
bst.remove(4)
print("\n노드 4를 삭제한 후의 이진 트리\n")
print_tree(bst.get_root())
@@ -0,0 +1,41 @@
"""
File: binary_tree.py
Created Time: 2022-12-20
Author: a16su ([email protected])
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, print_tree
"""실행 코드"""
if __name__ == "__main__":
# 이진 트리 초기화
# 노드 초기화
n1 = TreeNode(val=1)
n2 = TreeNode(val=2)
n3 = TreeNode(val=3)
n4 = TreeNode(val=4)
n5 = TreeNode(val=5)
# 노드 사이의 참조(포인터)를 설정한다
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
print("\n이진 트리 초기화\n")
print_tree(n1)
# 노드 삽입과 삭제
P = TreeNode(0)
# n1 -> n2 사이에 노드 P를 삽입한다
n1.left = P
P.left = n2
print("\n노드 P를 삽입한 후\n")
print_tree(n1)
# 노드 삭제
n1.left = n2
print("\n노드 P를 삭제한 후\n")
print_tree(n1)
@@ -0,0 +1,42 @@
"""
File: binary_tree_bfs.py
Created Time: 2022-12-20
Author: a16su ([email protected])
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, list_to_tree, print_tree
from collections import deque
def level_order(root: TreeNode | None) -> list[int]:
"""레벨 순서 순회"""
# 큐를 초기화하고 루트 노드를 넣는다
queue: deque[TreeNode] = deque()
queue.append(root)
# 순회 순서를 저장할 리스트를 초기화한다
res = []
while queue:
node: TreeNode = queue.popleft() # 큐에서 꺼낸다
res.append(node.val) # 노드 값을 저장한다
if node.left is not None:
queue.append(node.left) # 왼쪽 자식 노드를 큐에 삽입한다
if node.right is not None:
queue.append(node.right) # 오른쪽 자식 노드를 큐에 삽입한다
return res
"""실행 코드"""
if __name__ == "__main__":
# 이진 트리 초기화
# 여기서는 배열로부터 이진 트리를 바로 생성하는 함수를 사용한다
root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7])
print("\n이진 트리 초기화\n")
print_tree(root)
# 레벨 순서 순회
res: list[int] = level_order(root)
print("\n레벨 순서 순회의 노드 출력 순서 = ", res)
@@ -0,0 +1,65 @@
"""
File: binary_tree_dfs.py
Created Time: 2022-12-20
Author: a16su ([email protected])
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent))
from modules import TreeNode, list_to_tree, print_tree
def pre_order(root: TreeNode | None):
"""전위 순회"""
if root is None:
return
# 방문 우선순위: 루트 노드 -> 왼쪽 서브트리 -> 오른쪽 서브트리
res.append(root.val)
pre_order(root=root.left)
pre_order(root=root.right)
def in_order(root: TreeNode | None):
"""중위 순회"""
if root is None:
return
# 방문 우선순위: 왼쪽 서브트리 -> 루트 노드 -> 오른쪽 서브트리
in_order(root=root.left)
res.append(root.val)
in_order(root=root.right)
def post_order(root: TreeNode | None):
"""후위 순회"""
if root is None:
return
# 방문 우선순위: 왼쪽 서브트리 -> 오른쪽 서브트리 -> 루트 노드
post_order(root=root.left)
post_order(root=root.right)
res.append(root.val)
"""실행 코드"""
if __name__ == "__main__":
# 이진 트리 초기화
# 여기서는 배열로부터 이진 트리를 바로 생성하는 함수를 사용한다
root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7])
print("\n이진 트리 초기화\n")
print_tree(root)
# 전위 순회
res = []
pre_order(root)
print("\n전위 순회의 노드 출력 순서 = ", res)
# 중위 순회
res.clear()
in_order(root)
print("\n중위 순회의 노드 출력 순서 = ", res)
# 후위 순회
res.clear()
post_order(root)
print("\n후위 순회의 노드 출력 순서 = ", res)