解决Top N(前N大或前N小)的算法问题通常涉及到对数据进行排序、筛选或堆的操作。以下是一些常见的解决方法:
1、 排序法:
对整个数据集进行排序,然后取出前N个元素作为结果。时间复杂度为O(nlogn),适用于数据量不大的情况。
2、 部分排序法:
可以使用部分排序算法,比如堆排序中的堆,只维护前N个最大或最小元素。时间复杂度为O(nlogN),适用于大数据集合。
3、 计数法:
对数据进行计数,并选择出前N个最大或最小的元素。适用于数据有一定范围的情况。
4、 分治法:
使用分治法,将数据分割成不同的部分,只处理包含前N个元素的部分。适用于数据量大且分布均匀的情况。
5、 优先队列(堆): 使用堆数据结构来维护前N个元素,可以实现较高效的Top N查找。时间复杂度为O(nlogN)。
6、 快速选择算法:
类似于快速排序的思想,通过每次选择一个基准值,将数据分为比基准值大和小的两部分,只处理包含前N个元素的部分。时间复杂度为O(n)。
根据具体情况选择合适的算法,通常在处理大数据集时,优先考虑堆排序或快速选择算法,因为它们可以在较短的时间内找到Top N元素。
部分排序
function findTopN(arr, n) {
// 部分排序法
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] > arr[i]) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
}
return arr.slice(0, n);
}
// 示例
const arr = [3, 10, 4, 7, 6, 9, 1];
const n = 3;
const topN = findTopN(arr, n);
console.log(`前${n}个最大值为:${topN}`);
计数法
function findTopN(arr, n) {
// 计数法
const counts = {};
arr.forEach(num => {
counts[num] = (counts[num] || 0) + 1;
});
const uniqueNums = Object.keys(counts).map(Number);
uniqueNums.sort((a, b) => b - a); // 降序排序
const result = [];
let count = 0;
for (let num of uniqueNums) {
const freq = counts[num];
for (let i = 0; i < freq && count < n; i++) {
result.push(num);
count++;
}
if (count >= n) {
break;
}
}
return result;
}
// 示例
const arr = [3, 10, 4, 7, 6, 9, 1, 10, 10, 7];
const n = 3;
const topN = findTopN(arr, n);
console.log(`前${n}个最大值为:${topN}`);
分治法
function findTopN(arr, n) {
// 分治法
function quickSelect(arr, k, left, right) {
if (left === right) {
return arr[left];
}
let pivotIndex = Math.floor(Math.random() * (right - left + 1)) + left;
pivotIndex = partition(arr, left, right, pivotIndex);
if (k === pivotIndex) {
return arr[k];
} else if (k < pivotIndex) {
return quickSelect(arr, k, left, pivotIndex - 1);
} else {
return quickSelect(arr, k, pivotIndex + 1, right);
}
}
function partition(arr, left, right, pivotIndex) {
let pivotValue = arr[pivotIndex];
[arr[pivotIndex], arr[right]] = [arr[right], arr[pivotIndex]];
let storeIndex = left;
for (let i = left; i < right; i++) {
if (arr[i] > pivotValue) {
[arr[i], arr[storeIndex]] = [arr[storeIndex], arr[i]];
storeIndex++;
}
}
[arr[storeIndex], arr[right]] = [arr[right], arr[storeIndex]];
return storeIndex;
}
const result = [];
for (let i = 0; i < n; i++) {
result.push(quickSelect(arr, i, 0, arr.length - 1));
}
return result;
}
// 示例
const arr = [3, 10, 4, 7, 6, 9, 1];
const n = 3;
const topN = findTopN(arr, n);
console.log(`前${n}个最大值为:${topN}`);
快速选择算法
快速选择算法(Quickselect)它是快速排序的一种变体。要找到前N个最大的元素,我们可以利用快速选择算法找到第k大的元素,然后对前k大的元素进行筛选。
function partition(arr, left, right) {
let pivot = arr[right];
let i = left;
for (let j = left; j < right; j++) {
if (arr[j] >= pivot) { // 注意这里是 >= ,因为我们要找的是前N个最大的元素
[arr[i], arr[j]] = [arr[j], arr[i]];
i++;
}
}
[arr[i], arr[right]] = [arr[right], arr[i]];
return i;
}
function quickselect(arr, left, right, k) {
if (left <= right) {
let pivotIndex = partition(arr, left, right);
if (pivotIndex === k) {
return arr.slice(0, k + 1);
} else if (pivotIndex < k) {
return quickselect(arr, pivotIndex + 1, right, k);
} else {
return quickselect(arr, left, pivotIndex - 1, k);
}
}
return [];
}
function findTopNElements(arr, n) {
if (n <= 0) {
return [];
}
if (n >= arr.length) {
return arr.slice().sort((a, b) => b - a);
}
return quickselect(arr, 0, arr.length - 1, n - 1);
}
// 示例用法
const arr = [3, 2, 1, 5, 6, 4];
const n = 3;
const topNElements = findTopNElements(arr, n);
console.log(`前${n}个最大的元素:`, topNElements);
1、partition函数:这个函数用于将数组分成两部分,以选择的枢轴为基准,将大于等于枢轴的元素放在左边,小于枢轴的元素放在右边。这里选择数组的最后一个元素作为枢轴。
2、quickselect函数:这是一个递归函数,用于在分区后选择第k个最大的元素。如果分区点正好是k,则返回前k个元素。如果分区点小于k,则递归处理右半部分,否则处理左半部分。
3、findTopNElements函数:这是主函数,用于处理特殊情况(如n小于等于0或n大于等于数组长度),并调用quickselect函数。
优先队列
使用优先队列(最小堆)算法找到前N个最大的元素,可以使用JavaScript中的MinHeap来实现。我们将数组中的元素依次插入到一个大小为N的最小堆中,如果堆的大小超过N,就移除堆顶元素(最小的元素)。最终,堆中存放的就是前N个最大的元素。
class MinHeap {
constructor() {
this.heap = [];
}
insert(value) {
this.heap.push(value);
this.bubbleUp();
}
bubbleUp() {
let index = this.heap.length - 1;
while (index > 0) {
let parentIndex = Math.floor((index - 1) / 2);
if (this.heap[parentIndex] <= this.heap[index]) break;
[this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];
index = parentIndex;
}
}
extractMin() {
if (this.heap.length === 1) return this.heap.pop();
const min = this.heap[0];
this.heap[0] = this.heap.pop();
this.sinkDown(0);
return min;
}
sinkDown(index) {
const length = this.heap.length;
const element = this.heap[index];
while (true) {
let leftChildIndex = 2 * index + 1;
let rightChildIndex = 2 * index + 2;
let leftChild, rightChild;
let swap = null;
if (leftChildIndex < length) {
leftChild = this.heap[leftChildIndex];
if (leftChild < element) swap = leftChildIndex;
}
if (rightChildIndex < length) {
rightChild = this.heap[rightChildIndex];
if ((swap === null && rightChild < element) || (swap !== null && rightChild < leftChild)) {
swap = rightChildIndex;
}
}
if (swap === null) break;
[this.heap[index], this.heap[swap]] = [this.heap[swap], this.heap[index]];
index = swap;
}
}
size() {
return this.heap.length;
}
peek() {
return this.heap[0];
}
}
function findTopNElements(arr, n) {
if (n <= 0) {
return [];
}
if (n >= arr.length) {
return arr.slice().sort((a, b) => b - a);
}
const minHeap = new MinHeap();
for (let i = 0; i < arr.length; i++) {
minHeap.insert(arr[i]);
if (minHeap.size() > n) {
minHeap.extractMin();
}
}
const result = [];
while (minHeap.size() > 0) {
result.push(minHeap.extractMin());
}
return result.sort((a, b) => b - a); // 最后结果需要从大到小排序
}
// 示例用法
const arr = [3, 2, 1, 5, 6, 4];
const n = 3;
const topNElements = findTopNElements(arr, n);
console.log(`前${n}个最大的元素:`, topNElements);
1、MinHeap类:实现了一个最小堆,包括插入元素、移除最小元素、获取堆的大小、查看堆顶元素等操作。
2、findTopNElements函数:这个函数使用最小堆来存储前N个最大的元素。如果堆的大小超过N,就移除堆顶元素。最终,堆中存放的就是前N个最大的元素。
3、示例用法:创建一个数组和N值,并调用findTopNElements函数来找到前N个最大的元素。最后输出结果。