이진 탐색 트리
- 데이터의 저장 규칙이 있는 이진트리
- 데이터 저장 규칙
-> 부모 노드를 기준으로 작은 값은 좌측, 큰 값은 우측에 저장
- 데이터의 삽입 과정
-> 루트 노드부터 비교하면서 삽입하고자 하는 데이터가 작으면 왼쪽으로 크면 오른쪽으로 이동하면서 자리를 찾음
-> 탐색도 이런 과정을 거침
- 삭제
-> 삭제는 고려해야 할 상황이 3가지가 있음
-> 1. 삭제할 노드가 단말 노드인 경우
* 부모 노드(4)에서 왼쪽, 오른쪽(7) 노드를 삭제시킴
-> 2. 삭제할 노드가 하나의 자식 노드(서브 트리)를 갖는 경우
* 삭제하고자 하는 노드를 지우고 자식 노드를 삭제하고자 하는 노드의 부모 노드와 연결
* 삭제하려는 노드가 오른쪽이었다면 오른쪽에 연결하고 왼쪽이었다면 왼쪽에 연결
-> 3. 삭제할 노드가 두 개의 자식노드(서브트리)를 갖는 경우
* 삭제하고자 하는 노드의 데이터의 서브 트리에서 좌측에서 가장 큰 값과 우측에서 가장 작은 값을 찾음
* 아래의 경우 8을 삭제할 경우 8의 좌 우측에서 가장 작고 큰 값인 7과 9를 찾음
* 둘 중 하나의 값으로 대체
* 우측과 같은 경우에는 대입->삭제->연결의 순서
-> 4. 삭제할 노드가 루트노드인 경우
* 루트노
이진 탐색 트리의 구현
- 삽입
-> 비교하면서 NULL을 만날 때까지 탐색하고 그 위치에 삽입
void BSTInsert(BTreeNode * pRoot, BSTData data)
{
BTreeNode * pNode = NULL; // 부모 노드
BTreeNode * cNode = pRoot; // 현재 노드 (트리 자체가 루트 노드)
BTreeNode * nNode = NULL; // 새로 저장할 노드
// NULL이 나올때까지 탐색
while(cNode != NULL)
{
if(data == GetData(cNode)) {
return; // 키의 중복을 허용하지 않음
}
pNode = cNode;
if(GetData(cNode) > data) {
cNode = GetLeftSubTree(cNode);
}
else {
cNode = GetRightSubTree(cNode);
} // 비교하면서 작으면 왼쪽 크면 오른쪽으로
}
// pNode의 서브 노드에 추가할 새 노드의 생성
nNode = MakeBTreeNode(); // 새 노드의 생성
SetData(nNode, data); // 새 노드에 데이터 저장
// pNode의 서브 노드에 새 노드를 추가
if(pNode != NULL) // 새 노드가 루트 노드가 아니면
{
if(data < GetData(pNode)) {
MakeLeftSubTree(pNode, nNode);
}
else {
MakeRightSubTree(pNode, nNode);
} // 저장할때도 작으면 왼쪽에 크면 오른쪽에 저장
}
else // 새 노드가 루트 노드면
{
*pRoot = nNode;
}
}
- 탐색
-> 삽입과 같이 좌우측으로 비교하면서 내려가다가 찾고자 하는 데이터의 값을 가진 노드의 주소 값을 반환
BTreeNode * BSTSearch(BTreeNode * bst, BSTData target)
{
BTreeNode * cNode = bst; // 현재 노드
BSTData cd; // 현재 노드의 데이터
while(cNode != NULL)
{
cd = GetData(cNode);
if(target == cd) {
return cNode; // 찾고자 하는 값과 같다면 노드의 주소값을 반환
}
else if(target < cd) {
cNode = GetLeftSubTree(cNode);
}
else {
cNode = GetRightSubTree(cNode);
}
}
// while문을 빠져나왔을 경우는 원하는 데이터를 가진 노드가 없는 경우
return NULL;
}
- 삭제
-> 위와 같이 상황을 3개로 나눠서 구현
BTreeNode * BSTRemove(BTreeNode * pRoot, BSTData target)
{
BTreeNode * pNode = pRoot; // 부모 노드
BTreeNode * cNode = pRoot; // 현재 노드
BTreeNode * dNode; // 삭제하려는 노드
// 삭제 대상을 저장한 노드 탐색
while(cNode != NULL && GetData(cNode) != target)
{
pNode = cNode;
if(target < GetData(cNode))
cNode = GetLeftSubTree(cNode);
else
cNode = GetRightSubTree(cNode);
}
if(cNode == NULL) // 삭제 대상이 존재하지 않는다면,
return NULL;
dNode = cNode; // 삭제 대상을 dNode가 가리키게 한다.
// 첫 번째 경우: 삭제할 노드가 단말 노드인 경우
if(GetLeftSubTree(dNode) == NULL && GetRightSubTree(dNode) == NULL)
{
if(GetLeftSubTree(pNode) == dNode)
RemoveLeftSubTree(pNode);
else
RemoveRightSubTree(pNode);
}
// 두 번째 경우: 하나의 자식 노드를 갖는 경우
else if(GetLeftSubTree(dNode) == NULL || GetRightSubTree(dNode) == NULL)
{
BTreeNode * dcNode; // delete node의 자식 노드
if(GetLeftSubTree(dNode) != NULL)
dcNode = GetLeftSubTree(dNode);
else
dcNode = GetRightSubTree(dNode);
if(GetLeftSubTree(pNode) == dNode)
ChangeLeftSubTree(pNode, dcNode);
else
ChangeRightSubTree(pNode, dcNode);
}
// 세 번째 경우: 두 개의 자식 노드를 모두 갖는 경우
else
{
BTreeNode * mNode = GetRightSubTree(dNode); // 삭제하고자 하는 노드의 오른쪽 노드
BTreeNode * mpNode = dNode;
int delData;
// 우측에서 가장 작은 값을 구함
while(GetLeftSubTree(mNode) != NULL)
{
mpNode = mNode; // 부모-자식노드 연결을 위한 mpNode
mNode = GetLeftSubTree(mNode); // 좌측 노드를 구하면서 NULL이 나올때까지 반복
}
// 대체할 노드에 저장된 값을 삭제할 노드에 대입
delData = GetData(dNode); // 대입 전 데이터 백업
SetData(dNode, GetData(mNode)); // 삭제할 위치에 가장 작은 노드의 값을 대입
// 대체할 노드의 부모 노드와 자식 노드를 연결
if(GetLeftSubTree(mpNode) == mNode) {
ChangeLeftSubTree(mpNode, GetRightSubTree(mNode));
}
else {
ChangeRightSubTree(mpNode, GetRightSubTree(mNode));
}
}
return dNode;
}
'Programming > Data Structure' 카테고리의 다른 글
열혈 자료구조 - 12.2 균형 잡힌 이진 트리 : AVL 트리의 구현 (0) | 2021.01.17 |
---|---|
열혈 자료구조 - 12.1 균형 잡힌 이진 트리 : AVL 트리의 이해 (0) | 2021.01.17 |
열혈 자료구조 - 11.1 탐색의 이해와 보간 탐색 (0) | 2021.01.14 |
열혈 자료구조 - 10.2 복잡하지만 효율적인 알고리즘(3) (0) | 2021.01.10 |
열혈 자료구조 - 10.2 복잡하지만 효율적인 알고리즘(2) (0) | 2021.01.10 |