线性时间选择

(一)题目

问题描述

给定线性序集中nnn个元素和一个正数kkk,1≤k≤n1\leq k\leq n1≤k≤n,要求找出这nnn个元素中第kkk小的元素

注意:nnn中的元素不重复

(二)解答

1.RandomizedSelect算法

算法思路

在数组a[p:r]a[p:r]a[p:r]中随机找一个数iii将数组划分成两个子数组a[p:i]a[p:i]a[p:i]和a[i+1:r]a[i+1:r]a[i+1:r],如果数组a[p:r]a[p:r]a[p:r]的长度小于等于kkk,说明第kkk小的数在这个数组,将其递归,否则递归数组a[i+1:r]a[i+1:r]a[i+1:r],直到 p=rp=rp=r,数组被分割成只剩下一个元素,该元素就是第kkk小的元素。

举例

源代码
#include<iostream>
#include<cstdio>
#include<random>using namespace std;int n, k, len;//将数组数组首元素a[p]作为基准数将数组分割
int Partition(int a[], int p, int r);
//交换两个元素
void Swap(int &a, int &b);
//在数组中随机选择一个数将数组分割
int RandomizedPartition(int a[], int p, int r);
//产生随机数
int Random(int x, int y);
//线性划分
int RandomizedSelect(int a[], int p, int r, int k);int main()
{//输入数组长度ncin>>n;//输入数组int *a = new int[n];for (int i = 0; i < n; ++i){cin>>a[i];}//输入第k小cin>>k;//找第k小并返回int res = RandomizedSelect(a, 0, n - 1, k);cout<<res<<endl;delete []a;return 0;
}int Partition(int a[], int p, int r)
{//i指向首元素,j指向尾元素的下一个元素int i = p, j = r + 1;//将首元素作为基准数int x = a[p];while (1){//i从基准数右边的元素开始找,直到找到第一个大于等于基准数的元素while (a[++i] < x && i < r);//j从尾元素开始找,直到找到第一个小于等于基准数的元素while (a[--j] > x);//若i>=j,说明基准数的位置已找到,为jif (i >= j){break;}//交换两个元素,使得基准数左边的数均不大于它,右边的数均不小于它Swap(a[i], a[j]);}//将基准数归位a[p] = a[j];a[j] = x;//返回基准数的位置return j;
}void Swap(int &a, int &b)
{int temp;temp = a;a = b;b = temp;
}int RandomizedPartition(int a[], int p, int r)
{//在p和r之间找一个随机数int i = Random(p, r);Swap(a[i], a[p]);return Partition(a, p, r);
}int Random(int x, int y)
{return x + rand() % (y - x);
}int RandomizedSelect(int a[], int p, int r, int k)
{//数组被分割成只剩下一个元素,该元素就是第k小的元素if (p == r){return a[p];}//在数组中随机找一个数将数组分割,分成小于等于该基准的数组和大于该基准的数组int i = RandomizedPartition(a, p, r);//求较小数数组的长度len = i - p + 1;//若较小数数组的长度小于等于k,说明第k小的元素在这个数组内,将其递归if (k <= len){return RandomizedSelect(a, p, i, k);}//否则,说明第k小的元素在较大数数组,将其递归else{return RandomizedSelect(a, i + 1, r, k - len);}
}

2.Select算法

算法思路

将原数组分成⌈n5⌉\lceil\frac{n}{5}\rceil⌈5n​⌉组,每组有5个元素(最后一个数组的元素个数可能不等于5,)将这5个元素排序后找到其中位数并将中位数置于每组的开头,递归调用Select算法找到各组中位数的中位数作为划分基准x(⌈n5⌉\lceil\frac{n}{5}\rceil⌈5n​⌉为奇数时找其中位数,为偶数时找其中位数中较大的那个),划分后的处理与RandomizedSelect算法相同。

举例

