分治递归--线性时间选择算法及其实现

本文介绍了线性时间选择算法,一种在不完全排序的情况下找到数组中第k个最小元素的方法,通过划分和中位数操作,以O(n)时间复杂度解决传统排序的O(nlogn)问题。
摘要由CSDN通过智能技术生成

  前言:

    在计算机科学中,找到数组中第k个最小元素是一个常见的问题。常规的做法是对数组进行排序,然后取第k个元素作为结果。然而,排序整个数组的时间复杂度为O(nlogn),并不是最优解。本文将介绍一种名为线性时间选择(Linear Time Select)的算法,它能够在O(n)的时间复杂度内解决这个问题。

算法原理:

     线性时间选择算法通过利用"划分"和"中位数"的概念来进行众多元素的筛选和缩小范围,最终找到第k个最小的元素。

 算法运行演示--->线性时间选择 例子演示

具体的算法步骤如下:

函数准备

1.findMedian():对数组排序,并返回数组的中位数(在此问题中每个数组长度<=5 所以此函数的时间复杂度是5log5 是常数

// 寻找子数组的中位数 并对数组排序
int findMedian(vector<int>& nums, int start, int end) {
    // 将子数组排序
    sort(nums.begin() + start, nums.begin() + end );
    // 返回中位数
    return nums[(start + end) / 2];
}


2.partition():划分数组(将小于pivot的划分在左边大于的划分在右边),并返回pivot数对应的索引。

int partition(vector<int>& nums, int start, int end, int pivot) {
    int left = start;
    int right = end;
    while (true) {
        // 在左侧找到第一个大于等于pivot的数
        while (nums[left] < pivot) {
            left++;
        }
        // 在右侧找到第一个小于等于pivot的数
        while (nums[right] > pivot) {
            right--;
        }
        if (left >= right) {  // 找到中间位置
            return right;
        }
        // 交换左右两个数的位置
        swap(nums[left], nums[right]);
        left++;
        right--;
    }
}

 3.线性时间选择函数linearTimeSelect(vector<int>& nums, int k, int start, int end)  引用传递,改变原数组

例子:23个数据,找第8个最小的数:

1.将数组划分为若干个长度为5的子数组,并对每个子数组进行排序,并将这些中位数组成一个新的数组,称为medians。

// 将数组划分为若干个长度为5的子数组,对子数组排序。并找到每个子数组的中位数放入 数组medians
    vector<int> medians;
    for (int i = start; i <= end; i += 5) {
        int subEnd = i + 5 < end ? i + 5 : end;
        int median = findMedian(nums, i, subEnd);
        medians.push_back(median);
    }


2.递归调用线性时间选择函数,求出medians数组的中位数,记为medianOfMedians,并且根据medianOfMedians将nums数组进行划分,即将小于medianOfMedians的元素放在medianOfMedians的左侧,大于medianOfMedians的元素放在右侧。

// 递归调用线性时间选择函数,查找新数组中的中位数,并划分成长度为5的子数组排序。
    int medianOfMedians = linearTimeSelect(medians, medians.size() / 2, 0, medians.size() - 1);
// 划分数组,找到中位数的在num数组中的索引 并将数组按照medianOfMedians划分(前面值小于medianOfMedians后面值大于medianOfMedians)
    int pivotIndex = partition(nums, start, end, medianOfMedians);


3.如果medianOfMedians的索引恰好等于k,则找到第k个最小元素,返nums[medianOfMedians]。
如果k小于medianOfMedians的索引,则在左侧继续递归查找第k个最小元素。
如果k大于medianOfMedians的索引,则在右侧继续递归查找第k个最小元素。

  // 根据中位数的索引与第k个最小元素的位置关系,递归地对其中一个部分进行查找
    if (k == pivotIndex) {  // 找到第k个最小元素
        return nums[pivotIndex];
    } else if (k < pivotIndex) {  // 第k个最小元素在左侧
        return linearTimeSelect(nums, k, start, pivotIndex - 1);
    } else {  // 第k个最小元素在右侧
        return linearTimeSelect(nums, k, pivotIndex + 1, end);
    }

 

完整代码:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 寻找子数组的中位数 并对数组排序
int findMedian(vector<int>& nums, int start, int end) {
    // 将子数组排序
    sort(nums.begin() + start, nums.begin() + end );
    // 返回中位数
    return nums[(start + end) / 2];
}

// 划分数组,返回pivot数大小的索引并对数组进行排序
int partition(vector<int>& nums, int start, int end, int pivot) {
    int left = start;
    int right = end;
    while (true) {
        // 在左侧找到第一个大于等于pivot的数
        while (nums[left] < pivot) {
            left++;
        }
        // 在右侧找到第一个小于等于pivot的数
        while (nums[right] > pivot) {
            right--;
        }
        if (left >= right) {  // 找到中间位置
            return right;
        }
        // 交换左右两个数的位置
        swap(nums[left], nums[right]);
        left++;
        right--;
    }
}

// 线性时间选择函数
int linearTimeSelect(vector<int>& nums, int k, int start, int end) {
    // 如果只有一个元素,直接返回
    if (start == end) {
        return nums[start];
    }

    // 将数组划分为若干个长度为5的子数组,对子数组排序。并找到每个子数组的中位数放入 数组medians
    vector<int> medians;
    for (int i = start; i <= end; i += 5) {
        int subEnd = i + 5 < end ? i + 5 : end;
        int median = findMedian(nums, i, subEnd);
        medians.push_back(median);
    }

    // 递归调用线性时间选择函数,查找新数组中的中位数,并划分成长度为5的子数组排序。
    int medianOfMedians = linearTimeSelect(medians, medians.size() / 2, 0, medians.size() - 1);

    // 划分数组,找到中位数的在num数组中的索引 并将数组按照medianOfMedians划分(前面值小于medianOfMedians后面值大于medianOfMedians)
    int pivotIndex = partition(nums, start, end, medianOfMedians);

    // 根据中位数的索引与第k个最小元素的位置关系,递归地对其中一个部分进行查找
    if (k == pivotIndex) {  // 找到第k个最小元素
        return nums[pivotIndex];
    } else if (k < pivotIndex) {  // 第k个最小元素在左侧
        return linearTimeSelect(nums, k, start, pivotIndex - 1);
    } else {  // 第k个最小元素在右侧
        return linearTimeSelect(nums, k, pivotIndex + 1, end);
    }
}

int main() {
    vector<int> nums = {3, 1, 7, 5, 9, 2};
    int k = 5;
    int result = linearTimeSelect(nums, k - 1, 0, nums.size() - 1);  // 注意将k减1,因为索引从0开始
    cout << "第" << k << "小的元素是:" << result << endl;

    return 0;
}

时间复杂度:

   线性时间选择算法的时间复杂度为O(n),其中n是数组的长度。具体来说,算法通过划分和中位数的概念,将数组不断缩小范围,直到找到第k个最小元素。

  在每一次递归调用线性时间选择函数时,都会对子数组进行划分和排序操作。对于每个子数组,我们需要线性时间(O(n))来找到其中位数,并对其进行排序。

   假设每次划分后,我们能够将数组划分为规模减少了常数倍的子问题。那么,递归调用的次数可以表示为T(n) = T(n/5) + O(n),其中T(n/5)表示对规模为n/5的子问题递归调用线性时间选择函数的时间复杂度。

   根据主定理(Master Theorem),可以得知这样的递归关系的时间复杂度为O(n)。

   因此,整个线性时间选择算法的时间复杂度为O(n)。无论数组的规模有多大,算法都能在线性时间内找到第k个最小元素,这是该算法的优势之一。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辣鲨椒鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值