Computer Science/Data Structure & Algorithm

이진탐색트리 (Binary Search Tree, BST)

신수동탈곡기 2022. 3. 27. 15:31

개요

트리의 구조

  • 트리: Node와 Branch를 이용해서 사이클을 이루지 안호록 구성한 데이터 구조
  • 트리 중 이진 트리(Binary Tree) 형태의 구조로 탐색(검색) 알고리즘 구현을 위해 자주 사용

용어

  • Node: 트리에 데이터를 저장하는 기본 요소 (데이터, 연결된 다른 node에 대한 branch 정보 포함)
  • Root node: 트리의 최상위 노드
  • Level: Root node를 레벨 0으로 봤을 때 하위 node들의 깊이를 나타냄
  • Parent node: 어떤 노드의 하위 레벨에 연결된 노드 (위쪽 노드)
  • Child node: 어떤 노드의 상위 레벨에 연결된 노드 (아래쪽 노드)
  • Leaf node: Child node가 없는 노드
  • Depth: 트리에서 node가 가질 수 있는 최대 level

이진 트리와 이진 탐색 트리의 차이점

  • 이진 트리(Binary Tree): 노드의 최대 Branch 개수가 2인 트리
  • 이진 탐색 트리(Binary Search Tree): 이진 트리에서 왼쪽 노드는 해당 노드보다 작은 값, 오른쪽 노드는 해당 노드보다 큰 값을 같는다는 조건이 추가된 트리

장점

  • 빠른 탐색 속도
  • 글 하단의 배열과의 비교에서 알 수 있다.

단점

한 쪽 방향으로만 자라는 트리

  • 위와 같이 한 쪽으로만 node가 추가되는 트리에 링크드 리스트과 같은 탐색 속도를 가진다.

그림으로 보기

1) 이진탐색트리의 예시

이진탐색트리의 예

2) 자료 탐색시 배열과의 비교

탐색 시 배열과의 차이

배열에서 13이라는 값을 찾기 위해서는 5번의 탐색이 필요하지만, 이진탐색트리에서는 3회의 값 비교를 통해 찾을 수 있다. 이러한 탐색 속도 차이는 자료구조의 크기가 커질 수록 비례해서 커진다.

3) Node 삭제시의 로직

3-1) Leaf node를 삭제할 경우

  1. 삭제할 node의 parent node의 branch를 None으로 대체한다.

3-2) Child node가 1개인 node를 삭제할 경우

  1. 삭제할 node의 parent node가 삭제할 node의 child node를 가리키게 한다.

3-2) Child node가 2개인 node를 삭제할 경우 -> 매우 복잡한 과정을 거친다.

삭제할 node의 오른쪽에 있는 tree에서 가장 작은 값을 가지는 node가 삭제할 node 자리를 대체해야 한다.
  1. 삭제할 node의 오른쪽 tree에서 가장 작은 값을 가지는 node(= 삭제할 node의 오른쪽 tree에서 계속 왼쪽으로 가면서 왼쪽 branch가 없는 node)를 찾는다. (이 node를 target node라고 부르기로 함)
  2. 삭제할 node의 parent node가 target node를 가리키게 한다.
  3. target node가 child node를 가지고 있었다면, target node의 parent node가 target node의 child node를 가리키게 만든다.
  4. 삭제할 node의 오른쪽 child node를 target node의 오른쪽 child node로 만든다.
  5. 삭제할 node의 왼쪽 child node를 target node의 왼쪽 child node로 만든다.

구현

class Node:
	def __init__(self, value):
		self.value = value
		self.left = None
		self.right = None


