레이블이 Data Structure인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Data Structure인 게시물을 표시합니다. 모든 게시물 표시

Heap

자료 구조에서의 Heap은 Heap property를 만족하는 특별한 트리이다. Dynamic memory allocation의 heap과 다르니 혼동하지 않아야 한다.

Heap property
 - 부모 노드와 자식노드는 Heap 전체에 적용된 순서에 따라 정렬된다.
 - 만약 부모 노드가 자식노드의 키값보다 항상 크거나 같다면 최대힙(max heap)이라 한다.
 - 반대로 부모노드가 자식노드의 키값보다 항상 작거나 같아면 최소힙(min heap)이라고 한다.
- 따라서 max heap에서 root는 그 트리의 최대값이, min heap에서의 root는 그 트리의 최소값이 된다.
- 일반적으로 heap은 complete binary tree로 구현된다.


Heap의 구현

Heap은 complete binary tree특징이 있으므로 주로 배열을 이용해서 구현을 한다.
해당 노드(인덱스) "i"가 주어졌을 때 부모 노드와 자식노드를 구하는 방법은 아래와 같다.

indexParent = floor( (i-1) / 2 )
indexLeft = (2*i) + 1;
indexRight = (2*i) + 2;

이제 위 공식을 이용해 클래스를 만들어보자. 원래 자료 관리에 제한이 없게 dynamic memory를 써야 하지만, 알고리즘을 파악하는게 먼저니 배열을 미리 잡아놓자.

#define HEAP_SIZE 100
class Heap
{
private:
    int elements_[HEAP_SIZE];
    int elementIndex_;

public:
    Heap()
    {
        memset( elements_, 0, sizeof( elements_) );
        elementIndex_ = 0;
    }

    bool addData( int value );
};


* Add

데이터를 저장할 배열을 하나 생성하고, 마지막 인덱스를 저장할 값도 하나 생성하자. 이 인덱스가 힙의 크기가 된다. 일단 사용할 메서드는 값을 넣는 addData() 메서드이다.

bool Heap::addData( int value )
{
    if( elementIndex_ == 0 )
    {
        elements_[elementIndex_++] = value;
        return true;
    }

    if( elementIndex_ == HEAP_SIZE )
    {
        return false;
    }

    int index = elementIndex_;
    elements_[index] = value;

    do
    {
        int iParent = static_cast<int>(floor(index-1) / 2));
        if( elements_[iParent] < elements_[index] )
        {
            // swap
            int tmp = elements_[iParent];
            elements_[iParent] = elements_[index];
            elements_[index] = tmp;
        }
        else
        {
            break;
        }

        index = iParent;
    }while( index > 0 );

    ++elementIndex_;
    return true;
}

값을 넣을 때 일단 힙의 인덱스를 체크하자. 0이라면 빈 힙이므로 그냥 값을 넣자. 만약 인덱스가 최대 값이면 배열이 다 찬 상태이므로 에러처리를 하자.

이제 맨 배열의 맨 마지막 자리에 전달받는 값을 대입하고 부모인덱스와 비교를 하자. 위에 함수에서는 클 경우 swap이 발생하므로 최대힙이 만들어진다. 만약 부모인덱스와 비교해서 크지 않다면 더 처리할 필요는 없다. 맨 마지막으로 값이 하나 늘었으니 힙인덱스를 하나 늘려주자.

위 연산은 데이터의 길이가 증가해도 자신의 부모 노드와 값을 비교해서 swap하므로 O(logN) 시간복잡도를 갖는다.

* Pop

힙에서 데이터를 삭제하는 경우는 선택해서 삭제 할 수도 있지만 일반적으로 맨 위의 값을 꺼내오는 경우이다.

complete balance tree 특성 상 현재 노드에서 right child에만 값이 있는 경우는 없으므로 left child가 최대 인댁스를 넘었으면 그 노드가 leaf인 경우이고, left는 넘지 않았는데 right만 넘었으면 left가 leaf가 된다. 그래서 현재 노드에서 child와 비교 후 max heap일 경우 두 child 중 큰 값과 swap하고, min heap일 경우 두 child중 작은 값과 swap하면 된다.
void Heap::_swap( int index1, int index2 )
{
    int temp = elements_[index1];
    elements_[index1] = elements_[index2];
    elements_[index2] = temp;
}

