【重点!!!】【堆】【快排】215.数组中的第K个最大元素

题目
在这里插入图片描述

Python

快排法

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        return self.quickSort(nums, k)
    
    def quickSort(self, nums, count):
        bigger = list()
        same = list()
        smaller = list()
        label = random.choice(nums) # 随机选择用法
        for item in nums:
            if item > label:
                bigger.append(item)
            elif item < label:
                smaller.append(item)
            else:
                same.append(item)
        
        if count <= len(bigger): # 注意: 这里一定包含等号
            return self.quickSort(bigger, count)
        elif count > len(bigger) + len(same): # 注意: 这里一定不包含等号
            return self.quickSort(smaller, count - len(bigger) - len(same))
        else:
            return label

最小堆

在 Python 中,heapq 模块提供了堆队列算法(优先队列算法)的实现。堆是一种基于完全二叉树的数据结构,支持高效插入和提取最小/最大元素的操作。Python 的 heapq 默认实现的是最小堆(每次弹出最小值),但可以通过技巧模拟最大堆。
python中小根堆用法:https://blog.csdn.net/weixin_40134371/article/details/139996858

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        min_heap = []
        for i in range(k):
            heapq.heappush(min_heap, nums[i])
        
        for i in range(k, len(nums)):
            heapq.heappushpop(min_heap, nums[i])

        return min_heap[0]

偷鸡摸狗法

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        # nums.sort(reverse=True) # 这种排序也可
        return sorted(nums)[len(nums) - k]

Java

法1:小根堆

最大的K个元素 => 小根堆(类似上窄下宽的梯形)
最小的K个元素 => 大根堆(类似倒三角形)
必须掌握!!!

class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> q = new PriorityQueue<>();
        for (int i = 0; i < k; ++i) {
            q.offer(nums[i]);
        }

        for (int i = k; i < nums.length; ++i) {
            if (nums[i] > q.peek()) {
                q.poll();
                q.offer(nums[i]);
            }
        }

        return q.peek();
    }
}

法2:基于快排

class Solution {
    public int findKthLargest(int[] nums, int k) {
        List<Integer> list = new ArrayList<>();
        for (int i : nums) {
            list.add(i);
        }

        return quickSelect(list, k);
    }

