(十)数据结构之“堆”

71 阅读2分钟

@TOC

堆是什么?

在这里插入图片描述 堆是一种特殊的完全二叉树 所有的节点都大于等于(最大堆)或小于等于(最小堆)它的子节点

JS中的堆

在这里插入图片描述

JS中通常用数组表示堆 左侧子节点的位置是2 * index + 1 右侧子节点的位置是2 * index + 2 父节点位置是( index - 1 ) / 2

堆的应用

堆能高效、快速地找出最大值和最小值,时间复杂度:O(1) 找出第K个最大(最小)元素

第K个最大元素

构建一个最小堆,并将元素依次插入堆中 当堆的容量超过K,就删除堆顶 插入结束后,堆顶就是第K个最大元素

实现步骤 在类里,生命一个数组,用来装元素 主要方法:插入、删除堆顶、获取堆顶、获取堆大小 插入 将值插入堆的底部,即数组的尾部 然后上移:将这个值和它的父节点进行交换,直到父节点小于等于这个插入的值 大小为k的堆中插入元素的时间复杂度为O(logk) 删除堆顶 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构) 然后下移:将新堆顶和它的子节点进行交换,直到子节点大于等于这个新堆顶 大小为k的堆中删除堆顶的时间复杂度为O(logk) 获取堆顶和堆的大小 获取堆顶:返回数组的头部 获取堆的大小:返回数组的长度

class MinHeap {
    constructor() {
        this.heap = [];
    }
    swap(i1, i2) {
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    getParentIndex(i) {
    	//return Math.floor((i - 1) / 2);
        return (i - 1) >> 1;
    }
    getLeftIndex(i) {
        return i * 2 + 1;
    }
    getRightIndex(i) {
        return i * 2 + 2;
    }
    shiftUp(index) {
        if (index == 0) { return; }
        const parentIndex = this.getParentIndex(index);
        if (this.heap[parentIndex] > this.heap[index]) {
            this.swap(parentIndex, index);
            this.shiftUp(parentIndex);
        }
    }
    shiftDown(index) {
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if (this.heap[leftIndex] < this.heap[index]) {
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        if (this.heap[rightIndex] < this.heap[index]) {
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
    //插入
    insert(value) {
        this.heap.push(value);
        this.shiftUp(this.heap.length - 1);
    }
    //删除堆顶
    pop() {
        this.heap[0] = this.heap.pop();
        this.shiftDown(0);
    }
    //获取堆顶
    peek() {
        return this.heap[0];
    }
    //获取堆的大小
    size() {
        return this.heap.length;
    }
}

const h = new MinHeap();
h.insert(3);
h.insert(2);
h.insert(1);
h.pop();

LeetCode:215.数组中的第K个最大元素

在这里插入图片描述

解题思路 看到“第K个最大元素” 考虑选择使用最小堆 解题步骤 构建一个最小堆,并依次把数组的值插入堆中 当堆的容量超过K,就删除堆顶 插入结束后,堆顶就是第K个最大元素 在这里插入图片描述 时间复杂度O(n * logk),空间复杂度O(k)

LeetCode:347.前K个高频元素

在这里插入图片描述 法一: 在这里插入图片描述 时间复杂度O(n * logn)

法二 在这里插入图片描述

class MinHeap {
    constructor() {
        this.heap = [];
    }
    swap(i1, i2) {
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    getParentIndex(i) {
    	//return Math.floor((i - 1) / 2);
        return (i - 1) >> 1;
    }
    getLeftIndex(i) {
        return i * 2 + 1;
    }
    getRightIndex(i) {
        return i * 2 + 2;
    }
    shiftUp(index) {
        if (index == 0) { return; }
        const parentIndex = this.getParentIndex(index);
        if (this.heap[parentIndex] && this.heap[parentIndex].value > this.heap[index].value) {
            this.swap(parentIndex, index);
            this.shiftUp(parentIndex);
        }
    }
    shiftDown(index) {
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if (this.heap[leftIndex] && this.heap[leftIndex].value < this.heap[index].value) {
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        if (this.heap[rightIndex] && this.heap[rightIndex].value < this.heap[index].value) {
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
    //插入
    insert(value) {
        this.heap.push(value);
        this.shiftUp(this.heap.length - 1);
    }
    //删除堆顶
    pop() {
        this.heap[0] = this.heap.pop();
        this.shiftDown(0);
    }
    //获取堆顶
    peek() {
        return this.heap[0];
    }
    //获取堆的大小
    size() {
        return this.heap.length;
    }
}

在这里插入图片描述

时间O(n * logk),空间O(n)

LeetCode:23.合并K个排序链表

在这里插入图片描述 解题思路 新链表的下一个节点一定是k个链表头中的最小节点 考虑选择使用最小堆 解题步骤 构建一个最小堆,并依次把链表头插入堆中 弹出堆顶接到输出链表,并将堆顶所在链表的新链表头插入堆中 等堆元素全部弹出,合并工作就完成了

class MinHeap {
   constructor() {
       this.heap = [];
   }
   swap(i1, i2) {
       const temp = this.heap[i1];
       this.heap[i1] = this.heap[i2];
       this.heap[i2] = temp;
   }
   getParentIndex(i) {
   	//return Math.floor((i - 1) / 2);
       return (i - 1) >> 1;
   }
   getLeftIndex(i) {
       return i * 2 + 1;
   }
   getRightIndex(i) {
       return i * 2 + 2;
   }
   shiftUp(index) {
       if (index == 0) { return; }
       const parentIndex = this.getParentIndex(index);
       if (this.heap[parentIndex] && this.heap[parentIndex].val> this.heap[index].val) {
           this.swap(parentIndex, index);
           this.shiftUp(parentIndex);
       }
   }
   shiftDown(index) {
       const leftIndex = this.getLeftIndex(index);
       const rightIndex = this.getRightIndex(index);
       if (this.heap[leftIndex] && this.heap[leftIndex].val< this.heap[index].val) {
           this.swap(leftIndex, index);
           this.shiftDown(leftIndex);
       }
       if (this.heap[rightIndex] && this.heap[rightIndex].val< this.heap[index].val) {
           this.swap(rightIndex, index);
           this.shiftDown(rightIndex);
       }
   }
   //插入
   insert(value) {
       this.heap.push(value);
       this.shiftUp(this.heap.length - 1);
   }
   //删除堆顶
   pop() {
   	if(this.size() === 1) return this.heap.shift();
   	const top =  this.heap[0]
       this.heap[0] = this.heap.pop();
       this.shiftDown(0);
       return top;
   }
   //获取堆顶
   peek() {
       return this.heap[0];
   }
   //获取堆的大小
   size() {
       return this.heap.length;
   }
}

在这里插入图片描述 时间复杂度O(n * logk),n是所有链表的节点数之和,k是排序链表数,空间复杂度是O(k)

思考题

1、请用堆画出一场比赛的运动员排名情况,无需 Coding。 2、在 LeetCode 找到一道堆的题目,并通过测试