int Heap::popData()
{
    if( elementIndex_ == 0 )
        return 0;
 
    int head = elements_[0];
    elements_[0] = elements_[--elementIndex_];

    int currentIndex = 0; 

    for(;;)
    {
        int left = (2*currentIndex) + 1;
        int right = (2*currentIndex) + 2;

        if( left >= elementIndex_ )
        {
            // balance 트리이기 때문에 left가 범위를 벗어났으면 더 비교할 필요가 없다
            break;
        }
        else if( right >= elementIndex_ )
        {
            // left가 범위를 벗어나지 않았는데 right가 범위를 벗어난 경우는 left만 남은 경우이므로 left만 비교하자.
            if( elements_[currentIndex] < elements_[left] )
            {
                _swap( currentIndex, left );
            }
            break;
        }
        else
        {
            // 두 자식 모두 비교해서 다음 인덱스를 세팅하자.
            int selectedIndex = elements_[left] > elements_[right] ? left : right;
            _swap( currentIndex, selectedIndex );
            currentIndex = selectedIndex;
        }
    }

    return head;
}


출처
http://en.wikipedia.org/wiki/Binary_heap

Binary search tree

Binary search tree (BST)는 Tree의 특별한 경우로 아래의 몇가지 항목을 만족시킨다.

 - 왼쪽 서브트리 ( left subtree )는 현재 노드의 값 보다 작은 노드들만 위치한다.
 - 오른쪽 서브트리 ( right subtree ) 는 현재 노드의 값 보다 큰 노드들만 위치한다.
 - 왼쪽, 오른쪽 서브 트리 모두 각각 binary search tree이다.
 - 모든 노드는 최대 2개의 자손( successor )를 가질 수 있다.
 - 중복되는 값을 가진 노드는 없어야 한다.
 - 모든 노드는 root로 부터 고유한 길( unique path )이 있다.

Binary Tree 와 Binary search tree의 차이점.

 Binary search tree (BST)는 Binary Tree의 특이한 케이스이다. Binary Tree는 노드가 2개의 자식 노드를 가질 수 있는데, BST는 그 자식 노드가 왼쪽에는 자신보다 작은값을, 오른쪽에는 자신보다 큰 값을 갖는 경우를 말한다.

BST의 특이점

Binary "SEARCH" tree라는 이름에서 알 수 있듯, root 및 모든 subtree를 기준으로 특정 값을 찾을 때 찾는 값이 해당 노드값 보다 작으면 왼쪽 subtree를 크다면 오른쪽 subtree를 검색하면 원하는 값을 O(logN)이 런타임으로 찾을 수 있다.

트리를 InOrder방식으로 방문하면, 오름 차순으로 정렬된 데이터를 얻을 수 있다.


BST with C++

BST에 사용될 메서드를 생각해보자.
1. 값을 추가하는 add() 메서드
2. 값을 제거하는 remove() 메서드
3. 값이 존재하는지 찾는 find()메서드

일단 사용할 Node클래스를 먼저 정의하자.

class Node
{
public:
    int data_;
    Node* left_;
    Node* right_;

    Node()
    {
        data_ = 0;
        left_ = right_ = NULL;
    }
};

사용할 Tree클래스는 이 Node클래스를 이용해서 메서드를 작성하자.


  값을 추가하는 add() 메서드
 외부에서 호출하는 addData() 함수를 하나 만들고, 내부적으로 재귀적으로 사용할 _addData() 함수를 만들어서 밖에서는 안보이도록 숨기자. addData() 에서는 추가할 값만 받고, _addData() 함수에서는 부모 노드 값을 받아서 적당한 위치를 찾아 데이터를 만들자.

bool Tree::addData( int value )
{
    return _addData( root_, value );  
}

외부에서 보이는 addData() 메서드이다. 전달인자는 추가할 값 하나.

bool Tree::_addData( Node*& node, int value )
{
    if( node == NULL )
    {
        node = new Node;
        if( node == NULL )
            return false;

            node->data_ = value;
            node->left_ = node->right_ = NULL;

            return true;
    }
    else
    {
        if( node->data_ > value )
        {
            return _addData( node->left_, value );
        }
        else if( node->data_ < value )
        {
            return _addData( node->right_, value );
        }
    }

    return false;
}

 내부적으로 사용하는 _addData() 메서드이다. 추가할 노드의 부모 노드의 참조를 전달인자로 받는다. 만약 Node*& 연산이 가독성이 떨어진다고 생각되면 typedef를 이용해 예쁘게 만들어도 된다. _addData()에 node가 NULL인 경우는 원하는 위치를 찾은 경우이다. 메모리를 할당하고 값을 세팅하자.

 넘겨받는 node가 NULL이 아닐 경우 자리를 찾기 위해 값 비교를 한다. 만약 value값이 작다면 node의 left_를, 크다면 right_를 전달인자로 _addData()를 recursive 호출 하자. 그럼 적당한 자리를 찾아가서 값이 추가가 된다.

