分治算法--寻找第k大数

include <stdio.h>


void swap(int *a, int *b)
{
    int tmp;
    tmp = *a;
    *a = *b;
    *b = tmp;
}


int partition(int arr[], int left, int right, int pivotIndex)
{
    int storeIndex = left;
    int pivotValue = arr[pivotIndex];
    int i;


    swap(&arr[pivotIndex],&arr[right]);
    
    for (i = left; i < right; i ++)
    {
        if (arr[i] > pivotValue)
        {
            swap(&arr[i],&arr[storeIndex]);
            storeIndex++;
        }
    }
    swap(&arr[storeIndex],&arr[right]);
    return storeIndex;
}


int findKMax(int arr[], int left, int right, int k)
{
    int nRet;
    int pivotIndex = left + 1;


    nRet = partition(arr,left,right,pivotIndex);
    if (nRet < k)
    {
        return findKMax(arr,nRet+1,right,k);
    }
    else if (nRet > k)
    {
        return findKMax(arr,left,nRet-1,k);
    }
        
    return nRet;
}




int main()
{
    int i,k,nRet;
    int arr[] = {8,3,4,1,9,7,6,10};


    scanf("%d",&k);
    nRet = findKMax(arr,0,7,k-1);
    
    printf("The Kth Max Number locate in %d is :%d\n",nRet,arr[nRet]);
    for (i = 0; i < 8; i++)
    {
        printf("%3d",arr[i]);
    }
    return 0;

 

2013/6/17  19:55


分治算法--寻找第k大数

  问题描述:给定线性序集中n个元素和一个整数k,1≤k≤n,要求找出这n个元素中第k大的元素,(这里给定的线性集是无序的)。

  其实这个问题很简单,直接对线性序列集qsort,再找出第k个即可。但是这样的时间复杂度就是qsort的时间复杂度O(nlogn)。有没有更快的方法呢?看到网上有一种解法是采取了快排的思路,但是稍微坐了些改动,然后时间复杂度能够接近O(n)。因为最近刚刚写了快排的实现,所以在这我就再把这个实现一次吧。

  解题思路:与快排不同的是,这里只对划分出来的其中一组进行递归处理。任意选定一个pivotIndex,pivotValue = arr[pivotIndex]。经过一次划分后,pivotValue存储在storeIndex的位置,storeIndex把数组划分为两部分。比pivoteValue大的在前面,比pivotValue小的存储在后面(此时前后两部分是没有排好序的)。那么storeIndex位置的pivotValue就肯定是第storeIndex大的数。然后用K于storeIndex比较,如果K<storeIndex,那么说明第K大一定在右边,那么再对右边进行划分即可。如果K>storeIndex,那么说明第K大一定在左边,那么再对左边进行划分。然后递归,最后就可以得到第K大。


<think>我们要求寻找第K大的数,通常有两种方法: 1. 使用排序:将整个数组排序,然后直接取第K大的元素。但这样时间复杂度至少为O(n log n)。 2. 使用分治策略(类似快速排序的partition):这种方法可以在平均O(n)的时间复杂度内解决,最坏情况为O(n^2),但我们可以通过随机化来避免最坏情况。 这里我们介绍第二种方法,即快速选择算法(QuickSelect)。其基本思想是: - 选择一个基准元素(pivot),将数组分为两部分:左边部分都小于等于基准,右边部分都大于基准(或者反过来,这里我们按从大到小找,所以可以调整为左边大右边小)。 - 然后根据基准的位置与k的关系,决定在左边还是右边继续查找。 具体步骤: a. 随机选择一个元素作为基准(pivot)。 b. 将数组分为两部分:所有大于等于pivot的元素放在左边,小于pivot的放在右边(这样数组左部分都是大于等于基准的,右部分都是小于基准的)。 c. 设左边部分的长度为L: - 如果L等于k-1,那么基准值就是第k大的数。 - 如果L大于k-1,那么第k大的数一定在左边部分,递归在左边找第k大的数。 - 如果L小于k-1,那么第k大的数在右边部分,递归在右边找第k-1-L大的数(因为左边已经有L个大的数,还需要在右边找k-1-L个大的数)。 注意:题目中的第K大,通常是指从大到小排序后第k个位置上的数(即第1大就是最大的数)。 下面我们用C++实现这个算法: 注意:为了避免最坏情况(比如数组已经有序),我们随机选择基准点。 代码实现:</think>### 寻找第K大数(C++实现) 以下是使用快速选择算法(QuickSelect)在平均时间复杂度 **O(n)** 内解决“寻找第K大数”问题的完整实现: ```cpp #include <iostream> #include <vector> #include <cstdlib> #include <ctime> using namespace std; // 分区函数:将大于pivot的数放左边,小于的放右边 int partition(vector<int>& arr, int left, int right) { // 随机选择基准避免最坏情况 int random = left + rand() % (right - left + 1); swap(arr[random], arr[right]); int pivot = arr[right]; int i = left; for (int j = left; j < right; j++) { if (arr[j] > pivot) { // 注意:这里用 > 保证降序排列 swap(arr[i], arr[j]); i++; } } swap(arr[i], arr[right]); return i; } // 快速选择主函数 int quickSelect(vector<int>& arr, int left, int right, int k) { if (left <= right) { int pos = partition(arr, left, right); if (pos == k - 1) // 找到第K大元素 return arr[pos]; else if (pos < k - 1) // 在右半部分继续查找 return quickSelect(arr, pos + 1, right, k); else // 在左半部分继续查找 return quickSelect(arr, left, pos - 1, k); } return -1; // 错误情况 } // 封装函数 int findKthLargest(vector<int>& nums, int k) { srand(time(0)); // 初始化随机种子 return quickSelect(nums, 0, nums.size() - 1, k); } int main() { vector<int> nums = {3, 2, 1, 5, 6, 4}; int k = 2; cout << "第" << k << "大的数是: " << findKthLargest(nums, k) << endl; return 0; } ``` ### 关键代码解释: 1. **`partition()` 函数**: - 随机选择基准点(`pivot`)避免最坏情况 - 使用双指针法将大于 `pivot` 的元素移到左侧 - 返回基准点的最终位置 2. **`quickSelect()` 函数**: - **基准位置 = k-1**:找到第K大元素 - **基准位置 < k-1**:在右侧子数组继续查找 - **基准位置 > k-1**:在左侧子数组继续查找 3. **时间复杂度**: - 平均:**O(n)**(每次分区处理一半元素) - 最坏:**O(n²)**(通过随机化基准避免) - 空间:**O(1)**(原地操作) 4. **与快速排序的区别**: - 只需递归处理包含第K大元素的那一半数组 - 无需完全排序整个数组 ### 算法流程示例: ``` 数组: [3,2,1,5,6,4], k=2 1. 随机选基准5 → 分区后: [5,6,4 | 3,2,1] → 基准位置=2 2. 2 > 2-1? → 处理左半部分 [5,6,4] 3. 选基准4 → 分区后: [6,5 | 4] → 基准位置=0 4. 0 < 1 → 处理右半部分 [5,4] 5. 找到第2大元素=5 ``` ### 复杂度对比: | 方法 | 时间复杂度 | 空间复杂度 | |--------------------|------------|------------| | 快速选择(本方法) | O(n) | O(1) | | 堆排序 | O(n log k) | O(k) | | 全排序 | O(n log n) | O(1) |
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值