Frinee의 코드저장소

탐색(Search) 2

by Frinee

이 글은 윤성우 저 - "윤성우의 열혈 자료구조"를 공부하고 정리하여 작성하였습니다.

1. 균형 잡힌 이진 탐색 트리: AVL 트리의 이해

⚠️ 이진탐색 트리의 문제점과 AVL 트리

  • 이진 탐색 트리의 탐색 연산은 O(logn)의 시간 복잡도를 가짐
  • 하지만 균형이 맞지 않을수록 O(n)에 가까운 시간 복잡도를 보임.
  • 이진 탐색 트리의 단점을 해결한 트리를 ‘균형 잡힌 이진 트리’라 하며 그 종류는 대략 이렇다
    • AVL 트리
    • 2-3 트리
    • 2-3-4 트리
    • Red-Black 트리
    • B 트리

⚖️ 자동으로 균형을 잡는 AVL 트리와 균형 인수(Balance Factor)

  • Adelson-Velskii와 Landis에 의해 고안되어 이름도 이들의 이름을 땄음.
  • 노드가 추가될 때, 그리고 삭제될 때 트리의 균형상태를 파악해서 스스로 그 구조를 변경하여 균형을 잡는 트리
  • 균형 인수 = 왼쪽 서브 트리의 높이 - 오른쪽 서브 트리의 높이

🔧 리밸런싱이 필요한 첫 번째 상태와 LL 회전

  • ChangeRightSubTree(sNode, pNode)
  • 일반화

ChangeLeftSubTree(pNode, GetRightSubTree(cNode));
ChangeRightSubTree(cNode, pNode);

 

🔧 리밸런싱이 필요한 두 번째 상태와 RR 회전

ChangeRightSubTree(pNode, GetLeftSubTree(cNode));
ChangeLeftSubTree(cNode, pNode);

 

🔧 리밸런싱이 필요한 세 번째 상태와 LR 회전

  • LR 상태는 RR회전을 통해서 LL 상태가 되게 할 수 있음

🔧 리밸런싱이 필요한 네 번째 상태와 RL 회전

  • 자식 노드와 연결된 방향이 LR 상태와 반대인 것 외에는 차이가 없음
  • 부분적 LL 회전에 이어서 RR 회전을 진행

 

2. 균형 잡힌 이진 탐색 트리: AVL 트리의 구현

➕ 확장 포인트

  • 균형이 깨지는 순간은 삽입과 삭제에서 일어나기 때문에 이 두 과정을 확장해야 함
    • BSTInsert 함수 : 트리에 노드를 추가
    • BSTRemove 함수: 트리에서 노드를 제거
void BSTInsert(BTreeNode ** pRoot, BSTData data)
{
	...
	Rebalance(Root);
}

BTreeNode * BSTRemove(BTreeNode ** pRoot, BSTData target)
{
	...
	Rebalance(pRoot);
	return dNode;
}

 

🔍 추가적인 도구들 1: 트리가 균형을 이루는지를 확인

  • 불균형 여부는 루트 노드를 기준으로 판단
// 트리의 높이를 계산하여 반환
int GetHeight(BTreeNode *bst)
{
	int leftH;
	int rightH;
	
	if(bst == NULL)
		return 0;
	
	leftH = GetHeight(GetLeftSubTree(bst));
	rightH = GetHeight(GetRightSubTree(bst));
	
	// 큰 값의 높이를 반환
	if(leftH > rightH)
		return leftH + 1;
	else
		return rightH + 1;
}

int GetHeightDiff(BTreeNode *bst)
{
	int lsh;
	int rsh;
	
	if(bst == NULL)
		return 0;
	
	lsh = GetHeight(GetLeftSubTree(bst));
	rsh = GetHeight(GetRightSubTree(bst));
	return lsh - rsh;
}

 

🔍 추가적인 도구들 2: LL 회전, RR 회전

  • LL 회전

