算法--topK问题

186 阅读2分钟

一般我们说 topK 问题,就可以用大顶堆或小顶堆来实现, 最大的 K 个:小顶堆 最小的 K 个:大顶堆

//构建大顶堆
function MaxHeap(array) {
    //开始位置是最后一个非叶子节点
    let start = Math.floor(array.length / 2 - 1);
    for (let i = start; i >= 0; i--) {
        console.log(array);
        createHeap(array, i);
    }
    //打印构造好的大顶堆
    console.log(array);
    function createHeap(array, index) {
        //左子节点
        let leftNode = 2 * index + 1;
        //右子节点
        let rightNode = 2 * index + 2;
        let max = index;
        //左右子节点与根节点比较,找出最大的数
        if (leftNode < array.length && array[leftNode] > array[max]) {
            max = leftNode;
        }
        if (rightNode < array.length && array[rightNode] > array[max]) {
            max = rightNode;
        }
        //交换位置
        if (max != index) {
            let temp = array[max];
            array[max] = array[index];
            array[index] = temp;
            //交换位置后,不确定该位置下是否还是大顶堆,需要重排
            createHeap(array, max);
        }
    }
}


//构造小顶堆
function MinHeap(array) {
    //同样是从最后一个非叶子节点开始处理
    let start = Math.floor(array.length / 2 - 1);
    //构造小顶堆
    for (let i = start; i >= 0; i--) {
        createHeap(array, i);
    }
    console.log(array);
    function createHeap(array, index) {
        //左叶子节点
        let leftNode = 2 * index + 1;
        //右叶子节点
        let rightNode = 2 * index + 2;
        let min = index;
        if (leftNode < array.length && array[leftNode] < array[min]) {
            min = leftNode;
        }
        if (rightNode < array.length && array[rightNode] < array[min]) {
            min = rightNode;
        }
        //交换位置
        if (min != index) {
            let temp = array[min];
            array[min] = array[index];
            array[index] = temp;
            //交换位置之后不确定该min位置是否还是符合小顶堆,需要重排
            createHeap(array, min);
        }
    }
}
/**
 * 交换交换根节点和数组末尾元素的值
 */
function adjustHeap(array, arraylen) {
    temp = array[0];
    array[0] = array[arraylen - 1];
    array[arraylen - 1] = temp;
}
/**
 * 堆排序  buildBigHeap(建立大顶堆或者小顶堆)
 */
function heapSort(arr) {
    len = arr.length;
    for (let i = len; i > 0; i--) {
        buildBigHeap(arr, i);
        adjustHeap(arr, i);
    }
}





let findKthLargest = function(nums, k) {
    // 从 nums 中取出前 k 个数,构建一个小顶堆
    let heap = [,], i = 0
    while(i < k) {
       heap.push(nums[i++]) 
    }
    buildHeap(heap, k)
    
    // 从 k 位开始遍历数组
    for(let i = k; i < nums.length; i++) {
        if(heap[1] < nums[i]) {
            // 替换并堆化
            heap[1] = nums[i]
            heapify(heap, k, 1)
        }
    }
    
    // 返回堆顶元素
    return heap[1]
};

// 原地建堆,从后往前,自上而下式建小顶堆
let buildHeap = (arr, k) => {
    if(k === 1) return
    // 从最后一个非叶子节点开始,自上而下式堆化
    for(let i = Math.floor(k/2); i>=1 ; i--) {
        heapify(arr, k, i)
    }
}

// 堆化
let heapify = (arr, k, i) => {
    // 自上而下式堆化
    while(true) {
        let minIndex = i
        if(2*i <= k && arr[2*i] < arr[i]) {
            minIndex = 2*i
        }
        if(2*i+1 <= k && arr[2*i+1] < arr[minIndex]) {
            minIndex = 2*i+1
        }
        if(minIndex !== i) {
            swap(arr, i, minIndex)
            i = minIndex
        } else {
            break
        }
    }
}

// 交换
let swap = (arr, i , j) => {
    let temp = arr[i]
    arr[i] = arr[j]
    arr[j] = temp
}
class Heap {
    constructor(comparator = (a, b) => a - b, data = []) {
        this.data = data;
        this.comparator = comparator;//比较器
        this.heapify();//堆化
    }

