10_Heap

Aug 23rd 2020 by jyoon

git

전체 구현 코드와 테스트 코드

설명

  • 트리와 비슷한 자료구조의 일종
  • 최대힙, 최소힙이 있다.

    • 최대힙: 부모가 자식보다 크다.
    • 최소힙: 부모가 자식보다 작다.
  • 힙은 자식에 대한 포인터를 갖는 대신에 배열을 사용해 자료를 저장

    • 배열에서 힙 노드의 자식위치(인덱스)를 쉽게 계산할 수 있다. -> 힙을 사용하면 부모와 자식 간의 관계를 쉽게 정의할 수 있다.

구조

heap heap1

time complexity

구현 List

  1. Heap

    • 아래 기본 기능 구현

      swap
      parentIndex, leftChildIndex, rightChildIndex
      parent, leftChild, rightChild
      peek, size
  2. MinHeap

    • Root가 자식보다 작다.
    • (Heap 상속)
  3. MaxHeap

    • Root가 자식보다 크다.
    • (Heap 상속)
  4. 힙정렬

    • 오름차순: MinHeap으로 add한 data를 poll 했을 떄
    • 내림차순: MaxHeap으로 add한 data를 poll 했을 떄
  5. 연습문제

      * 연습문제 1.일련의 숫자에서 중간 값찾기
        * MedianHeap
      * 연습문제 2.배열에서 K번째로 가장 작은 값 찾기 
      * 연습문제 3.배열에서 K번째로 가장 큰 값 찾기

Heap

  // # 1. Heap
  function Heap() {
    this.items = [];
  }

  Heap.prototype.swap = function(idx1, idx2) {
    [ this.items[idx1], this.items[idx2] ] = [ this.items[idx2], this.items[idx1] ];
    // let temp = this.items[idx1];
    // this.items[idx1] = this.items[idx2];
    // this.items[idx2] = temp;
  };

  Heap.prototype.parentIndex = function(idx) {
    return Math.floor((idx - 1) / 2); // ###
  };

  Heap.prototype.leftChildIndex = function(idx) {
    return idx * 2 + 1;
  };

  Heap.prototype.rightChildIndex = function(idx) {
    return idx * 2 + 2;
  };

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

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

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

  Heap.prototype.peek = function(item) {
    return this.items[0];
  };

  Heap.prototype.size = function() {
    return this.items.length;
  };

MinHeap

  // # 2. MinHeap
  function MinHeap() {
    this.items = [];
  }

  MinHeap.prototype = Object.create(Heap.prototype);
  MinHeap.prototype.add = function(item) {
    // this.items[this.items.length] = item;
    this.items.push(item);
    this.bubbleUp();
    return this.items.length;
  };

  MinHeap.prototype.poll = function() {
    let item = this.items[0];
    this.items[0] = this.items.pop();
    this.bubbleDown();
    return item;
  };

  // add에서 array에 마지막에 요소를 추가한 후 트리 상단으로 올라가면서 재정렬
  //  : root가 자식것 보다 더 크면 bubbleUp 진행
  MinHeap.prototype.bubbleUp = function() {
    let idx = this.items.length - 1;
    while (this.parent(idx) && this.parent(idx) > this.items[idx]) {
      this.swap(this.parentIndex(idx), idx);
      idx = this.parentIndex(idx);
    }
  };

  // poll에서 array 첫번째 요소를 제거 후 아래로 내려가면서 정렬
  //  : root가 자식것 보다 더 크면 bubbleDown 진행
  //  : root node의 왼, 오른쪽 노드중 더 작은 수를 root와 swap
  MinHeap.prototype.bubbleDown = function() {
    let idx = 0;
    while (this.leftChild(idx) && (this.leftChild(idx) < this.items[idx] || this.rightChild(idx) < this.items[idx])) {
      var smallerIndex = this.leftChildIndex(idx);
      if (this.rightChild(idx) && this.rightChild(idx) < this.items[smallerIndex]) {
        smallerIndex = this.rightChildIndex(idx);
      }
      this.swap(smallerIndex, idx);
      idx = smallerIndex;
    }
  };

MaxHeap

  // # 3. MaxHeap
  function MaxHeap() {
    this.items = [];
  }

  MaxHeap.prototype = Object.create(Heap.prototype);
  MaxHeap.prototype.add = function(item) {
    this.items.push(item);
    this.bubbleUp();
    return this.items.length;
  };
  MaxHeap.prototype.poll = function() {
    const item = this.items[0];
    this.items[0] = this.items.pop();
    this.bubbleDown();
    return item;
  };

  MaxHeap.prototype.bubbleUp = function() {
    let 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);
    }
  };

  MaxHeap.prototype.bubbleDown = function() {
    let index = 0;
    while (
      this.leftChild(index) &&
      (this.leftChild(index) > this.items[index] || this.rightChild(index) > this.items[index])
    ) {
      let biggerIndex = this.leftChildIndex(index);
      if (this.rightChild(index) && this.rightChild(index) > this.items[biggerIndex]) {
        biggerIndex = this.rightChildIndex(index);
      }
      this.swap(biggerIndex, index);
      index = biggerIndex;
    }
  };