源代码
#include<iostream>
#include<cstdio>
#include<random>using namespace std;int n, k, len;//选择排序
void SelectSort(int a[], int p, int r);
//将x作为基准数将数组分割,返回x的位置
int Partition(int a[], int p, int r, int x);
//交换两个元素
void Swap(int &a, int &b);
//找每组的中位数,返回中位数的位置i
int SearchMid(int a[], int p, int r);
//线性划分
int Select(int a[], int p, int r, int k);int main()
{//输入数组长度ncin>>n;//输入数组int *a = new int[n];for (int i = 0; i < n; ++i){cin>>a[i];}//输入第k小cin>>k;//找第k小并返回int res = Select(a, 0, n - 1, k);cout<<res<<endl;delete []a;return 0;
}void SelectSort(int a[], int p, int r)
{for (int i = p; i < r; ++i){int index = i;for (int j = i + 1; j <= r; ++j){if (a[j] < a[index]){index = j;}}Swap(a[i], a[index]);}
}int Partition(int a[], int p, int r, int x)
{//i指向首元素的前一个位置,j指向尾元素的后一个位置int i = p - 1, j = r + 1;while (1){//i从基准数右边的元素开始找,直到找到第一个大于等于基准数的元素while (a[++i] < x && i < r);//j从尾元素开始找,直到找到第一个小于等于基准数的元素while (a[--j] > x && j > p);//若i>=j,说明基准数的位置已找到,为jif (i >= j){break;}//交换两个元素,使得基准数左边的数均不大于它,右边的数均不小于它Swap(a[i], a[j]);}//返回基准数的位置return j;
}void Swap(int &a, int &b)
{int temp;temp = a;a = b;b = temp;
}int SearchMid(int a[], int p, int r)
{//建立与数组a同等大小的数组bint *b = new int[r - p + 1];//用数组b存放数组a(注意此时b的首地址为0,而a的首地址为p)for (int i = p; i <= r; ++i){b[i - p] = a[i];}//将数组b排序,b[(r-p+1)/2]为中位数SelectSort(b, 0, r - p);for (int i = p; i <= r; ++i){if (a[i] == b[(r - p + 1) / 2]){return i;}}delete []b;return 0;
}int Select(int a[], int p, int r, int k)
{if (r - p < 5){SelectSort(a, p, r);return a[p + k - 1];}//分成n/5组,每组5个,找到每组的中位数并将它放到数组首元素的位置for (int i = 0; i <= (r - p - 4) / 5; ++i){int mid = SearchMid(a, p + 5 * i, p + 5 * i + 4);Swap(a[mid], a[p + i]);}//找到各组中位数的中位数int x = Select(a, p, p + (r - p - 4) / 5, (r - p - 4) / 10 + 1);//按照中位数划分int i = Partition(a, p, r, x);//求较小数数组的长度len = i - p + 1;//若较小数数组的长度小于等于k,说明第k小的元素在这个数组内,将其递归if (k <= len){return Select(a, p, i, k);}//否则,说明第k小的元素在较大数数组,将其递归else{return Select(a, i + 1, r, k - len);}
}

(三)总结

复杂度分析

1.RandomizedSelect算法

对于RandomizedSelect算法,该算法划分的基准是随机的,最好情况下的时间复杂度为O(n)\Omicron(n)O(n),而最坏情况下的时间复杂度为O(n2)\Omicron(n^2)O(n2),但这种情况在随机划分的过程中发生的的概率极小,因此RandomizedSelect算法的平均时间复杂度为O(n)\Omicron(n)O(n)。

2.Select算法

对于Select算法,该算法划分的基准是固定的,若能在线性时间内找到一个划分基准,使得按这个基准划分的两个子数组长度均至少为原数组长度的 倍(0<ε\varepsilonε<1),那么在最坏情况下,Select算法的时间复杂度也为O(n)\Omicron(n)O(n)。

例如:当ε=910\varepsilon=\frac{9}{10}ε=109​时,算法递归调用产生的2个子数组的长度至少缩短110\frac{1}{10}101​。因此,在最坏情况下,算法所需的计算时间T(n)T(n)T(n)满足T(n)≤T(9n10)+O(n)T(n)\leq T(\frac{9n}{10})+\Omicron(n)T(n)≤T(109n​)+O(n),解得T(n)=O(n)T(n)=\Omicron(n)T(n)=O(n)

在线性时间内找到一个划分基准,使得按这个基准划分的两个子数组长度均至少为原数组长度的ε\varepsilonε倍(0<ε\varepsilonε<1),那么在最坏情况下,Select算法的时间复杂度也为O(n)\Omicron(n)O(n)。

例如:当ε=910\varepsilon=\frac{9}{10}ε=109​时,算法递归调用产生的2个子数组的长度至少缩短110\frac{1}{10}101​。因此,在最坏情况下,算法所需的计算时间T(n)T(n)T(n)满足T(n)≤T(9n10)+O(n)T(n)\leq T(\frac{9n}{10})+\Omicron(n)T(n)≤T(109n​)+O(n),解得T(n)=O(n)T(n)=\Omicron(n)T(n)=O(n)

