어떤 자료형이 가지는 기능과 속성만 명시하고 자료형의 구체적인 구현 방법은 명시하지 않은것
Heap(Max Heap, Min Heap)
최대값또는최소값을빠르게찾기위해 쓰인다
Complete Binary Tree(완전이진트리)이다
마지막레벨을제외한모든레벨에노드가꽉차있다.
마지막레벨에노드가있을때는항상왼쪽부터순서대로채워야한다.
배열로구현한다
Max Heap 은항상부모노드의값이자식노드의값보다크다그래서root 노드의값은최대값이온다. 최대값을빠르게구할수있다.
Min Heap 은항상부모노드의값이자식노드의값보다작다, 최소값을 빠르게 구할 수 있다.
노드개수를알면, 트리구조는무조건확정할수있다.
Heapify algorithm
모든노드의위치가 heap 구조를만족하도록, 위치를조정하는 알고리즘
어떤노드가힙 구조(max-heap 이면 부모노드가 항상크다, min-heap 은 그 반대)를위반하고있을때, 그노드의두자식중에서더 큰자식과자신의위치를바꾸는알고리즘, 위치를바꾼뒤에도여전히자식이존재하는경우, 또자식중에서더큰자식과자신의위치를바꾸어야한다. 자식이더이상존재하지않을때까지이과정은계속반복된다.(min-heap 은 반대로 부모 노드가 항상 자식들 보다 작게 위치를 조정한다)
시간복잡도 :
한번자식노드로내려갈때마다노드의갯수가 2배씩증가한다O(nlogn)
그러나실제로는 n/2 개수만큼만연산을해줘도Heapify가완료된다그래서
실제시간복잡도는O(n)이다
힙 트리 와 배열 인덱스 관계
위트리노드의숫자는곧완전이진트리의삽입순서이자, 배열로힙트리를표현할때각각노드의인덱스와동일하다
i번노드의부모는heap[(i / 2)]
i번노드의왼쪽자식은 heap[(2*i)]
i번노드의오른쪽자식은heap[(2*i) + 1]
Insert(삽입)
1.삽입할데이터를가장마지막자리에놓기
2.삽입한데이터의값을부모노드와비교해서크면부모노드와위치를바꾼다
이것을부모노드가더클때까지반복한다(heapify algorithm)
version 1
void Insert(Max_Heap* _heap, int _iData)
{
int* arr = _heap->iDataArr;
int* iSize = &_heap->iSize;
int currentIndex = ++(*iSize); //맨마지막 노드 인덱스 가져오기
/*
맨 마지막 노드에 데이터를 삽입, 해당 데이터가 부모 보다 크면 부모와 위치를 바꾸고 아니면 그대로 둠
*/
while ((currentIndex != 1) && (_iData > arr[currentIndex / 2])) //root가 아니다 && 삽입 데이터가 현재노드의 부모보다 크다
{
arr[currentIndex] = arr[currentIndex / 2]; // 자식 노드자리에 부모 노드 값을 대입, 부모노드가 작으므로 밑으로 내림
currentIndex /= 2; // 현재 인덱스에 부모 인덱스 대입
}
arr[currentIndex] = _iData; //최종 위치에 삽입
}
version 2
void Insert_2(int* arr2, int key, int input)
{
arr2[key] = input; // 마지막 노드에 입력값 넣기
while (key > 1) //root에 도달할때까지 반복
{
if (arr2[key] > arr2[key / 2]) // 부모 노드보다 더 크다면
{
Swap_int(&arr2[key], &arr2[key / 2]); // 부모 노드와 자리를 바꿈
key /= 2; // 부모노드 인덱스로 전환
}
else
{
break;
}
}
}
힙트리특성상 루트 노드의 값을 반환하게 되어있고, Max Heap 에서는 최대값, Min Heap 에서는 최소값을 꺼내게 되어있다.
1.루트노드값(최대값, 최소값)을꺼낸다
2.제일마지막에위치한데이터를루트로옮긴다
3.자식노드중에가장큰값의자리와바꾼다
이것을 자식노드보다클때까지반복한다(heapify algorithm)
version 1
int Pop(Max_Heap* _heap)
{
if (_heap->iSize == 0)
{
return 0;
}
int ret = _heap->iDataArr[1]; // 꺼내올 데이터(root)
_heap->iDataArr[1] = _heap->iDataArr[_heap->iSize--]; // root 자리에 맨마지막 위치 데이터 대입
int parent = 1; //root 부터 시작
int child = 0;
while (1)
{
child = parent * 2; //왼쪽 자식, i번 노드의 왼쪽 자식 == heap[(2*i)]
if (child + 1 <= _heap->iSize && _heap->iDataArr[child] <= _heap->iDataArr[child + 1]) // 오른쪽 자식 인덱스가 존재한다 && 오른쪽 자식이 왼쪽 자식보다 크거나 같다
{
child++; //오른쪽 자식으로 전환
}
if (child > _heap->iSize || _heap->iDataArr[child] < _heap->iDataArr[parent]) //자식인덱스가 힙 사이즈보다 크다(범위를 넘어버림) || 부모가 제일 큰 자식 보다 크다
{
break; //중단
}
Swap_int(&_heap->iDataArr[parent], &_heap->iDataArr[child]); //부모와 제일 큰 자식 자리를 바꿈
parent = child; // 자식의 자식들을 비교하기위해, 자식 노드를 부모 노드로 봄
}
return ret; // 꺼내올 데이터 리턴
}
version 2
int Pop_2(Max_Heap* _heap)
{
int result = _heap->iDataArr[1]; //꺼내올 데이터
int* arr = _heap->iDataArr;
int* iSize = &_heap->iSize;
Swap_int(&arr[1], &arr[*iSize]); //마지막 노드 데이터와 루트 데이터를 바꿈
(*iSize)--; //전체 노드 갯수 감소
int parentIdx = 1;
int childIdx = parentIdx * 2;
if (childIdx + 1 <= *iSize) //오른쪽 자식 노드 인덱스가 노드 전체 갯수를 넘지 않는다, 즉 오른쪽 자식이 존재한다
{
childIdx = (arr[childIdx] > arr[childIdx + 1]) ? childIdx : childIdx + 1; //왼쪽 자식, 오른쪽 자식, 둘 중에 큰 노드 선별
}
while (childIdx <= *iSize && arr[parentIdx] < arr[childIdx]) //자식 인덱스가 범위를 넘지 않는다 && 자식노드가 부모노드 보다 크다
{
Swap_int(&arr[parentIdx], &arr[childIdx]); // 자식 노드와 부모노드 위치 바꾸기
parentIdx = childIdx; //자식 노드를 부모 노드로 봄
childIdx *= 2; //왼쪽 자식 노드
if (childIdx + 1 <= *iSize) //오른쪽 자식 노드 인덱스가 노드 전체 갯수를 넘지 않는다, 즉 오른쪽 자식이 존재한다
{
childIdx = (arr[childIdx] > arr[childIdx + 1]) ? childIdx : childIdx + 1; //왼쪽 자식, 오른쪽 자식, 둘 중에 큰 노드 선별
}
}
return result;
}
전체 코드
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT_HEAP_MAXSIZE 7
typedef struct _tagMaxHeap
{
int* iDataArr;
int iSize;
} Max_Heap;
void Swap_int(int* _a, int* _b);
void InitHeap(Max_Heap* _heap);
void Insert(Max_Heap* _heap, int _iData);
void Insert_2(int* arr2, int key, int input);
int Pop(Max_Heap* _heap);
int Pop_2(Max_Heap* _heap);
int main()
{
Max_Heap heap;
InitHeap(&heap);
Insert(&heap, 8);
Insert(&heap, 3);
Insert(&heap, 15);
Insert(&heap, 6);
Insert(&heap, 10);
Insert(&heap, 2);
Insert(&heap, 18);
/*
Insert_2(heap.iDataArr, ++heap.iSize, 8 );
Insert_2(heap.iDataArr, ++heap.iSize, 3 );
Insert_2(heap.iDataArr, ++heap.iSize, 15 );
Insert_2(heap.iDataArr, ++heap.iSize, 6 );
Insert_2(heap.iDataArr, ++heap.iSize, 18 );
Insert_2(heap.iDataArr, ++heap.iSize, 2 );
Insert_2(heap.iDataArr, ++heap.iSize, 10 );
*/
for (int i = 0; i < DEFAULT_HEAP_MAXSIZE; i++)
{
printf("%d ", Pop_2(&heap));
}
return 0;
}
void InitHeap(Max_Heap* _heap)
{
_heap->iDataArr = (int*)calloc(DEFAULT_HEAP_MAXSIZE, sizeof(int));
_heap->iSize = 0;
}
/*
i번 노드의 왼쪽 자식 == heap[(2*i)]
i번 노드의 오른쪽 자식 == heap[(2*i) + 1]
i번 노드의 부모 == heap[(i / 2)]
*/
void Insert(Max_Heap* _heap, int _iData)
{
int* arr = _heap->iDataArr;
int* iSize = &_heap->iSize;
int currentIndex = ++(*iSize); //맨마지막 노드 인덱스 가져오기
/*
맨 마지막 노드에 데이터를 삽입, 해당 데이터가 부모 보다 크면 부모와 위치를 바꾸고 아니면 그대로 둠
*/
while ((currentIndex != 1) && (_iData > arr[currentIndex / 2])) //root가 아니다 && 삽입 데이터가 현재노드의 부모보다 크다
{
arr[currentIndex] = arr[currentIndex / 2]; // 자식 노드자리에 부모 노드 값을 대입, 부모노드가 작으므로 밑으로 내림
currentIndex /= 2; // 현재 인덱스에 부모 인덱스 대입
}
arr[currentIndex] = _iData; //최종 위치에 삽입
}
void Insert_2(int* arr2, int key, int input)
{
arr2[key] = input; // 마지막 노드에 입력값 넣기
while (key > 1) //root에 도달할때까지 반복
{
if (arr2[key] > arr2[key / 2]) // 부모 노드보다 더 크다면
{
Swap_int(&arr2[key], &arr2[key / 2]); // 부모 노드와 자리를 바꿈
key /= 2; // 부모노드 인덱스로 전환
}
else
{
break;
}
}
}
int Pop(Max_Heap* _heap)
{
if (_heap->iSize == 0)
{
return 0;
}
int ret = _heap->iDataArr[1]; // 꺼내올 데이터(root)
_heap->iDataArr[1] = _heap->iDataArr[_heap->iSize--]; // root 자리에 맨마지막 위치 데이터 대입
int parent = 1; //root 부터 시작
int child = 0;
while (1)
{
child = parent * 2; //왼쪽 자식, i번 노드의 왼쪽 자식 == heap[(2*i)]
if (child + 1 <= _heap->iSize && _heap->iDataArr[child] <= _heap->iDataArr[child + 1]) // 오른쪽 자식 인덱스가 존재한다 && 오른쪽 자식이 왼쪽 자식보다 크거나 같다
{
child++; //오른쪽 자식으로 전환
}
if (child > _heap->iSize || _heap->iDataArr[child] < _heap->iDataArr[parent]) //자식인덱스가 힙 사이즈보다 크다(범위를 넘어버림) || 부모가 제일 큰 자식 보다 크다
{
break; //중단
}
Swap_int(&_heap->iDataArr[parent], &_heap->iDataArr[child]); //부모와 제일 큰 자식 자리를 바꿈
parent = child; // 자식의 자식들을 비교하기위해, 자식 노드를 부모 노드로 봄
}
return ret; // 꺼내올 데이터 리턴
}
int Pop_2(Max_Heap* _heap)
{
int result = _heap->iDataArr[1]; //꺼내올 데이터
int* arr = _heap->iDataArr;
int* iSize = &_heap->iSize;
Swap_int(&arr[1], &arr[*iSize]); //마지막 노드 데이터와 루트 데이터를 바꿈
(*iSize)--; //전체 노드 갯수 감소
int parentIdx = 1;
int childIdx = parentIdx * 2;
if (childIdx + 1 <= *iSize) //오른쪽 자식 노드 인덱스가 노드 전체 갯수를 넘지 않는다, 즉 오른쪽 자식이 존재한다
{
childIdx = (arr[childIdx] > arr[childIdx + 1]) ? childIdx : childIdx + 1; //왼쪽 자식, 오른쪽 자식, 둘 중에 큰 노드 선별
}
while (childIdx <= *iSize && arr[parentIdx] < arr[childIdx]) //자식 인덱스가 범위를 넘지 않는다 && 자식노드가 부모노드 보다 크다
{
Swap_int(&arr[parentIdx], &arr[childIdx]); // 자식 노드와 부모노드 위치 바꾸기
parentIdx = childIdx; //자식 노드를 부모 노드로 봄
childIdx *= 2; //왼쪽 자식 노드
if (childIdx + 1 <= *iSize) //오른쪽 자식 노드 인덱스가 노드 전체 갯수를 넘지 않는다, 즉 오른쪽 자식이 존재한다
{
childIdx = (arr[childIdx] > arr[childIdx + 1]) ? childIdx : childIdx + 1; //왼쪽 자식, 오른쪽 자식, 둘 중에 큰 노드 선별
}
}
return result;
}
void Swap_int(int* _a, int* _b)
{
*_a ^= *_b ^= *_a ^= *_b;
}