Binary Search Tree(이진 탐색 트리)
이진 트리는 하나의 노드가 두개의 포인터를 가지고 있다
그중에서 이진 탐색 트리 (binary search tree) 는 이진 탐색과 연결 리스트를 결합한 자료구조라고 할 수 있다
각 노드의 왼쪽 서브트리에는 해당 노드의 값보다 작은 값을 지닌 노드들로 이루어져 있다
각 노드의 오른쪽 서브트리에는 해당 노드의 값보다 큰 값을 지닌 노드들로 이루어져 있다
중복된 노드가 없어야 한다
왼쪽 서브트리 , 오른쪽 서브트리 또한 이진 탐색 트리이다
순회
트리의 모든 노드를 한 번씩 방문하여 모든 정보에 대한 선형 순서를 만들어내는 방법
전위순회 ( PreOrder Traversal ) : Root 를 가장 먼저 들린다
Root 를 방문
왼쪽 Subtree 를 방문
오른쪽 Subtree 를 방문
중위순회 ( InOrder Traversal ) : Root 를 중간에 들린다
왼쪽 Subtree 를 방문
Root 를 방문
오른쪽 Subtree 를 방문
후위순회 ( PostOrder Traversal) : Root 를 가장 나중에 들린다
왼쪽 Subtree 를 방문
오른쪽 Subtree 를 방문
Root 를 방문
트리 노드 삭제 루틴
트리 노드를 삭제할시 , 3 가지 경우로 나뉜다
하위 노드가 없는경우
하위 노드가 하나만 있는경우 ( left 또는 right)
하위 노드를 삭제된 노드 자리에 앉힌다
하위 노드가 둘다 있는 경우
해당 노드가 head 루트 노드 보다 작으면 right node 를 삭제된 노드 자리에 앉힌다
( 해당 노드가 head 루트 노드 보다 크면 left node 를 삭제된 노드 자리에 앉힌다 )
나머지 노드 left node 를 새로 앉힌 노드의 left node 로 맞춘다
( 나머지 노드가 right 노드 였던 경우 새로 앉힌 노드의 right node 에 맞춘다 )
순회 순서
위와 같은 이진탐색 트리가 존재할때
중위순회를 할경우
4,5,6,7,10,11,13,17,20 순으로 순회 한다 즉 그림으로 그리면
위와 같은 경로가 그려진다
모든 노드는 left, right, root 노드를 가지고 있으며 , 각각 노드들이 또다른 어떤 노드의 루트 노드가 될 수 있다 , 즉 , 순회할수 있는 방향은 left, right, root 등으로 나뉘어지는게 어떤조건에서 어떤 경로로 이동하는 것일까 ?
일단 첫시작점을 이미 찾았다는 전제 하에서
가고자 하는 방향이 nullptr 인 경우 무시하기로 한다
또한 순회하면서 나오는 값들은 이전값보다는 높아야한다
4 -> rightNode -> leftNode
5 -> rootNode
6 -> rightNode / -> leftNode(nullptr)
7 -> rootNode -> rootNode-> rootNode (7 보다 높지않은 값들 (6,4) 을 거쳐서 10 에 도달
10 -> rightNode->leftNode
11 -> rootNode
13 ->rightNode -> leftNode
17 -> rootNode
20 ->/rightNode(nullptr)
즉 위패턴으로 이터레이터 연산자 오버로딩 operator ++ () 을 구현할 경우
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void operator ++ ()
{
assert(m_pNode != nullptr);
if (m_pNode->m_pRightNode != nullptr)
{
m_pNode = m_pNode->m_pRightNode;
while (m_pNode->m_pLeftNode != nullptr)
{
m_pNode = m_pNode->m_pLeftNode;
}
return;
}
else if (m_pNode->m_iData < m_pNode->m_pRootNode->m_iData)
{
m_pNode = m_pNode->m_pRootNode;
return;
}
while (m_pNode->m_iData > m_pNode->m_pRootNode->m_iData)
{
m_pNode = m_pNode->m_pRootNode;
}
m_pNode = m_pNode->m_pRootNode;
}
cs
위와 같은 코드가 된다
그럼 반대 루틴을 보면
20 -> leftNode
17 -> rootNode -> rootNode
13 -> leftNode
11 -> rootNode -> rootNode
10 -> leftNode -> rightNode -> rightNode //m_pHead
7 -> rootNode
6 -> leftNode
5 -> rootNode -> rootNode
4
일단 시작은 leftNode 로 간뒤 rightNode 를 찾는다 , 그리고 rightNode 가 나오면 나올수록 계속 rightNode 로 간다
그리고 rootNode->rootNode 가 되는 부분은 왼쪽으로 뻗은길을 찾기위한 반복되는 과정이라고 할수있다
즉 해당 노드의 데이터가 노드의 루트 데이터 보다 큰경우를 찾는것이다
즉 오른쪽으로 뻗은길이 나올때까지 이동한다
(pNode->iData > pNode->pRoot->iData)
또다른 트리를 통해 다시 한번 자세히 살펴보면
4,10,11,13,26,27,28,29,30,31 를 노드로 가지고 있는 트리다 이걸 제일 큰값 31에서 제일 작은값 4까지
순회하는 로직을 그려보면 붉은 화살표와 같다
순회 동작 과정
31
처음 31 에서 시작할때는 바로 root 데이터가 30 이므로 ( 왼쪽으로 뻗은길 )
30 으로 한칸 이동하면 끝난다
30
30 에서는 왼쪽으로 뻗은길이 있기떄문에 바로 13 으로 갈수 있지만
30 의 leftNode 자식이 있으므로 (30 보다 작고 13 보다 큰수 )
leftNode 로 가야 한다
그렇게 해서 29 로 이동한후 rightNode 가 있는지 (29 와 30 사이의 수 ) 파악하고 없으면 대기 한다
29
29 에서도 마찬가지로 leftNode (27) 로 이동
27 에서 rightNode 가 있는지 확인 , 28 발견 했으므로 rightNode 로 이동한다
이때 주의할점은 rightNode 로 한번 갈때는
가장 끝의 rightNode 까지 이동해야한다는것이다
여기서 나오는 rightNode 들은 27 과 29 사이의 수들이다
즉 가장끝 rightNode 일 수록 29 에 가까운 바로 아랫수가 될것이다
그러나 28 이 끝이므로 28 에서 멈춘다
28
28 은 leftNode 가 없다 즉 root 로 이동해야 하는데 ,
root 로 이동할때는 왼쪽으로 뻗은길이면 한번만 이동하면되고
오른쪽으로 뻗은길이면 왼쪽으로 뻗은길이 나올때까지 계속 이동해야 한다
(28 > root->iData) 조건을 만족할때까지
그런데 왼쪽으로 뻗은길 즉 28 은 root->iData 인 27 보다 크므로
한번만 이동한다
27
LeftNode 가 있으므로 , leftNode 로 이동
26 에서 멈추고 rightNode 가 있는지 확인 , 없으므로 그자리에 대기
26
leftNode,RightNode 둘다 없으므로 아까처럼 root 를 타야한다
그런데 오른쪽길로 뻗어있다 즉 ( 26 < pRoot->iData) 식이 되므로
26 > pRoot->iData 조건이 만족할때까지 계속 이동한다
그렇게 하면 27, 29, 30,( 셋다 전부 26 보다 크다 ) 거쳐서 13 에 도착한다 ( 26 보다 작은수 )
즉 26 보다 작은수가 나올때까지 계속 이동한것이다
13 에서 대기한다
13
leftNode 를 확인하고 , 11 이 있으므로 11 로 이동 , rightNode 를 확인한다
그러나 rightNode 가 없으므로 11 에서 대기 한다
1 1
13 에서 대기한다 leftNode, RightNode 가 없으므로 root 로 올라가는데 마찬가지로
왼쪽으로 뻗은길을 찾을때까지 계속올라간다 , 그러나 바로 왼쪽으로 뻗으므로 13 > (pRoot->iData == 10)
바로 한칸 이동한다
10 ( phead)
head 에 도달 이때도 마찬가지로 leftNode 를 확인 4 가 있으므로 4 로 이동한뒤
rightNode 를 확인한다
없으므로 ,4 에서 대기한다
4
끝에 도달
그림으로 그리면 위와 같다 ,
위의 경로를 패턴화 시키면
아래와 같은 순서로 동작한다
1. LeftNode 를 확인 , LeftNode 가 있는경우 이동
그리고 rightNode 가 있는경우 가장 끝의 rightNode 까지 이동
2. LeftNode 가 없으므로 Root 로 이동 이때
해당 노드 데이터가 root 데이터보다 큰경우가 올때까지 계속
root 로 이동 ( 왼쪽으로 뻗은길이 나올때까지 이동 )
그래서 코드로 정리하면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void operator -- ()
{
assert(m_pNode != nullptr);
if (m_pNode->m_pLeftNode != nullptr)
{
m_pNode = m_pNode->m_pLeftNode;
while(m_pNode->m_pRightNode != nullptr)
{
m_pNode = m_pNode->m_pRightNode;
}
return;
}
else if (m_pNode->m_iData > m_pNode->m_pRootNode->m_iData)
{
m_pNode = m_pNode->m_pRootNode;
return;
}
while (m_pNode->m_iData < m_pNode->m_pRootNode->m_iData)
{
m_pNode = m_pNode->m_pRootNode;
}
m_pNode = m_pNode->m_pRootNode;
}
cs
위와 같은 코드가 나온다