[DataStructure&Algorithm] Heap


ヒープ

  • 完全バイナリツリーの一種で、優先キューのために作成されたデータ構造.
  • このデータ構造は、
  • の複数の値の中で最大値または最大値をすばやく見つけることができます.
  • 臀部は半整列状態(緩やかな整列)を維持する.
  • より大きい値はより高いレベルにあり、より小さい値はより低いレベルにあります.
    簡単に言えば、親ノードのキー値は常に子ノードのキー値(小さい)より大きいツリーです.
  • HIPツリーでは、値の重複が許可されます.(バイナリ・ナビゲーション・ツリーでは値の重複は許可されていません.)
  • hipの条件:完全バイナリツリーは、以下に述べる最大hip、最小hipの条件を満たさなければならない.

    ヒップ類

  • 最大お尻(最大heap)
  • 親ノードのキー値が子ノードのキー値以上の完全バイナリツリー
  • 鍵(親ノード)=>鍵(子ノード)
  • 最小スタック(minheap)
  • 親ノードのキー値が子ノードのキー値以上の完全バイナリツリー
  • 鍵(親ノード)<=鍵(子ノード)

  • ヒープの実施

  • hipを格納する標準データ構造は配列である.
  • の実装を簡略化するために、最初のインデックス0は使用されない.
  • 特定の場所のノード番号は、新しいノードを追加するときも変わらない.
  • ex)ルートノードの右側ノード番号は常に3です.
  • ヒップホップにおける親ノードと子ノードの関係
  • 左子索引=(親索引)*2
  • 右子索引=(親索引)*2+1
  • 親インデックス=(子インデックス)/2
  • 臀部を削除

  • 最大hipでは、最大の代価はルートノードであるため、ルートノードが削除されます.
  • 最大HIPでは、削除演算は削除コストが最も大きい要素である.
  • 削除されたルートノードはhipの最後のノードを取得します.
  • ヒップを再構築します.

  • 最大臀部コード数

    class MaxHeapTree {
      // 편리한 구현을 위해 index는 1부터 사용
      constructor() {
        this._items = [""];
      }
    
      get items() {
        return this._items;
      }
    
      get root() {
        return _items[1];
      }
    
      /**
       * 구현 절차
       * - 1) 힙의 가장 마지막 위치(배열의 마지막)에 노드에 추가한다
       * - 2) 힙의 조건을 만족시키도록 연산 (부모노드와 교환)
       *  - 새로 추가한 노드와 부모 노드를 비교하여 부모노드가 더 작으면 위치를 바꾼다
       *  - (더 큰 부모노드가 없을때까지 반복)
       */
      insert(key) {
        this._items.push(key);
        this._allignAfterInsert();
      }
    
      /**
       * 힙의 삭제는 루트 노드 삭제만 구현한다. (queue의 pop 연산이라고 생각하면 됨)
       *
       * 구현 절차
       * - 1) 루트노드를 제거
       * - 2) 제거한 루트노드자리에 마지막 노드를 삽입
       * - 3) 힙의 조건을 만족시키도록 연산
       *  - 루트노드의 자식노드 중 더 큰 값과 교환한다.
       *  - (더 큰 자식노드가 없을때까지 반복)
       */
    
      remove() {
        this._items[1] = this._items.pop();
        this._allignAfterRemove();
      }
    
      _allignAfterInsert() {
        let childIndex = this._items.length - 1;
        let child = this._items[childIndex];
    
        let parentIndex = Math.floor(childIndex / 2) || 1;
        let parent = this._items[parentIndex];
    
        let newChildIndex;
        while (parent < child) {
          newChildIndex = parentIndex;
          this._items[newChildIndex] = child;
          this._items[childIndex] = parent;
    
          parentIndex = Math.floor(newChildIndex / 2) || 1;
          parent = this._items[parentIndex];
          childIndex = newChildIndex;
        }
      }
    
      _allignAfterRemove() {
        let parentIndex = 1;
        let parent = this._items[parentIndex];
    
        let leftChild = this._items[parentIndex * 2];
        let rightChild = this._items[parentIndex * 2 + 1];
    
        let bigChild = leftChild > rightChild ? leftChild : rightChild;
        let bigChildIndex = this._items.indexOf(bigChild);
    
        let newParentIndex;
        while (parent < bigChild) {
          newParentIndex = bigChildIndex;
          this._items[newParentIndex] = parent;
          this._items[parentIndex] = bigChild;
    
          leftChild = this._items[newParentIndex * 2];
          rightChild = this._items[newParentIndex * 2 + 1];
    
          bigChild = leftChild > rightChild ? leftChild : rightChild;
          bigChildIndex = this._items.indexOf(bigChild);
          parentIndex = newParentIndex;
        }
      }
    }
    
    // Test
    it("MaxHeapTree 클래스는 잘 동작한다.", () => {
      const heap = new MaxHeapTree();
    
      heap.insert(9);
      heap.insert(3);
      heap.insert(5);
      heap.insert(10);
      heap.insert(6);
      expect(heap.items).toEqual(["", 10, 9, 5, 3, 6]);
    
      heap.insert(8);
      expect(heap.items).toEqual(["", 10, 9, 8, 3, 6, 5]);
    
      heap.remove();
      expect(heap.items).toEqual(["", 9, 6, 8, 3, 5]);
      heap.remove();
      expect(heap.items).toEqual(["", 8, 6, 5, 3]);
    });

    最小ヒップ

    class MinHeapTree {
      // 편리한 구현을 위해 index는 1부터 사용
      constructor() {
        this._items = [""];
      }
    
      get items() {
        return this._items;
      }
    
      get root() {
        return _items[1];
      }
    
      /**
       * 구현 절차
       * - 1) 힙의 가장 마지막 위치(배열의 마지막)에 노드에 추가한다
       * - 2) 힙의 조건을 만족시키도록 연산 (부모노드와 교환)
       *  - 새로 추가한 노드와 부모 노드를 비교하여 부모노드가 더 크면 위치를 바꾼다
       *  - (더 큰 부모노드가 없을때까지 반복)
       */
      insert(key) {
        this._items.push(key);
        this._allignAfterInsert();
      }
    
      /**
       * 힙의 삭제는 루트 노드 삭제만 구현한다. (queue의 pop 연산이라고 생각하면 됨)
       *
       * 구현 절차
       * - 1) 루트노드를 제거
       * - 2) 제거한 루트노드자리에 마지막 노드를 삽입
       * - 3) 힙의 조건을 만족시키도록 연산
       *  - 루트노드의 자식노드 중 더 작은 값과 교환한다.
       *  - (더 작은 자식노드가 없을때까지 반복)
       */
      remove() {
        this._items[1] = this._items.pop();
        this._allignAfterRemove();
      }
    
      _allignAfterInsert() {
        let childIndex = this._items.length - 1;
        let child = this._items[childIndex];
    
        let parentIndex = Math.floor(childIndex / 2) || 1;
        let parent = this._items[parentIndex];
    
        let newChildIndex;
        while (parent > child) {
          newChildIndex = parentIndex;
          this._items[newChildIndex] = child;
          this._items[childIndex] = parent;
    
          parentIndex = Math.floor(newChildIndex / 2) || 1;
          parent = this._items[parentIndex];
          childIndex = newChildIndex;
        }
      }
    
      _allignAfterRemove() {
        let parentIndex = 1;
        let parent = this._items[parentIndex];
    
        let leftChild = this._items[parentIndex * 2];
        let rightChild = this._items[parentIndex * 2 + 1];
    
        let smallChild = leftChild < rightChild ? leftChild : rightChild;
        let smallChildIndex = this._items.indexOf(smallChild);
    
        let newParentIndex;
        while (parent > smallChild) {
          newParentIndex = smallChildIndex;
          this._items[newParentIndex] = parent;
          this._items[parentIndex] = smallChild;
    
          leftChild = this._items[newParentIndex * 2];
          rightChild = this._items[newParentIndex * 2 + 1];
    
          smallChild = leftChild < rightChild ? leftChild : rightChild;
          smallChildIndex = this._items.indexOf(smallChild);
          parentIndex = newParentIndex;
        }
      }
    }
    
    // Test
    it("MinHeapTree 클래스는 잘 동작한다.", () => {
      const heap = new MinHeapTree();
    
      heap.insert(9);
      heap.insert(3);
      heap.insert(5);
      heap.insert(10);
      heap.insert(6);
      expect(heap.items).toEqual(["", 3, 6, 5, 10, 9]);
    
      heap.insert(2);
      expect(heap.items).toEqual(["", 2, 6, 3, 10, 9, 5]);
    
      heap.remove();
      expect(heap.items).toEqual(["", 3, 6, 5, 10, 9]);
      heap.remove();
      expect(heap.items).toEqual(["", 5, 6, 9, 10]);
    });

    リファレンスコード

    class Heap {
      constructor(size) {
        if (size && isNaN(size)) throw Error(`Invalidate param`);
    
        this.idx = 0;
        this.arr = new Array(size ? size : 11).fill(null);
      }
    
      add(n) {
        if (this.idx + 1 === this.arr.length) throw Error(`Stack overflow`);
    
        let idx = ++this.idx;
        this.arr[idx] = n;
    
        while (idx > 1) {
          const nextIdx = idx >> 1;
          const parent = this.arr[nextIdx];
          const cur = this.arr[idx];
    
          if (parent <= cur) break;
    
          this.arr[nextIdx] = cur;
          this.arr[idx] = parent;
    
          idx >>= 1;
        }
    
        return true;
      }
    
      print() {
        console.log(this.arr);
      }
    
      pop() {
        if (this.idx === 0) throw Error(`Empty stack`);
    
        const ret = this.arr[1];
        let idx = 1;
    
        this.arr[1] = this.arr[this.idx];
        this.arr[this.idx--] = null;
    
        while (idx < this.idx) {
          if (idx * 2 > this.idx || idx * 2 + 1 > this.idx) break;
    
          let nextIdx = idx * 2;
          if (this.arr[idx] <= this.arr[nextIdx]) nextIdx = idx;
          if (this.arr[nextIdx] > this.arr[idx * 2 + 1]) nextIdx = idx * 2 + 1;
          if (nextIdx === idx) break;
    
          const tmp = this.arr[idx];
          this.arr[idx] = this.arr[nextIdx];
          this.arr[nextIdx] = tmp;
          idx = nextIdx;
        }
    
        return ret;
      }
    
      peek() {
        return this.arr[this.idx];
      }
    }
    
    function main() {
      const heap = new Heap();
      for (let i = 10; i > 0; i--) {
        heap.add(i);
      }
      heap.print();
      while (heap.peek()) {
        console.log(heap.pop(), heap.idx);
        heap.print();
      }
    }
    
    main();
  • 参照コード
  • 
    const Heap = function Heap() {
      this.heap = Array(30).fill(' ');
      this.heapSize =0;
    }
    
    Heap.prototype.insertHeap = function(data) {
      const heap = this.heap;
      const newData = data;
      if(this.heapSize === 0) {
        heap[1] = newData;
        this.heapSize++;
      }else {
        this.heapSize++
        let idx = this.heapSzie;
    
        heap[idx] = newData;
    
        // 데이터를 넣었으면 비교 연산
    
        let parentIdx = parseInt(idx/2);
    
        while(parentIdx > 0) {
          if(heap[parentIdx]< heap[idx]) {
            let temp = heap[parentIdx];
            heap[parentIdx] = heap[idx];
            heap[idx] = temp;
          }else{
            break;
            parentIdx = parseInt(parseInt / 2);
          }
        }
      }
    }
    
    Heap.prototype.printAll = function () {
      let result = "";
      
      for(let i=0; i< this.heapSize; i++) {
        result += `${this.heap[i]}`;
        console.log("출력", result);
      }
    }
    
    Heap.prototype.deleteHeap = function() {
      const lastIdx = this.heapSize;
      const heap = this.heap;
      const deleteVal = heap[i];
      let idx = 1;
      
      console.log(heap);
    
      heap[1] = heap[lastIdx];
      heap[lastIdx] = "";
    
      while(heap[idx * 2] !== "" && heap[idx*2+1] !=="") {
        let temp = 0; 
        if(heap[idx] < heap[idx*2]){
          temp = heap[idx];
          heap[idx] = heap[idx*2];
          heap[idx*2] = temp;
          idx *= 2;
        }
        else if(heap[idx] < heap[idx*2+1]) {
          temp = heap[idx];
          heap[idx] = heap[idx*2+1];
          heap[idx*2+1] = temp;
          idx = idx*2+1;
        }else{
          break;
        }
      }
    
      console.log(`${delteVal} 삭제완료` );
    }
    
    
    function main() {
      const heap = new Heap();
      heap.insertHeap(23);
      heap.insertHeap(19);
      heap.insertHeap(10);
      heap.insertHeap(15);
      heap.insertHeap(9);
      heap.insertHeap(13);
    
      heap.deleteHeap();
      heap.printAll();
    }
    
    main();
    
    

    reference

  • https://medium.com/basecs/learning-to-love-heaps-cef2b273a238
  • https://1ilsang.dev/2020-04-02/js/heap
  • https://taesung1993.tistory.com/23