힙정렬

  /**
  * # MinHeap, MaxHeap의 add로 추가 할때마다 정렬이 되고 
  *   정렬된 배열(Heap class add function으로 추가된 배열)을 
  *   pop()으로 호출하면서 꺼낸 객체를 저장하기만 하면 된다.
  * 
  * # 시간 복잡도 
  *  * 힙정렬의 시간 복잡도는 O(nlog2(n))이다.(빠른정렬, 병합정렬 과 같다.)
  *    : 삼투가 O(log2(n))의 시간이 걸리고 정렬이 n개의 항목을 꺼내야 하기 때문에 
  */

  //오름차순 정렬
  console.log('오름차순 정렬');
  var minHeapExample = new MinHeap();
  minHeapExample.add(12);
  minHeapExample.add(2);
  minHeapExample.add(23);
  minHeapExample.add(4);
  minHeapExample.add(13);
  minHeapExample.items; // [2, 4, 23, 12, 13]

  console.log(minHeapExample.poll()); // 2
  console.log(minHeapExample.poll()); // 4
  console.log(minHeapExample.poll()); // 12
  console.log(minHeapExample.poll()); // 13
  console.log(minHeapExample.poll()); // 23

  //내림차순 정렬
  console.log('내림차순 정렬');
  var maxHeapExample = new MaxHeap();
  maxHeapExample.add(12);
  maxHeapExample.add(2);
  maxHeapExample.add(23);
  maxHeapExample.add(4);
  maxHeapExample.add(13);
  maxHeapExample.items; // [23, 13, 12, 2, 4]

  console.log(maxHeapExample.poll()); // 23
  console.log(maxHeapExample.poll()); // 13
  console.log(maxHeapExample.poll()); // 12
  console.log(maxHeapExample.poll()); // 2
  console.log(maxHeapExample.poll()); // 4

연습문제

연습문제 1.일련의 숫자에서 중간 값찾기

  // 연습문제 1.일련의 숫자에서 중간 값찾기
  function MedianHeap() {
    this.minHeap = new MinHeap();
    this.maxHeap = new MaxHeap();
  }

  MedianHeap.prototype.push = function(value) {
    if (value > this.median()) {
      this.minHeap.add(value);
    } else {
      this.maxHeap.add(value);
    }

    // Re balancing
    if (this.minHeap.size() - this.maxHeap.size() > 1) {
      this.maxHeap.add(this.minHeap.poll());
    }

    if (this.maxHeap.size() - this.minHeap.size() > 1) {
      this.minHeap.add(this.maxHeap.poll());
    }
  };
  MedianHeap.prototype.median = function() {
    if (this.minHeap.size() == 0 && this.maxHeap.size() == 0) {
      return Number.NEGATIVE_INFINITY;
    } else if (this.minHeap.size() == this.maxHeap.size()) {
      return (this.minHeap.peek() + this.maxHeap.peek()) / 2;
    } else if (this.minHeap.size() > this.maxHeap.size()) {
      return this.minHeap.peek();
    } else {
      return this.maxHeap.peek();
    }
  };
  debugger;
  console.log('연습문제 1.일련의 숫자에서 중간 값찾기');
  var medianH = new MedianHeap();

  medianH.push(12);
  console.log(medianH.median()); // 12
  medianH.push(2);
  console.log(medianH.median()); // 7 ( because 12 + 2 = 14; 14/2 = 7)
  medianH.push(23);
  console.log(medianH.median()); // 12
  medianH.push(13);
  console.log(medianH.median()); // 12.5  

연습문제 2.배열에서 K번째로 가장 작은 값 찾기

  const array1 = [ 12, 3, 13, 4, 2, 40, 23 ];
  function getKthSmallestElement(array, k) {
    var minH = new MinHeap();
    array.forEach((el) => {
      minH.add(el);
    });
    //### 중요: 1부터 시작
    for (var i = 1; i < k; i++) {
      minH.poll();
    }
    return minH.poll();
  }
  debugger;
  console.log('연습문제 2.배열에서 K번째로 가장 작은 값 찾기');
  //minH.items => "3,4,13,12,23,40"
  console.log(getKthSmallestElement(array1, 2)); // 3
  console.log(getKthSmallestElement(array1, 1)); // 2
  console.log(getKthSmallestElement(array1, 7)); // 40

연습문제 3.배열에서 K번째로 가장 큰 값 찾기

  // 연습문제 3.배열에서 K번째로 가장 큰 값 찾기
  const array2 = [ 12, 3, 13, 4, 2, 40, 23 ];
  function getKthBiggestElement(array, k) {
    let maxH = new MaxHeap();
    array.forEach((el) => {
      maxH.add(el);
    });

    for (let i = 1; i < k; i++) {
      maxH.poll();
    }
    return maxH.poll();
  }
  debugger;
  console.log('연습문제 3.배열에서 K번째로 가장 큰 값 찾기');
  //minH.items => "40,4,23,3,2,12,13"
  //bubble down을 하기 때문에 위 items에서 순서를 카운트해서 답을 찾으려면 맞지 않는다.
  //3.MaxHeap의 poll 과정 적어 놓은것 확인해보자
  console.log(getKthBiggestElement(array2, 2)); // 23
  console.log(getKthBiggestElement(array2, 1)); // 40
  console.log(getKthBiggestElement(array2, 7)); // 2