힙(Heap)

  • 가장 높은 항목이나 가장 낮은 항목을 O(1)시간에 반환할 수 있는 자료구조 입니다.
  • Tree 형식과 비슷하고 포인터가 아닌 배열을 기반으로 한 완전 이진 트리입니다.
  • Tree와 다르게 중복된 값도 추가를 할 수 있습니다.
  • 우선 순위 큐 개념을 이용하여 데이터들이 우선순위를 가지고 있고 빠져 나갈때 우선순위로 빠져나갑니다.

힙 종류

최대 힙(Max-Heap)

  • 최상위 노드가 가장 높은 값을 갖고 있습니다.
  • 부모 노드 값 >= 자식 노드의 값

최소 힙(Min-Heap)

  • 최상의 노드가 가장 낮은 값을 갖고 있습니다.
  • 부모 노드 값 <= 자식 노드 값

힙 배열 인덱스 구조

  • 노드 : 인덱스
  • 자신 : N
  • 부모 : (N-1)/2
  • 왼쪽 자식 : (N*2)+1
  • 오른쪽 자식 : (N*2)+2
(1) index - 자신
(2) index*2 + 1; - 왼쪽 자식
(3) index*2 + 2; - 오른쪽 자식

  (1)(2)(3)
!! 0,1,2
!! 1,3,4
!! 2,5,6
!! 3,7,8
!! 4,9,10
                 0
       1                   2
    3     4           5      6
 7   8  9  10   11 12 13 14      

힙 정렬(Heap Sort)

  • 완전 이진 트리를 형식으로 만들어진 힙 자료구조를 정렬하는 알고리즘입니다.
  • 불안정 정렬에 속합니다.
  • 오름차순, 내림차순 정렬이 있습니다.

사용 예시

  • 가장 크거나 작은 값을 구할 때
  • 최대 i 만큼 떨어진 요소들을 정렬할 때

장점

  • 최악의 경우에도 빠른 시간 복잡도를 보장합니다.
  • 가장 크거나 작은 값을 필요로 할 때 유용합니다.

단점

  • 데이터들의 상태에 따라 다른 정렬들에 비해서 조금 느린편입니다.
  • 데이터의 순서가 바뀌는 불안정한 알고리즘입니다.

code

// ----------------------- 힙 클래스 -----------------------

function heap() {
  this.items = [];
}

heap.prototype.swap = function(index1, index2) {
  var temp = this.items[index1];
  this.items[index1] = this.items[index2];
  this.items[index2] = temp;
}

heap.prototype.parentIndex = function(index) {
  return Math.floor((index-1)/2);
}

heap.prototype.leftChildIndex = function(index) {
  return index*2+1
}

heap.prototype.rightChildIndex = function(index) {
  return index*2+2
}

heap.prototype.parent = function(index) {
  return this.items[this.parentIndex(index)];
}


heap.prototype.leftChild = function(index) {
  return this.items[this.leftChildIndex(index)];
}

heap.prototype.rightChild = function(index) {
  return this.items[this.rightChildIndex(index)];
}


// ----------------------- 삼투 구현 -----------------------

function minHeap() {
  this.items = [];
}
minHeap.prototype = Object.create(heap.prototype);


minHeap.prototype.bubbleDown = function() {
  var index = 0;

  while(this.leftChild(index) && (this.leftChild(index) < this.items[index] ||
        this.rightChild(index) < this.items[index]) ) {
          var smallerIndex = this.leftChildIndex(index);
          if(this.rightChild(index) && this.rightChild(index) < this.items[smallerIndex]) {
            smallerIndex = this.rightChildIndex(index);
          }
          this.swap(smallerIndex, index);
          index = smallerIndex;
        }
}

minHeap.prototype.bubbleUp = function() {
  var index = this.items.length-1;
  while(this.parent(index) && this.parent(index) > this.items[index]) {
    this.swap(this.parentIndex(index), index);
    index = this.parentIndex(index);
  }
}


// ----------------------- 최소 힙 -----------------------


minHeap.prototype.add = function(item) {
  this.items[this.items.length] = item;
  this.bubbleUp()
}