算法分析与设计-线性时间选择详解(通俗易懂,含图解,附源码)(c++)相关推荐

  1. ADSP-21489的开发详解:SPIflash的硬件设计及程序烧写详解(含Flash驱动源码)

    硬件准备 ADSP-21489EVB:ADI 21489处理器的开发板 AD-HP530ICE:ADI DSP专用仿真器 USBi:ADI SigmaDSP和SHARC DSP的图形化编程调试器 软件 ...

  2. Android APP:Preference使用详解和实例(附源码)

    Android APP:Preference使用详解和实例 一.Preference 是Android app中重要的控件之一,Settings 模块大部分都是通过Preference 实现的,这里将 ...

  3. C++文件操作详解,实用文件辅助类源码分享,建议收藏自用!

    学习C++的小伙伴,应该会经常遇见对文件进行操作的需求,例如读写文件,作为一个使用频率较高的操作,我们每次重复地编写代码,就是浪费劳动力了,所以作者将自己常用的文件操作封装成了一个类,需要的小伙伴自取 ...

  4. AIDL在Telephony中的应用 —— ITelephony 详解 (以Android 9.0源码讲解)

    转载请注明出处:https://blog.csdn.net/turtlejj/article/details/84861020,谢谢- Telephony模块中大量的使用了AIDL,但网上却很少有文章 ...

  5. 一分钟详解PCL-1.8.1从源码搭建开发环境四(VTK库的编译)

  6. java tomcat源码_详解Tomcat系列(一)-从源码分析Tomcat的启动

    在整个Tomcat系列文章讲解之前, 我想说的是虽然整个Tomcat体系比较复杂, 但是Tomcat中的代码并不难读, 只要认真花点功夫, 一定能啃下来. 由于篇幅的原因, 很难把Tomcat所有的知 ...

  7. 【异步编程学习笔记】JDK中的FutureTask和CompletableFuture详解(使用示例、源码)

    文章目录 FutureTask概述 使用实例 类图结构 FutureTask的run()方法 FutureTask的局限性 CompletableFuture概述 CompletableFuture代 ...

  8. 【Java入门提高篇】Day26 Java容器类详解(八)HashSet源码分析

    前面花了好几篇的篇幅把HashMap里里外外说了个遍,大家可能对于源码分析篇已经讳莫如深了.别慌别慌,这一篇来说说集合框架里最偷懒的一个家伙--HashSet,为什么说它是最偷懒的呢,先留个悬念,看完 ...

  9. word2vec 中的数学原理详解(六)若干源码细节

    word2vec 是 Google 于 2013 年开源推出的一个用于获取 word vector 的工具包,它简单.高效,因此引起了很多人的关注.由于 word2vec 的作者 Tomas Miko ...

  10. java toast_详解Android中的Toast源码

    Toast源码实现 Toast入口    我们在应用中使用Toast提示的时候,一般都是一行简单的代码调用,如下所示: [java] view plaincopyprint?在CODE上查看代码片派生 ...

最新文章

  1. TVM量化路线图roadmap
  2. Imagine,is real crazy!
  3. java layout_java - 以编程方式在LinearLayout中设置边距
  4. 微信公众平台----带参数二维码生成和扫描事件
  5. Spring-视图解析
  6. SAP UI5 subscribe event实现原理
  7. linux如何添加默认路由表_linux 添加静态路由
  8. django表格_Django表格
  9. Android studio 如何导入并引用Library工程
  10. ELK 性能(1) — Logstash 性能及其替代方案
  11. 英语计算机简历模板,计算机研究生英文简历模板
  12. python 主函数传参_Python函数传参方法超级大汇总
  13. Python 每日一记31相关性矩阵建立
  14. 百分百解决python manage.py makemigrations没有反应
  15. PMP项目管理-项目成本管理(3)
  16. 联阳IT6561方案设计|替代IT6561方案DP转HDMI转换|CS5218替代IT6561芯片设计
  17. Cobalt Strike远控木马分析
  18. 【非常简单bug管理工具-TAPD 】
  19. 学习华为认证HCIA,HCIP,HCIE分别需要多长时间呢?
  20. 2022济南大学acm新生赛题解

热门文章

  1. dsoframer java_(二)   内嵌WORD/OFFICE的WINFORM程序——DSOFRAMER使用小结
  2. abortonerror_错误:无法解决:com.android.support:appcompat-v7:24.0.0
  3. Java自动化测试系列[v1.0.1][PO设计模式]
  4. android 5.1声道,加入5.1声道音效非常强大_三星 I699(GALAXY Trend/电信版)_手机Android频道-中关村在线...
  5. Docker修改MySQL默认端口
  6. DbUtil的介绍使用
  7. 【工具】telnet用法
  8. linux虚拟机rzsz安装(wget方法,自测可用)
  9. 一文读懂蓝牙低功耗BLE的应用市场
  10. Arcgis使用教程(八)地图文档(.Mxd)的使用方法详解