百万级数据量下找到数组中的第K个最大元素

相信看过我这篇文章的朋友可能会对这些不同时间复杂度的实现有着好奇,到底它们的差别会有多大呢?今天我们自己造数据,运行一下看看是什么效果。

问题:

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

造数据

造数据我用Python比较方便,下面的代码是Python3.8环境运行的:

Python 复制代码
import sys
import random

def main():
    if len(sys.argv) < 2:
        print("请提供要生成的数字数量作为命令行参数。")
        sys.exit(1)
    try:
        n = int(sys.argv[1])
    except ValueError:
        print("参数必须为整数。")
        sys.exit(1)

    # 生成包含正负数的大范围随机数
    nums = [str(random.randint(-10**5, 10**5)) for _ in range(n)]
    print(' '.join(nums))

if __name__ == "__main__":
    main()

给上面的代码命名为generate.py,然后在命令行使用下面的方式就能造出百万数据了:

bash 复制代码
python generate.py 1000000 > data.txt

读数据,算法比较

只要打开Ide,创建一个类就好了:

Java 复制代码
package hot100;


import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.PriorityQueue;

public class KthLargest {
    public static void main(String[] args) {
        String filePath = "C:\Users\xqkon\Documents\Work\Java Projects\leetcode\src\hot100\data.txt";
        int k = 1000; // 可根据需要修改或通过命令行参数传入

        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line = br.readLine();
            String[] parts = line.split("\s+");
            int[] nums = new int[parts.length];

            // 将字符串转换为整数数组
            for (int i = 0; i < parts.length; i++) {
                nums[i] = Integer.parseInt(parts[i]);
            }

            // 性能测试
            long startTime = System.nanoTime();
            int result = findKthLargestForce(nums, k);
            long endTime = System.nanoTime();

            // 输出结果和时间(毫秒)
            System.out.printf("第%d大元素: %d%n", k, result);
            System.out.printf("耗时: %.3f 毫秒%n", (endTime - startTime) / 1e6);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static int findKthLargest(int[] nums, int k) {
        return quickSelect(nums, 0, nums.length - 1, nums.length - k);
    }

    // 快速选择算法
    private static int quickSelect(int[] nums, int left, int right, int k) {
        if (left == right) return nums[left];

        int pivotIndex = partition(nums, left, right);

        if (k == pivotIndex) return nums[k];
        else if (k < pivotIndex) return quickSelect(nums, left, pivotIndex - 1, k);
        else return quickSelect(nums, pivotIndex + 1, right, k);
    }

    private static int partition(int[] nums, int left, int right) {
        int pivot = nums[right];
        int i = left;
        for (int j = left; j < right; j++) {
            if (nums[j] <= pivot) {
                swap(nums, i, j);
                i++;
            }
        }
        swap(nums, i, right);
        return i;
    }

    private static void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    // 使用类库里的最小堆
    public static int findKthLargestPQ(int[] nums, int k) {
        PriorityQueue<Integer> heap = new PriorityQueue<Integer>();  // also can add param: (a, b) -> a - b, still 最小堆 (min - heap)
        for (int i : nums) {
            if (heap.size() < k) {
                heap.offer(i);
            } else if (i > heap.peek()) {
                heap.poll();
                heap.offer(i);
            }
        }
        return heap.peek();
    }

    // 暴力解法
    public static int findKthLargestForce(int[] nums, int k) {
        Arrays.sort(nums);
        return nums[nums.length-k];
    }
}

接下来是三种算法的比较(k = 100):

暴力方法 O(nlogn):

bash 复制代码
第1000大元素: 99800
耗时: 91.194 毫秒

最小堆 O(nlogk):

bash 复制代码
第1000大元素: 99800
耗时: 41.477 毫秒

快速选择排序 平均O(n):

bash 复制代码
第1000大元素: 99800
耗时: 7.565 毫秒

由此可见O(n)级别算法的厉害之处,当数据量来到百万级别时间直接差了一个量级。想想看,如果是互联网公司,动辄上亿的数据量呢?可想而知,将心比心,大家为什么要考算法。

相关推荐
过往入尘土24 分钟前
服务端与客户端的简单链接
人工智能·python·算法·pycharm·大模型
zycoder.29 分钟前
力扣面试经典150题day1第一题(lc88),第二题(lc27)
算法·leetcode·面试
蒙奇D索大35 分钟前
【数据结构】考研数据结构核心考点:二叉排序树(BST)全方位详解与代码实现
数据结构·笔记·学习·考研·算法·改行学it
智驱力人工智能1 小时前
工厂抽烟检测系统 智能化安全管控新方案 加油站吸烟检测技术 吸烟行为智能监测
人工智能·算法·安全·边缘计算·抽烟检测算法·工厂抽烟检测系统·吸烟监测
程序员爱钓鱼2 小时前
Go语言实战案例——进阶与部署篇:编写Makefile自动构建Go项目
后端·算法·go
_Power_Y2 小时前
Java面试常用算法api速刷
java·算法·面试
艾醒(AiXing-w)2 小时前
大模型面试题剖析:模型微调中冷启动与热启动的概念、阶段与实例解析
人工智能·深度学习·算法·语言模型·自然语言处理
天选之女wow3 小时前
【代码随想录算法训练营——Day32】动态规划——509.斐波那契数、70.爬楼梯、746.使用最小花费爬楼梯
算法·leetcode·动态规划
红衣小蛇妖3 小时前
LeetCode-704-二分查找
java·算法·leetcode·职场和发展
rongqing20193 小时前
问题记录:一个简单的字符串正则匹配算法引发的 CPU 告警
算法