minHeap.prototype.poll = function() {
  var temp = this.items[0];
  this.items[0] = this.items[this.items.length-1];
  this.items.pop();
  this.bubbleDown();
  return temp;
}


var minH = new minHeap();
minH.add(1);
minH.add(10);
minH.add(5);
minH.add(100);
minH.add(8);
console.log(minH)

console.log(minH.poll());
console.log(minH.poll());
console.log(minH.poll());
console.log(minH.poll());
console.log(minH.poll());
console.log(minH)

 

설명

// ----------------------- 힙 클래스 -----------------------

// 배열을 기반으로 한 완전 이진 트리이기 때문에 배열로 초기화함
function heap() {
  this.items = [];
}

// 값 비교 후 위치를 바꿔야할때 실행 되는 함수
heap.prototype.swap = function(index1, index2) {
  var temp = this.items[index1];
  this.items[index1] = this.items[index2];
  this.items[index2] = temp;
}

// 해당 index의 부모 인덱스 찾는 함수
heap.prototype.parentIndex = function(index) {
  return Math.floor((index-1)/2);
}

// 해당 index의 왼쪽 자식 인덱스 찾는 함수
heap.prototype.leftChildIndex = function(index) {
  return index*2+1
}

// 해당 index의 오른쪽 자식 인덱스 찾는 함수
heap.prototype.rightChildIndex = function(index) {
  return index*2+2
}




// 1. 해당 인덱스의 부모 인덱스를 찾는다
// 2. this.items[부모 인덱스]를 통해 값을 찾는다

// 해당 index의 부모 값을 찾는 함수
heap.prototype.parent = function(index) {
  return this.items[this.parentIndex(index)];
}

// 해당 index의 왼쪽 자식 값을 찾는 함수
heap.prototype.leftChild = function(index) {
  return this.items[this.leftChildIndex(index)];
}

// 해당 index의 오른쪽 자식 값을 찾는 함수
heap.prototype.rightChild = function(index) {
  return this.items[this.rightChildIndex(index)];
}





// ----------------------- 삼투 구현 -----------------------

function minHeap() {
  this.items = [];
}

// 프로토타입을 복사 해서 힙 클래스로부터 도움 함수를 상속받는다
minHeap.prototype = Object.create(heap.prototype);



// bubbleDown함수는 poll함수에서 트리의 가장 밑부분(배열의 마지막 요소)을 꼭대기(배열[0])에 저장했지만 최솟값인지 알수 없기 때문에
// 반복문을 통해 해당 값 위치를 찾아주는 로직이다.

// (1) - index에 0을 저장해 트리의 꼭대기부터 내려가도록 한다.
// while
// (2) - while문 조건에서 (왼쪽 자식의 존재 여부), (현재 값(부모) > 왼쪽 자식 && 현재 값(부모) > 오른쪽 자식)를 확인한다.
// (3) - (2)조건문을 통과한다면 smallerIndex에 왼쪽 자식 인덱스 값을 임시로 저장한다.
// (4) - 왼쪽 자식과 오른쪽 자식의 값을 비교하고 더 값이 작은 쪽에 index를 smallerIndex에 저장한다.
// (5) - (1)의 인덱스(index)와 (4)의 인덱스(smallerIndex에) 위치를 바꿔준다(위치가 바뀐다는 의미는 트리의 꼭대기에서 점점 내려온다는 의미)
// (6) - index의 값을 변경하면서 자신의 자리를 찾아간다.
minHeap.prototype.bubbleDown = function() {
  var index = 0; // (1)
  while(this.leftChild(index) && (this.leftChild(index) < this.items[index] || // (2)
        this.rightChild(index) < this.items[index]) ) {
          var smallerIndex = this.leftChildIndex(index); // (3)
          if(this.rightChild(index) && this.rightChild(index) < this.items[smallerIndex]) {
            smallerIndex = this.rightChildIndex(index); // (4)
          }
          this.swap(smallerIndex, index); // (5)
          index = smallerIndex; // (6)
        }
}




// bubbleUp함수는 트리모양에서 가장 밑부분에서 꼭대기까지 조건에 맞다면 올라오는 로직이다.