    heapify() {
        if (this.size() < 2) return;
        for (let i = Math.floor(this.size()/2)-1; i >= 0; i--) {
            this.bubbleDown(i);//bubbleDown操作
        }
    }

    peek() {
        if (this.size() === 0) return null;
        return this.data[0];//查看堆顶
    }

    offer(value) {
        this.data.push(value);//加入数组
        this.bubbleUp(this.size() - 1);//调整加入的元素在小顶堆中的位置
    }

    poll() {
        if (this.size() === 0) {
            return null;
        }
        const result = this.data[0];
        const last = this.data.pop();
        if (this.size() !== 0) {
            this.data[0] = last;//交换第一个元素和最后一个元素
            this.bubbleDown(0);//bubbleDown操作
        }
        return result;
    }

    bubbleUp(index) {
        while (index > 0) {
            const parentIndex = (index - 1) >> 1;//父节点的位置
            //如果当前元素比父节点的元素小,就交换当前节点和父节点的位置
            if (this.comparator(this.data[index], this.data[parentIndex]) < 0) {
                this.swap(index, parentIndex);//交换自己和父节点的位置
                index = parentIndex;//不断向上取父节点进行比较
            } else {
                break;//如果当前元素比父节点的元素大,不需要处理
            }
        }
    }

    bubbleDown(index) {
        const lastIndex = this.size() - 1;//最后一个节点的位置
        while (true) {
            const leftIndex = index * 2 + 1;//左节点的位置
            const rightIndex = index * 2 + 2;//右节点的位置
            let findIndex = index;//bubbleDown节点的位置
            //找出左右节点中value小的节点
            if (
                leftIndex <= lastIndex &&
                this.comparator(this.data[leftIndex], this.data[findIndex]) < 0
            ) {
                findIndex = leftIndex;
            }
            if (
                rightIndex <= lastIndex &&
                this.comparator(this.data[rightIndex], this.data[findIndex]) < 0
            ) {
                findIndex = rightIndex;
            }
            if (index !== findIndex) {
                this.swap(index, findIndex);//交换当前元素和左右节点中value小的
                index = findIndex;
            } else {
                break;
            }
        }
    }

    swap(index1, index2) {//交换堆中两个元素的位置
        [this.data[index1], this.data[index2]] = [this.data[index2], this.data[index1]];
    }

    size() {
        return this.data.length;
    }
}

var maxSlidingWindow = function(nums, k) {
    let ans = [];
    let heap = new Heap((a, b) => b.val - a.val);//大顶堆
    for(let i=0;i<k-1;i++) heap.offer({val: nums[i], index: i});//初始的时候将0~k-1的元素加入堆中
    for(let i=k-1; i<nums.length; i++){//滑动窗口从从索引为k-1的元素开始遍历
        heap.offer({val: nums[i], index: i});//将新进入滑动窗口的元素加堆中
      //当堆顶元素不在滑动窗口中的时候,不断删除堆顶堆元素,直到最大值在滑动窗口里。
        while(heap.peek().index<=i-k) heap.poll();
        ans.push(heap.peek().val);//将在滑动窗口里的最大值加入ans
    }
    return ans;
}

var maxSlidingWindow = function (nums, k) {
    const q = [];//单递减的双端队列
    const ans = [];//最后的返回结果
    for (let i = 0; i < nums.length; i++) {//循环nums
        //当进入滑动窗口的元素大于等于队尾的元素时 不断从队尾出队,
        //直到进入滑动窗口的元素小于队尾的元素,以保证单调递减的性质
        while (q.length && nums[i] >= nums[q[q.length - 1]]) {
            q.pop();
        }
        q.push(i);//元素的索引入队
        while (q[0] <= i - k) {//队头元素已经在滑动窗口外了,移除对头元素
            q.shift();
        }
        //当i大于等于k-1的时候,单调递减队头就是滑动窗口的最大值
        if (i >= k - 1) ans.push(nums[q[0]]);
    }
    return ans;
};