    public int quickSelect(List<Integer> list, int k) {
        int n = list.size();
        Random rand = new Random();
        int rInx = rand.nextInt(n); // 生成0 ~ n-1之间的整数
        List<Integer> big = new ArrayList<>();
        List<Integer> same = new ArrayList<>();
        List<Integer> small = new ArrayList<>();
        for (int i = 0; i < n; ++i) {
            if (list.get(i) > list.get(rInx)) {
                big.add(list.get(i));
            } else if (list.get(i) == list.get(rInx)) {
                same.add(list.get(i));
            } else {
                small.add(list.get(i));
            }
        }

        if (big.size() >= k) {
            return quickSelect(big, k);
        } else if (k > (big.size() + same.size())) {
            return quickSelect(small, k - (big.size() + same.size()));
        } else {
            return list.get(rInx);
        }
    }
}
<think>好的,我现在要解决用户的问题:在C语言中利用快速序算法找出第k小的数。首先,我需要理解用户的需求。用户可能正在学习算法,或者需要解决一个实际问题,比如在量数据中找到中位数或者特定名的数值。用户可能对快速序和选择算法不太熟悉,需要详细的解释和正确的代码示例。 首先,我应该回顾快速序的基本原理。快排通过分治策略,选择一个基准元素,将数组分成两部分,左边的元素小于等于基准,右边的元素于基准。然后递归地对左右两部分进行序。但用户的问题不是要完全数组,而是找到第k小的元素,这让我想到快速选择算法,它是快速序的变种,可以在平均O(n)的时间复杂度内找到第k小的元素,这比完全序更高效。 接下来,我需要确认用户是否了解快速选择算法,或者是否混淆了快排和快速选择。用户提到“利用快排”,可能只是指基于分治的思想,而不是完整的序。因此,在回答中需要明确区分这两种算法,并解释为什么快速选择更适合这个问题。 然后,我需要考虑用户可能的误区。例如,直接使用快速序完全数组后取第k个元素,虽然可行,但效率较低,特别是当k较小时。因此,应该重点介绍快速选择算法,说明其优势。 接下来,我需要设计一个正确的快速选择算法实现。这包括选择基准元素的方法(比如随机选择或三数取中),分区操作,以及递归地在左或右子数组中进行查找。同时,需要注意数组索引的处理,避免越界错误或逻辑错误。 代码实现部分,需要确保分区函数正确,能够将数组分为小于基准和于基准的两部分,并返回基准的最终位置。然后,根据这个位置与k-1的比较,决定继续处理左半部分还是右半部分。这里要注意数组是从0开始索引,所以第k小的数对应的索引是k-1。 另外,测试用例也很重要。例如,当k=1时找最小值,k等于数组长度时找最大值,以及中间值的情况。需要确保代码在各种情况下都能正确运行,并且处理边界条件,比如数组为空或k值超出范围。 用户可能忽略的错误点包括:基准选择不当导致最坏时间复杂度O(n²),没有处理重复元素,或者递归终止条件不正确。因此,在解释中需要强调这些点,并给出优化建议,比如随机选择基准以避免最坏情况。 最后,需要比较不同方法的优劣。例如,快速选择适用于无需完全序的情况,而序方法(维护一个小为k的)可能在k远小于n时更高效。用户可能根据具体情况选择不同方法,但快速选择在平均情况下更优。 总结下来,回答的结构应该包括:问题分析、算法原理、代码实现、复杂度分析、优化建议和测试示例。确保用户不仅能理解算法思路,还能正确实现并应用到实际问题中。</think>以下是使用快速选择算法(QuickSelect)在C语言中查找第k小数的详细解析: --- ### 核心算法原理 **快速选择算法**是快速序的改进版本,基于分治策略,在平均$O(n)$时间复杂度内找到第k小的元素(最优时间复杂度$O(n)$,最坏情况$O(n^2)$) --- ### 标准实现代码 ```c #include <stdio.h> #include <stdlib.h> #include <time.h> // 分区函数(三数取中法优化) int partition(int arr[], int left, int right) { // 选择基准值优化 int mid = left + (right - left)/2; if(arr[left] > arr[right]) swap(&arr[left], &arr[right]); if(arr[mid] > arr[right]) swap(&arr[mid], &arr[right]); if(arr[left] < arr[mid]) swap(&arr[left], &arr[mid]); int pivot = arr[left]; int i = left + 1, j = right; while(1) { while(i <= j && arr[i] <= pivot) i++; while(i <= j && arr[j] >= pivot) j--; if(i > j) break; swap(&arr[i], &arr[j]); } swap(&arr[left], &arr[j]); return j; } // 快速选择核心函数 int quickSelect(int arr[], int left, int right, int k) { if(left == right) return arr[left]; int pivotIndex = partition(arr, left, right); int currentRank = pivotIndex - left + 1; if(k == currentRank) { return arr[pivotIndex]; } else if(k < currentRank) { return quickSelect(arr, left, pivotIndex-1, k); } else { return quickSelect(arr, pivotIndex+1, right, k - currentRank); } } // 辅助函数 void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } ``` --- ### 算法特性对比 | 特性 | 快速选择算法 | 完全快速序 | |---------------------|------------------------------|-----------------------| | **时间复杂度** | 平均$O(n)$,最坏$O(n^2)$ | $O(n \log n)$ | | **空间复杂度** | $O(1)$ | $O(\log n)$栈空间 | | **是否修改原数组** | 是 | 是 | | **适用场景** | 仅需部分序 | 需要完全序 | --- ### 正确性验证测试 ```c int main() { int testCases[][2] = { {3, 1}, // 数组:[3], k=1 → 3 {5,2,1,3,4}, // 序后[1,2,3,4,5] {9,7,5,3,1} // 逆序测试 }; for(int i=0; i<3; i++){ int len = sizeof(testCases[i])/sizeof(int)-1; int k = testCases[i][len]; int* arr = testCases[i]; printf("第%d小的数是: %d\n", k, quickSelect(arr, 0, len-2, k)); // len-2除k值本身 } return 0; } ``` --- ### 常见问题解析 1. **基准选择优化** - 使用三数取中法(Median-of-Three)可以有效避免最坏情况 - 随机选择基准:`swap(&arr[left], &arr[left + rand()%(right-left+1)]);` 2. **重复元素处理** 分区时应使用双向扫描法(Hoare分区法),确保重复元素正确分布 3. **索引边界处理** `currentRank`计算时需要包含基准元素的位置: `pivotIndex - left + 1` --- ### 复杂度优化建议 | 优化方法 | 效果 | 实现难度 | |-----------------------|-------------------------------|----------| | 三点中值法 | 减少最坏情况概率 | ★★☆ | | 随机化基准选择 | 数学期望$O(n)$ | ★☆☆ | | 插入序混合策略 | 小数组切换插入序(n<10) | ★★★ | | 三向切分法 | 优化重复元素处理 | ★★★☆ | --- ### 扩展应用场景 1. 查找中位数(k = n/2) 2. 查找前k个最小元素(修改算法返回区间) 3. 分布式系统Top K问题(结合MapReduce) 4. 实时数据流处理(维护小根结构) --- > **关键理解点**:快速选择算法本质是通过每次分区操作确定一个元素的最终位置,根据该位置与目标k的关系决定下一步操作范围,逐步缩小查找区间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值