이런식으로 add된 값들은 balance되지 않는다. 따라서 10, 9, 8, 7 이런식으로 add를 하면 10을 루트로 가진 왼쪽노드로만 이루어진 트리가 만들어진다. AVL트리는 나중에 따로 연구하자.

 값을 제거하는 remove()메서드
 상대적으로 remove 메서드는 add보다 조금 복잡하다. leaf를 remove하면 간단하지만, 중간에 값을 remove하면 트리를 새로 구성해야 하기 때문이다.

remove하는 시나리오는 크게 3개이다.
 - leaf를 remove하는 경우 : 그냥 삭제하자
 - left에 subtree가 있는 경우 : left의 subtree에서 가장 큰 값을 가져와 현재 node에 대입하고, 그 노드를 제거
 - right에 subtree가 있는 경우 : right의 subtree에서 가장 작은 값을 가져와 현재 node에 대입하고, 그 노드는 제거
 - remove를 재귀적으로 호출하도록 하자.


bool Tree::removeData( int value )
{
    Node** node = _findNode( value );
    if( node == NULL )
        return false;

    return _removeData( *node );
}

find함수를 이용해 제거 될 node를 찾는다. 이제 이 node를 remove해보자.

bool Tree::_removeData( Node*& node )
{
    if( node == NULL )
        return false;

    if( node->left_ == NULL && node->right_ == NULL )
    {
        delete node;
        node = NULL;
    }
    else if( node->right_ != NULL )
    {
        // .2 get smallest node from right node
        Node** targetNode = &node->right_;
        for(;;)
        {
            if( (*targetNode)->left_ == NULL )
            {
                node->data_ = (*targetNode)->data_;
                _removeData( (*targetNode) );
                break;
            }
            else
            {
                targetNode = &(*targetNode)->left_;
            }
        } 
    }
    else if( node->left_ != NULL )
    {
        // .3 get biggest node from left node
        Node** targetNode = &node->left_;
        for(;;)
        {
            if( (*targetNode)->right_ == NULL )
                {
                node->data_ = (*targetNode)->data_;
                _removeData( (*targetNode) );
                break;
            }
            else
            {
                targetNode = &(*targetNode)->right_;
            }
        } 
    }
  
    return true;
}

내부적으로 호출되는 _removeData() 메서드이다. 일단 제거될 node가 leaf인지 확인하자. 이 노드는 그냥 제거하면 된다. leaf가 아니라면 머리를 써야된다. left나 right중 아무 노드나 골라서 먼저 찾아보자. 이 자리에는 left노드 중 가장 큰 값이나, right 노드 중 가장 작은 값이 오면 BST의 조건을 만족한다.

코드에서는 right먼저 검사를 했다. right에서 가장 작은 값을 찾자. BST특성 상 작은 값은 left에 존재하므로 left만 따라가면 된다. 만약 left가 NULL이라면 이 값이 가장 작은 값이다.  그럼 그 값을 복사해서 내 자리에 놓고 그 자리는 제거한다. 그럼 재귀적으로 제거된 값에 대해서 똑같은 연산이 반복되서 트리가 구성된다.

 값을 찾는 find() 메서드
 BST의 특징을 따라 노드를 따가라면 된다. 현재 노드와 비교해서 값이 작으면 left로, 반대로 크다면 right로 이동해서 똑같이 비교를 하면 된다.

Node** Tree::_findNode( int value )
{
    Node** pNode = &root_;
    for(;;)
    {
        if( pNode == NULL || (*pNode) == NULL )
            break;

        if( (*pNode)->data_ == value )
            break;

        if( (*pNode)->data_ > value )
            pNode = &(*pNode)->left_;
        else
            pNode = &(*pNode)->right_;
    }

    return pNode;
}

출처
http://en.wikipedia.org/wiki/Binary_search_tree
홈