// (1) - index에 add함수에서 추가한 요소의 위치(this.items.length-1)값을 넣는다.
// (2) - while문 조건에서 부모의 존재 여부와 부모의 값 > 현재 추가한 값 일때 swap함수를 이용해 교체한다.
// (3) - swap과정에서 위치를 바꾸고 index값을 바꿈으로써 현재 추가한 값의 위치를 (2)조건문과 비교하여 찾아간다.
//     !!! 현재 추가한 값이 최솟값이라면 가장 밑부분에서 꼭대기까지 swap을 하게 된다.
minHeap.prototype.bubbleUp = function() {
  console.log('bubbleUp 함수 실행')
  var index = this.items.length-1; // (1)
  console.log(`방금 추가한 값 : ${this.items[index]}, 부모의 값 : ${this.parent(index)}`)
  console.log('')
  // 부모값이 현재 추가한 값보다 큰 경우 스왑하는 반복문
  while(this.parent(index) && this.parent(index) > this.items[index]) { // (2)
    this.swap(this.parentIndex(index), index);
    index = this.parentIndex(index); // (3)
  }
}







// ----------------------- 최소 힙 -----------------------


// 배열의 총 길이에 +1 위치에 해당 item 값을 추가하고 bubbleUp()함수 실행한다
// 즉 배열의 크기가 0부터 추가횟수만큼 증가한다는 의미!!
minHeap.prototype.add = function(item) {
  this.items[this.items.length] = item;
  this.bubbleUp()
}


// 최소 값을 꺼내지만 배열에서 제거 할때는 가장 마지막 노드를 제거한다.
// 즉 var a = [1,2,3,4,5]에서 1을 제거하지만 a.pop()을 통해 배열의 마지막 요소를 삭제함

// 이유
// (1) temp 임시 변수에 최솟값(arr[0])을 저장한다.
// (2) arr[0]번째에 arr[마지막 인덱스] 값을 저장한다.
// (3) arr.pop()을 통해 마지막 인덱스를 제거한다(하지만 0번째 index에 저장되어있으므로 상관없다)
// (4) bubbleDown()함수를 실행한다. => 그 이유는 arr[0]번째에 arr[마지막 인덱스] 값을 저장했지만 최솟값인지 모르기 때문에
// (5) 최솟값(temp)을 return하여 출력한다.
minHeap.prototype.poll = function() {
  var temp = this.items[0]; // (1)
  this.items[0] = this.items[this.items.length-1]; // (2)
  this.items.pop(); // (3)
  this.bubbleDown(); // (4)
  return temp; // (5)
}






var minH = new minHeap();
minH.add(10);
minH.add(3);
minH.add(8);
minH.add(5);
minH.add(100);
minH.add(1);
// bubbleUp 함수 실행
// 방금 추가한 값 : 10, 부모의 값 : undefined
//
// bubbleUp 함수 실행
// 방금 추가한 값 : 3, 부모의 값 : 10
//
// bubbleUp 함수 실행
// 방금 추가한 값 : 8, 부모의 값 : 3
//
// bubbleUp 함수 실행
// 방금 추가한 값 : 5, 부모의 값 : 10
//
// bubbleUp 함수 실행
// 방금 추가한 값 : 100, 부모의 값 : 5
//
// bubbleUp 함수 실행
// 방금 추가한 값 : 1, 부모의 값 : 8

console.log(minH) // heap { items: [ 1, 5, 3, 10, 100, 8 ] }

console.log(minH.poll()); // 1
console.log(minH.poll()); // 3
console.log(minH.poll()); // 5
console.log(minH.poll()); // 8
console.log(minH.poll()); // 10

console.log(minH) // heap { items: [ 100 ] }

 

'자료구조' 카테고리의 다른 글

그래프(Graph)  (0) 2020.07.14
큐(Queue) 스택(Stack)  (0) 2020.07.09
해시 테이블(Hash Table)  (0) 2020.07.08
병합 정렬(Merge Sort)  (0) 2020.07.07
빠른 정렬(Quick Sort)  (0) 2020.07.03

+ Recent posts