堆排序及leetcode347---前 K 个高频元素

堆排序及leetcode347—前 K 个高频元素

大顶堆:堆首元素是最大元素
小顶堆:堆首元素是最小元素

堆排序的基本思路:

  1. 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

  2. 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

  3. 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

堆排序算法说明:图解排序算法(三)之堆排序

图解方式非常清晰直观。

注意堆排序初始操作1是从下开始的非叶子节点进行操作,如果与上层父节点的互换导致下层子结构也出现问题,就需要返回修改子结构(这个修改操作与操作2,3相似,可以使用一个函数来封装这个向下检测修改的操作)。

注意堆排序的交换操作2和3是将非叶子节点与它的左右节点中较大的一方(大顶堆)进行交换操作,交换操作之后对交换后的左节点或右节点判断是否符合堆排序的结构

堆排序及leetcode347---前 K 个高频元素_第1张图片
比如这里4和9互换后,对交换后的左节点也就是4判断它的子树是否符合结构,发现不符合就需要从5,6中找较大的一个与它互换
堆排序及leetcode347---前 K 个高频元素_第2张图片
互换后判断4是叶子节点,操作到此就结束了。

结构搭建好一次次取出根节点元素的时候,末尾元素跑到第一个,需要重新判断结构。要注意如果新的根节点a与左节点进行了互换(依然是取最大或者最小的一个,根据大顶堆还是小顶堆来处理),就有可能打破了原先左子树的结构,而右子树没有进行任何操作,则右子树是不需要进行操作的。


leetcode347—前 K 个高频元素
这道题中构建了一个小顶堆来存储:

// 小顶堆,节点值小于左右子树节点值
function smallHeap(compareFn){
    this.queue = [];
    this.compareFn = compareFn;
}

smallHeap.prototype.push = function(item){
    this.queue.push(item)
    // 从最后一个非叶子节点开始比较 排序
    let index = this.queue.length - 1;
    let parrent = Math.floor((index-1)/2);
    // 新添加元素值小于父亲节点就需要交换
    while (parrent >= 0 && this.compare(parrent, index) > 0) {
        [this.queue[parrent], this.queue[index]] = [this.queue[index], this.queue[parrent]];
        index = parrent;
        parrent = Math.floor((index-1)/2);
    }
}


// 弹出第一个元素后需要进行进行堆排序
smallHeap.prototype.pop = function(){
    // 保存弹出的元素,排好序后返回
    let res = this.queue[0];
    // 按照堆排序流程 最后一个节点替代根节点
    this.queue[0] = this.queue.pop();

    // 从上到下 排序比较
    let index = 0;
    let left = 1;

    // left表示左子树,left+1表示右子树;从左右子节点中选择小的一方来于根节点交换
    // 不交换的那一侧本身是符合结构的不需要操作, 只需要对进行交换操作的一方从上向下进行结构的调整
    let selectchild = this.compare(left, left+1) > 0 ? left + 1: left  

    while(selectchild!== undefined && this.compare(index, selectchild)>0){
        [this.queue[index], this.queue[selectchild]] = [this.queue[selectchild], this.queue[index]];
        index = selectchild;
        selectchild = this.compare(index*2+1, index*2+2)>0 ? index*2+2 : index*2+1;
    }
    return res;
}


smallHeap.prototype.compare = function(index1, index2) {
    // 用于处理索引超出范围的情况
    if (this.queue[index1]===undefined) return 1;
    if (this.queue[index2]===undefined) return -1;

    return this.compareFn(this.queue[index1], this.queue[index2]);
}

push部分

push部分的操作就类似于初始对堆的排序,从最下方非叶子节点开始搭建出完整结构。但有点不同的是,每次新添加节点前,原先是已经保持了堆排序的正确结构,所以从下往上的更新不需要回头检查。(因为与新节点交换的节点值已经是当前子树中的最大或最小值了,即使换到下一层,也不会导致下一层的结构出现问题。)

例: a是这个子树中原本的最小值,现在新加的b更小,所以与a交换。堆排序及leetcode347---前 K 个高频元素_第3张图片

交换后因为a原本比b以外的子树中所有节点值都小,所以进行交换操后,a还是比a所在的新的子树中的节点值都小,所以不需要进行下层结构的检查更新。
堆排序及leetcode347---前 K 个高频元素_第4张图片

pop部分

pop部分的操作就类似于搭建好结构后的堆首元素和堆尾元素的互换操作(即取出进行顺序排列),更新了堆首元素后要保证结构的正确性,需要从根节点开始向下,选取左右节点的一方进行互换然后更新互换后的左子树或右子树的结构。

你可能感兴趣的:(排序,代码随想录刷题,算法,javascript)