BTreeNode * RotateLL(BTreeNode * bst)
{
	BTreeNode * pNode;
	BTreeNode * cNode;
	
	// pNode와 cNode가 LL회전을 위해 적절한 위치를 가리키게 한다.
	pNode = bst;
	cNode = GetLeftSubTree(pNode);
	
	// 실제 LL회전을 담당하는 두 개의 문장
	ChangeLeftSubTree(pNode, GetRightSubTree(cNode));
	ChangeRightSubTree(cNode, pNode);
	
	// LL회전으로 인해서 변경된 루트 노드의 주소 값 반환
	return cNode;
}
  • RR 회전
BTreeNode * RotateRR(BTreeNode * bst)
{
	BTreeNode * pNode;
	BTreeNode * cNode;
	
	// pNode와 cNode가 RR회전을 위해 적절한 위치를 가리키게 한다.
	pNode = bst;
	cNode = GetRightSubTree(pNode);
	
	// 실제 RR회전을 담당하는 두 개의 문장
	ChangeRightSubTree(pNode, GetLeftSubTree(cNode));
	ChangeLeftSubTree(cNode, pNode);
	
	// RR회전으로 인해서 변경된 루트 노드의 주소 값 반환
	return cNode;
}

 

🔍 추가적인 도구들 3: LR 회전, RL 회전

  • LR 회전 & RL 회전

BTreeNode * RotateLR(BTreeNode * bst)
{
	BTreeNode * pNode;
	BTreeNode * cNode;
	
	// pNode와 cNode가 LR 회전을 위해 적절한 위치를 가리키게 한다.
	pNode = bst;
	cNode = GetLeftSubTree(pNode);
	
	// 실제 LR 회전을 담당하는 두 개의 문장
	ChangeLeftSubTree(pNode, RotateRR(cNode)); // 부분적 RR 회전
	return RotateLL(pNode);  // LL 회전
}

BTreeNode * RotateRL(BTreeNode * bst)
{
	BTreeNode * pNode;
	BTreeNode * cNode;
	
	// pNode와 cNode가 RL 회전을 위해 적절한 위치를 가리키게 한다.
	pNode = bst;
	cNode = GetRightSubTree(pNode);
	
	// 실제 RL 회전을 담당하는 두 개의 문장
	ChangeRightSubTree(pNode, RotateLL(cNode)); // 부분적 LL 회전
	return RotateRR(pNode);  // RR 회전
}

 

🔍 추가적인 도구들 4: Rebalance 함수

BTreeNode * Rebalance(BTreeNode ** pRoot)
{
	int hDiff = GetHeightDiff(*pRoot);  // 균형 인수 계산
	
	// LL or LR 상태
	if(hDiff > 1)
	{
		if(GetHeightDiff(GetLeftSubTree(*pRoot)) > 0) // 왼쪽 서브트리가 왼쪽으로 치우쳐 있다면
			*pRoot = RotateLL(*pRoot);
		else
			*pRoot = RotateLR(*pRoot);
	}
	// RR or RL 상태
	if(hDiff < -1)
	{
		if(GetHeightDiff(GetRightSubTree(*pRoot)) < 0) // 오른쪽 서브트리가 오른쪽으로 치우쳐 있다면
			*pRoot = RotateRR(*pRoot);
		else
			*pRoot = RotateRL(*pRoot);
	}
	
	return *pRoot;
}

 

자료

  • 윤성우의 열혈 자료구조 (윤성우 저, 2023.10)

'[컴퓨터 과학자 스터디] > 자료구조' 카테고리의 다른 글

그래프(Graph)  (0) 2024.12.05
테이블(Table)과 해쉬(Hash)  (2) 2024.12.05
탐색(Search) 1  (1) 2024.12.02
정렬(Sorting)  (0) 2024.11.27
우선순위 큐(Priority Queue)와 힙(Heap)  (0) 2024.11.24

블로그의 정보

프리니의 코드저장소

Frinee

활동하기