이제 앞에서 설계한 이진 탐색 트리를 구체적으로 구현합시다.
먼저 이진 탐색 트리의 추가 기능인 AddBook 메서드를 구현하기로 해요.
bool BinSearchTree::AddBook(int bnum,string title) { //먼저 주요 키인 도서 번호로 부모 노드를 검색하기로 해요. Node *parent = Find(root,bnum); if(parent) { //검색한 부모의 키가 입력한 도서 번호와 일치하였는지 확인하세요. Book *sbook = parent->book; if(sbook->GetBNum() == bnum) { //일치하면 도서를 보관하지 않고 추가 실패를 반환하세요. return false; } } //입력받은 인자로 도서 개체를 생성하세요. Book *book = new Book(bnum,title); //그리고 검색한 부모의 자식으로 도서 개체를 보관한 노드를 매달게 하세요. HangNode(parent,new Node(book)); return true; }
다음은 이진 탐색 트리의 가장 핵심적인 검색 기능입니다. 이 기능은 내부 메서드로 추가할 때 부모 노드를 검색하고 삭제나 검색 기능에서 입력한 도서 번호와 같은 키 값을 갖는 노드를 찾는 메서드입니다.
Node *BinSearchTree::Find(Node *sr, int bnum)const { //만약 검색할 서브 트리의 루트가 0인지 확인하세요. if(sr==0) { //서브 트리의 루트가 0이라는 말은 root가 0이라는 말과 같습니다. //이 메서드에서는 재귀 호출을 사용할 것이며 언제나 서브 트리가 존재할 때만 호출할 것입니다. //따라서 처음 호출할 때 sr이 0일 때만 이 조건에 해당하며 결국 root가 0이라는 말과 같습니다. return sr; } //입력 받은 도서 번호와 서브 트리의 루트에 보관한 도서의 번호의 차이를 계산합니다. int gap = bnum - sr->book->GetBNum(); if(gap==0) { //만약 차이가 없다면 sr을 반환하세요. return sr; } if(gap>0) { //차이가 0보다 크면 오른쪽 서브 트리에서 탐색해야 합니다. if(sr->right) { //만약 오른쪽 서브 트리가 있으면 재귀 호출하여 탐색한 값을 by pass 하세요. return Find(sr->right,bnum); } //만약 오른쪽 서브 트리가 없으면 탐색을 끝내고 현재 sr을 반환하세요. return sr; } //gap이 0이거나 0보다 크면 반환했죠. 여기에 도달했다는 것은 gap이 0보다 작다는 것입니다. if(sr->left) { //왼쪽 서브 트리가 있으면 재귀 호출하여 탐색한 값을 by pass 하세요. return Find(sr->left,bnum); } //만약 왼쪽 서브 트리가 없으면 탐색을 끝내고 현재 sr을 반환하세요. return sr; }
이제 추가할 노드와 부모 노드의 연결하는 메서드를 구현합시다.
void BinSearchTree::HangNode(Node *parent, Node *now) { if(parent == 0) { //만약 부모가 없으며 새로운 노드가 첫 노드이므로 root로 설정하세요. root = now; return; } //먼저 새로운 노드의 parent 링크를 설정하세요. now->parent = parent; //그리고 부모 노드의 어느 쪽에 매달 것인지 판단하기 위해 키 값의 차이를 계산하세요. int gap = now->book->GetBNum() - parent->book->GetBNum(); //차이가 0보다 크면 부모의 오른쪽 자식으로 매달고 그렇지 않으면 왼쪽 자식으로 매다세요. if(gap>0) { parent->right = now; } else { parent->left = now; } }
이제 사용하는 곳에서 검색에 사용할 FindBook 메서드를 구현합시다.
bool BinSearchTree::FindBook(int bnum)const { //먼저 bnum으로 검색합니다. Node *now = Find(root,bnum); if(now) { //now가 존재하면 now에 보관한 도서 개체를 구하세요. Book *sbook = now->book; if(sbook->GetBNum() == bnum) { //만약 도서 개체의 도서 번호와 입력받은 bnum이 같으면 검색 성공입니다. //여기에서는 도서 정보를 출력하게로 했죠. sbook->View(); return true; } } //여기에 도달했다는 것은 검색 실패를 의미합니다. return false; }
도서 삭제 기능을 구현합시다.
bool BinSearchTree::RemoveBook(int bnum) { //먼저 bnum으로 노드를 검색하세요. Node *now = Find(root,bnum); if(now) { //now가 존재하면 now에 보관한 도서 개체를 구하세요. Book *sbook = now->book; if(sbook->GetBNum() == bnum) { //만약 도서 개체의 도서 번호와 입력받은 bnum이 같으면 삭제할 노드입니다. //트리에서 연결을 끊으세요. DeHang(now); return true; } } //여기에 도달했다는 것은 삭제할 노드가 없다는 것입니다. return false; }
이제 연결을 끊는 메서드를 구현합시다.
void BinSearchTree::DeHang(Node *now) { //먼저 자식이 양쪽에 있으면 자신과 같은 성향을 갖는 노드를 찾습니다. if(now->left && now->right) { //자신과 같은 성향을 갖는 노드는 왼쪽 서브 트리에서 제일 큰 값을 갖는 노드와 //오른쪽 서브 트리에서 제일 작은 값을 갖는 노드입니다. //여기에서는 왼쪽 서브 트리에서 찾기로 해요. Node *alter = now->left; //왼쪽 서브 트리에서 제일 큰 값을 갖는 노드를 찾아야 하므로 //오른쪽 자식이 있으면 이동하면서 처음으로 오른쪽 자식이 없는 노드를 찾으세요. while(alter->right) { alter = alter->right; } //now에 보관한 도서 개체와 대체할 노드에 보관한 도서 개체를 교환하세요. Book *tmpbook = now->book; now->book = alter->book; alter->book = tmpbook; //이제 대체할 노드를 now에 설정하세요. //언제나 now는 자식이 없거나 하나만 있는 노드라는 논리가 성립합니다. now = alter; } //자식이 없거나 하나만 있을 때는 //자신의 부모에 자신이 있던 위치에 자식을 매달고 //자식의 부모 링크를 자신의 부모로 변경합니다. //이를 위해 먼저 부모를 기억하는 변수를 선어하여 설정하세요. Node *pa = now->parent; Node *child = 0; //자식은 자신의 왼쪽 노드 혹은 오른쪽 노드입니다. //아래의 코드는 왼쪽에 자식이 있다면 논리합 연산 뒤에 대입 구문은 수행하지 않습니다. //만약 오른쪽에 자식이 있거나 아무 자식이 없으면 논리합 연산 뒤에 대입 구문도 수행합니다. //따라서 아래 구문으로 정확하게 자신의 자식 노드를 설정할 수 있습니다. (child = now->left)||(child = now->right); if(pa) { //부모가 있다면 부모에 자신이 있던 위치에 자식을 설정합니다. if(pa->left == now) { pa->left = child; } else { pa->right = child; } } else { //부모가 없다는 것은 now가 root라는 것입니다. //따라서 now를 삭제하면 자식 노드를 root로 설정하세요. root = child; } if(child) { //자식이 있다면 자식의 부모 링크를 pa로 설정하세요. child->parent = pa; } //삭제할 now의 도서 개체를 소멸하고 now 노드도 소멸하세요. delete now->book; delete now; }
전체 출력하는 ListAll 메서드를 구현합시다.
void BinSearchTree::ListAll()const { //여기에서는 전위 순회, 중위 순회, 후위 순회를 하며 모든 노드를 출력하기로 해요. cout<<"=== Pre order ===="<<endl; PreOrder(root); cout<<"=== In order ===="<<endl; InOrder(root); cout<<"=== Post order ===="<<endl; PostOrder(root); }
다음은 전위 순회입니다.
void BinSearchTree::PreOrder(Node *sr)const { if(sr) { //sr이 참일 때만 수행합니다. //따라서 sr이 0일 때가 재귀의 탈출 조건입니다. //먼저 서브 트리의 루트 정보를 출력합니다. sr->book->View(); //그리고 왼쪽 서브 트리와 오른쪽 서브 트리를 재귀 호출로 순회하세요. PreOrder(sr->left); PreOrder(sr->right); } }
중위 순회와 후위 순회도 서브 트리의 루트 정보를 언제 출력하는지만 차이가 있을 뿐이죠.
void BinSearchTree::InOrder(Node *sr)const { if(sr) { InOrder(sr->left); sr->book->View(); InOrder(sr->right); } } void BinSearchTree::PostOrder(Node *sr)const { if(sr) { PostOrder(sr->left); PostOrder(sr->right); sr->book->View(); } }
소멸자에서는 후위 순회로 모든 노드를 소멸하세요.
BinSearchTree::~BinSearchTree(void) { Clear(root); } void BinSearchTree::Clear(Node *sr) { if(sr) { Clear(sr->left); Clear(sr->right); //노드에 보관한 도서 개체를 소멸한 후에 노드를 소멸하세요. delete sr->book; delete sr; } }