这个题目原本是一个面试题,我写了三种用JS实现的方式,做个笔记,就当做是温习知识点吧!有别的实现方式欢迎评论。
先造五千条随机且不重复
的假数据,代码如下:
js
function genData() {
const arr = [];
while(arr.length < 5000) {
const num = Math.floor(10000 * Math.random()) + 1;
if (!arr.includes(num)) {
arr.push(num);
}
}
return arr;
}
一、数组的sort方法实现
利用数组提供的API:sort
、slice
就可以非常快速的实现。这种方式很easy,就不过多赘述了,直接上代码。
js
function getTop10() {
const list = genData();
if (!list || list.length === 0) return [];
// 使用sort配合reverse多此一举。
// list.sort((a, b) => a - b).reverse();
// const top10 = list.slice(0, 10);
const top10 = list.sort((a, b) => b - a).slice(0, 10);
return top10;
}
二、使用快排的方法实现
快速排序,这种排序方式在日常开发中很常见,为了巩固知识点,先来回顾一下快速排序的思想。
快速排序的排序过程只需要三步即可:
- 在要排序的数组中,找一个元素作为一个基准点;
- 准备两个空数组,将所有小于基准点的元素放在左边,所有大于基准点的元素放在右边;
- 重复上面两个步骤,递归执行完成,直到每个数组中只有一个元素为止;
为了实现标题中提到的功能,需要稍微改动一下,将大于基准元素的数据放在左边,将小于基准元素的数据放在右边,就能得到一个由大到小排序的数组了。
首先定义一个快排函数quickSort
,函数接收一个数组作为参数,函数体内有以下逻辑:
- 先判断数组长度是否小于等于1,为真就直接返回;
- 找到基准点对应的元素,一般取数组总长度的中间值;
- 定义两个空数组,
right
存放大于基准元素的数据,left
存放小于基准元素的数据; - 遍历数组;
- 递归调用quickSort方法;
js
function quickSort(arr) {
if (arr.length <= 1) return arr;
const piovtIndex = Math.floor(arr.length / 2);
// 利用数组splice方法,将基准元素从数组中删除
const piovt = arr.splice(piovtIndex, 1)[0];
const left = [];
const right = [];
for(let i = 0, len = arr.length; i < len; i++) {
if (arr[i] > piovt) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
// 递归调用quickSort方法
return quickSort(left).concat([piovt], quickSort(right));
}
通过上面的代码得到了一个由大到小排序的数组,而数组中最前面的十个元素就是我们想要的结果了。 具体代码如下:
js
function findTop10() {
const arr = quickSort(genData());
console.log(arr)
return arr.splice(0, 10);
}
效果截图:
三、使用小顶堆方式实现
注意:
小顶堆这种方式性能还算OK,空间复杂度低,不过IO读取非常频繁,对系统压力比较大,在写业务的时候,如若遇到需要处理超大型数据的时候,前端若要用此种方式来处理大数据,需要综合考虑,慎重慎重!
在实现最小堆之前,需要了解一些前置知识:
- 什么是小顶堆?
- 堆是一个完全二叉树
- 堆上任意一个节点的值,都必须小于等于其左右两个子节点的值。(大顶堆正好相反)
- 在小顶堆中,根节点是堆中最小的元素
- 用数组实现的小顶堆,每个节点中,它的父节点、左右子节点对应的位置是什么?
- 小顶堆可以用一个数组表示,给定一个节点i,那么它的父节点一定为A(i/2),左子节点为A(2i),右子节点为A(2i+1)
放上一张图再配合上面的概念,理解起来会轻松些~
上面这张图来自于前端进阶算法9,想要了解更多关于堆的知识,可以看下这篇文章。
有了前面的知识铺垫后,接下来开始构想实现思路:
- 维护一个最小堆,这个堆最多包含10个元素(10个最大的数)
- 遍历数组中的每个元素,判断最小堆的长度,如果堆的长度不足10个:
- 就将当前元素作为插入元素,直接插入到最小堆中,同时将该元素与其父节点做比较,如果插入元素小于父节点,则插入元素与父节点交换位置,确保父节点总是小于子节点的。
- 如果最小堆的长度已满10个元素,则判断当前元素与最小堆的根节点进行比较:(在小顶堆中,根节点是堆中最小的元素)
- 当前元素比最小堆中的第一个元素大,把最小堆中的根节点替换掉。
- 进行下沉操作,即将当前元素放到合适的位置上面。
- 最终遍历完成后,最小堆中存放的10个数就是数组中最大的10个数。
代码示例:
js
function findTopTen(array) {
if (!array || array.length === 0) {
return [];
}
if (array.length <= 10) {
return array.sort((a, b) => b - a);
}
// 创建一个最小堆,用于存储最大的10个数
const minHeap = new MinHeap(10);
// 遍历数组,将元素插入最小堆
for (let i = 0; i < array.length; i++) {
minHeap.insert(array[i]);
}
// 从最小堆中取出最大的10个数
const topTen = minHeap.extractTopTen();
return topTen;
}
// 最小堆类
class MinHeap {
constructor(maxSize) {
this.heap = [];
this.maxSize = maxSize;
}
// 插入元素到堆中
insert(element) {
if (this.heap.length < this.maxSize) {
this.heap.push(element);
this.bubbleUp(this.heap.length - 1);
} else if (element > this.heap[0]) {
this.heap[0] = element;
this.sinkDown(0);
}
}
// 上浮操作,确保父节点小于子节点
bubbleUp(index) {
const parentIndex = Math.floor((index - 1) / 2);
if (index && this.heap[parentIndex] > this.heap[index]) {
this.swap(parentIndex, index);
this.bubbleUp(parentIndex);
}
}
// 下沉操作,确保父节点小于子节点
sinkDown(index) {
const length = this.heap.length;
let smallest = index;
const left = 2 * index + 1;
const right = 2 * index + 2;
if (left < length && this.heap[left] < this.heap[smallest]) {
smallest = left;
}
if (right < length && this.heap[right] < this.heap[smallest]) {
smallest = right;
}
if (smallest !== index) {
this.swap(index, smallest);
this.sinkDown(smallest);
}
}
// 交换堆中两个元素的位置
swap(i, j) {
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
}
// 提取堆中最大的10个数
extractTopTen() {
return this.heap.sort((a, b) => b - a).slice(0, 10);
}
}
在上面的示例代码中,MinHeap
类维护了一个最小堆,最多包含10个元素。当插入一个新元素时,如果堆的大小还没有达到10,就直接插入并上浮;如果堆已满且新元素大于堆顶元素,就替换堆顶元素并下沉。最终,extractTopTen
方法将堆中的元素排序并返回前10个最大的数。
打印结果:
本文内容就是这些了,有不正确的地方欢迎掘友们纠正~