class BinarySearchTree:
	def __init__(self, value):
		self.head = Node(value)

	def insert(self, value):
		# root node를 현재 node로 두고
		self.current = self.head
		while True:
			# 새로 넣을 값이 현재 node보다 작을 경우 
			if value < self.current.value:
				# 현재 node의 왼쪽 branch에 이미 node가 있을 경우
				if self.current.left:
					# 현재 node의 왼쪽 node를 현재 node로 변경 후 재탐색
					self.current = self.current.left
				# 현재 node의 왼쪽 branch에 node가 없는 경우
				else:
					# node 인스턴스를 생성하고 현재 node의 왼쪽 branch로 지정
					self.current.left = Node(value)
					# while문 탈출
					break
			# 새로 넣을 값이 현재 node보다 크거나 같을 경우
			else:
				# 현재 node의 오른쪽 branch에 이미 node가 있을 경우
				if self.current.right:
					# 현재 node의 오른쪽 node를 현재 node로 변경 후 재탐색
					self.current = self.current.right
				# 현재 node의 오른쪽 branch에 node가 없는 경우
				else:
					# node 인스턴스를 생성하고 현재 node의 오른쪽 branch로 지정
					self.current.right = Node(value)
					# while문 탈출
					break

	def isExist(self, value):
		# root node를 현재 node로 두고
		self.current = self.head
		
		# 현재 node가 존재할 때
		while self.current:
			# 찾는 값이 현재 node의 값과 같다면 
			if value == self.current.value:
				# 찾는 값이 존재하므로 True 리턴
				return True
			# 찾는 값이 현재 node보다 작으면 
			elif value < self.current.value:
				# 현재 node의 왼쪽 branch의 node를 현재 node로 재탐색
				self.current = self.current.left
			# 찾는 값이 현재 node보다 크면 
			else:
				# 현재 node의 오른쪽 branch의 node를 현재 node로 재탐색
				self.current = self.current.right
		# 탐색을 하다 더이상 branch가 없어 while을 탈출하면 찾는 값이 없으므로 False 리턴
		return False

	def delete(self, value):
		searched = False
		self.current = self.head
		self.parent = self.head

		# while을 통해 지우고자 하는 node와 지우고자 하는 node의 parent node 탐색
		while self.current:
			if value == self.current.value:
				searched = True
				break
			elif value < self.current.value:
				self.parent = self.current
				self.current = self.current.left
			else:
				self.parent = self.current
				self.current = self.current.right
		
		# 못 찾았으면 False 리턴	
		if not searched:
			return False

		# 지우고자 하는 node에 왼쪽 오른쪽 branch가 모두 있을 때
		if self.current.left and self.current.right:
			# 지우고자 하는 node의 값이 parent node보다 작을 때
			if value < self.parent.value:
				# 현재 node의 오른쪽 node를 타겟으로
				self.target = self.current.right
				self.target_parent = self.current.right
				
				# 타겟 node의 왼쪽 node가 없을 때 까지 
				# (= 지우고자 하는 node의 tree에서 제일 값이 작은 node를 찾을 때 까지)
				while target.left:
					self.target_parent = self.target
					self.target = self.target.left

				# 타겟 node의 오른쪽 branch node가 있다면
				if self.target.right:
					# 타겟 node의 오른쪽 branch node를 타겟 node의 parent node의 왼쪽 node로 대체
					self.target_parent.left = self.target.right
				else:
					# 없으면 타겟 node의 parent node의 왼쪽 node를 None으로
					self.target_parent.left = None

				# 현재 node의 parent node의 왼쪽 node를 타겟 node로
				self.parent.left = self.target
				# 아래 두 라인을 통해 current node를 타겟 노드로 대체
				self.target.right = self.current.right
				self.target.left = self.current.left

				del self.current
			# 지우고자 하는 node의 값이 parent node보다 클 때
			else:
				self.target = self.current.right
				self.target_parent = self.current.right

				while self.target.left:
					self.target_parent = self.target
					self.target = self.target.left

				if self.target.right:
					self.target_parent.left = self.target.right
				else:
					self.target_parent.left = None

				self.parent.right = self.target
				self.target.right = self.current.right
				self.target.left = self.current.left
				del self.current
		# 지우고자 하는 node에 오른쪽 branch만 있을 때
		elif not self.current.left and self.current.right:
			if value < self.parent.value:
				self.parent.left = self.current.right
			else:
				self.parent.right = self.current.right
			# 지우려는 node 인스턴스 삭제
			del self.current
		# 지우고자 하는 node에 왼쪽 branch만 있을 때
		elif self.current.left and not self.current.right:
			if value < self.parent.value:
				self.parent.left = self.current.left
			else:
				self.parent.right = self.current.left
			# 지우려는 node 인스턴스 삭제
			del self.current
		# 지우고자 하는 node가 leaf node일 때
		else:
			# 지우려는 값이 parent node보다 작다면 (왼쪽 branch라면)
			if value < self.parent.value:
				# parent node의 왼쪽 branch를 None으로 대체
				self.parent.left = None
			# 지우려는 값이 parent node보다 크다면 (오른쪽 branch라면)
			else:
				# parent node의 오른쪽 branch를 None으로 대체
				self.parent.right = None
			# 지우려는 node 인스턴스 삭제
			del self.current

	def leftRight(self, value):
		self.current = self.head
		
		while self.current:
			# 찾고자 하는 node의 값과 현재 node의 값이 같을 때
			if value == self.current.value:
				# 현재 노드의 왼쪽 branch node의 값, 현재 노드의 오른쪽 branch node의 값 출력
				print(self.current.left.value, self.current.right.value)
				# 리턴
				return True
			# 찾고자 하는 node의 값이 현재 node의 값보다 작을 때
			elif value < self.current.value:
				# 현재 node의 왼쪽 branch node를 현재 노드로 대체
				self.current = self.current.left
			# 찾고자 하는 node의 값이 현재 node의 값보다 클 때
			else:
				# 현재 node의 오른쪽 branch node를 현재 노드로 대체
				self.current = self.current.right

		return False

if __name__ == "__main__":
	BST = BinarySearchTree(100)
	BST.insert(200)
	BST.insert(150)
	BST.insert(140)
	BST.insert(160)
	BST.insert(300)
	BST.delete(200)
	BST.leftRight(150)

'Computer Science > Data Structure & Algorithm' 카테고리의 다른 글

그래프  (0) 2022.05.30
병합정렬 (Merge sort)  (0) 2022.04.17
퀵정렬 (Quick sort)  (0) 2022.04.17
동적계획법(Dynamic Programming)과 분할정복 (Divide and Conquer)  (0) 2022.04.10
힙 (Heap)  (0) 2022.03.27