当前位置: 首页 > article >正文

快速选择算法--无序数组中寻找中位数 O(n)的算法及证明

一、排序算法

排序的算法是最容易想到的,但是即使是快排,平均复杂度也只有 O ( n log ⁡ n ) O(n \log n) O(nlogn)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

double findMid(vector<int>& nums) {
    int n = nums.size();
    sort(nums.begin(), nums.end());
    if (n % 2) {
        return nums[n/2];
    }
    else {
        return (nums[n/2] + nums[n/2-1]) / 2.0;
    }
}


int main() {
    vector<int> nums = {9, 8, 7, 6, 1, 2, 3, 4};
    cout << findMid(nums) << endl;
    return 0;
}

这就很简单,没什么说的了,只需要注意如果数组为偶数需要求两个数的平均值

二、快速选择算法

1、随机选择一个元素作为基准

2、将数组分为三个部分,小于基准,等于基准,大于基准

3、确定位置:

  • 如果基准的位置恰好是中位数位置,那么返回基准
  • 如果中位数在小于基准位置,那么递归处理小于部分
  • 如果中位数在大于基准位置,那么递归处理大于部分
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int quickselect(vector<int> nums, int l, int r, int k) {
    if (l == r) return nums[l];

    // 元素选择为最右边元素
    int pivot_value = nums[r];

    int j = l;
    for (int i=l; i<r; i++) {
        if (nums[i] < pivot_value) {
            swap(nums[i], nums[j]);
            ++j;
        }
    }
    // 基准元素放回数组中间
    swap(nums[r], nums[j]);

    if (k == j) {
        return nums[k];
    }
    else if (k < j) {
        return quickselect(nums, l, j-1, k);
    } 
    else {
        return quickselect(nums, j+1, r, k);
    }
    

}

double findMid(vector<int>& nums) {
    int n = nums.size();
    if (n % 2) {
        // cout << '$' << endl;
        return quickselect(nums, 0, n-1, n/2);
    }
    else {
        int a = quickselect(nums, 0, n-1, n/2);
        int b = quickselect(nums, 0, n-1, n/2-1);
        // cout << a << ' ' << b << endl;
        return (a + b) / 2.0;
    }
}


int main() {
    vector<int> nums = {9, 8, 7, 6, 1, 2, 3, 4};
    cout << findMid(nums) << endl;
    return 0;
}

快速选择算法有点类似于快速排序,可以帮助我们快速找到数组中的第k个元素。但是快速排序的时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)为什么快速选择算法就是 O ( n ) O(n) O(n) 呢?

2.1、期望时间复杂度

期望分析

  • 假设基准将数组分为 a n an an ( 1 − a ) n (1−a)n (1a)n 两部分,其中 a a a 是一个介于 0 和 1 之间的常数。
  • 每次递归调用后的时间复杂度可以表示为: T ( n ) = T ( α n ) + O ( n ) T(n)=T(αn)+O(n) T(n)=T(αn)+O(n)
  • 这里, O ( n ) O(n) O(n) 是当前分区所需的时间。

递归展开

  • 我们可以展开这个递归公式:

T ( n ) = O ( n ) + T ( a n ) = O ( n ) + O ( n 2 ) + T ( a 2 n ) = . . . = O ( n ) + O ( n 2 ) + . . . + O ( a k n ) T(n)=O(n)+T(an)=O(n) + O(n^2) + T(a^2n) = ... = O(n) + O(n^2) + ...+O(a^k n) T(n)=O(n)+T(an)=O(n)+O(n2)+T(a2n)=...=O(n)+O(n2)+...+O(akn)

其中k是递归深度,直到n变为1。

求和公式

  • 求和部分是一个几何级数:

O ( n ) + O ( n 2 ) + . . . + O ( a k n ) = O ( n ) ( 1 + a + a 2 + . . . + a k ) = 1 − a k 1 − a O ( n ) O(n) + O(n^2) + ...+O(a^k n) = O(n)(1+a+a^2+...+a^k) = \frac{1-a^k}{1-a} O(n) O(n)+O(n2)+...+O(akn)=O(n)(1+a+a2+...+ak)=1a1akO(n)

最终结果

  • 因此,整体的时间复杂度可以表示为: O ( n ) O(n) O(n)

2.2、最差时间复杂度

最差时间复杂度就可以看做每一层选择的值都很差,都需要和剩下的n-1个值比较
T ( n ) = T ( n − 1 ) O ( n ) = T ( n − 2 ) O ( n − 1 ) O ( n ) = . . . = O ( n ( n − 1 ) 2 ) = O ( n 2 ) T(n) = T(n-1)O(n) = T(n-2)O(n-1)O(n)=...=O(\frac{n(n-1)}{2}) = O(n^2) T(n)=T(n1)O(n)=T(n2)O(n1)O(n)=...=O(2n(n1))=O(n2)


http://www.kler.cn/news/328116.html

相关文章:

  • Django 解决跨域
  • [EBPF] 实时捕获DM数据库是否存在SQL阻塞
  • 线性调频(LFM)脉冲压缩雷达仿真
  • 【RabbitMQ】面试题
  • 一级建造师备考攻略及一建各科老师推荐(各科四大金刚)
  • Python程序转exe后去除命令行窗口的方法
  • MQ高级:RabbitMQ小细节
  • 论文阅读:LM-Cocktail: Resilient Tuning of Language Models via Model Merging
  • Threejs创建正多边体
  • 【Verilog学习日常】—牛客网刷题—Verilog企业真题—VL63
  • Git | Dockerized GitLab 安装使用(简单实操版)
  • 经典sql题(十四)炸裂函数的恢复
  • 【AIGC】ChatGPT提示词助力自媒体内容创作升级
  • 鸿蒙NEXT开发-ArkTS(基于最新api12稳定版)
  • 梯度检查点技术的使用
  • LINUX-线程
  • MySql基础34题写题记录(3-10)
  • 【tbNick专享】虚拟机域控、成员服务器、降级等管理
  • pip install kaggle-environments ISSUE:Failed to build vec-noise
  • MicoZone-Git
  • 深度剖析IT技术前沿:编织数字世界的未来篇章
  • 怎么通过AI大模型开发一个网站?
  • SQL第11课——使用子查询
  • 1.1.5 计算机网络的性能指标(下)
  • 作文网源码 范文论文网模板 带会员系统+支付接口+整站数据
  • docker_阿里云镜像仓库
  • 代码随想录算法训练营第56天 | 1、冗余连接,2、冗余连接II
  • 【数学分析笔记】第4章第2节 导数的意义和性质(1)
  • 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-09-29
  • 谷歌发布Imagen 3,超过SD3、DALL・E-3,谷歌发布新RL方法,性能提升巨大,o1模型已证明