问题描述

 给定线性序集中n个元素和一个整数k,1≤k≤n,要求找出这n个元素中第k小的元素。在线性时间内O(n)

  • k=1; 最小元素 O(n)
  • k=n; 最大元素 O(n)
  • k=(n+1)/2: 中位数 O(n)?

算法思想

 线性时间选择问题的分治法:模仿快速排序算法,找第k小元素

思想:对输入数组递归划分,但仅对划分出的子数组之一进行递归处理。

主要算法

template<class Type>
Type RandomizedSelect(Type a[ ], int p, int r, int k)
{if (p==r) return a[p];//一次快速排序,随机选择基准元素,划分数组int i=RandomizedPartition(a,p,r), j=i-p+1;        // j为a[p,i]中元素个数if (k<=j) return RandomizedSelect(a,p,i,k);//返回第k-j小元素else return RandomizedSelect(a,i+1,r,k-j);
}

小例子

 有一个数组:{ 4, 2, 5, 7, 4, 9, 6, 21 }

复杂度分析

平均时间复杂度

        

 可以证明,算法randomizedSelect可以在O(n)平均时间内找出n个输入元素中的第k小元素

最坏时间复杂度

 在最坏情况下(找最小,但总在最大处划分),算法randomizedSelect需要O(n2)计算时间。
      

        

完整算法

//2d9-1 随机划分线性时间选择
#include "stdafx.h"
#include <iostream>
#include <ctime>
using namespace std; int a[] = {5,7,3,4,8,6,9,1,2};template <class Type>void Swap(Type &x,Type &y);inline int Random(int x, int y);template <class Type>int Partition(Type a[],int p,int r);template<class Type>int RandomizedPartition(Type a[],int p,int r);template <class Type>Type RandomizedSelect(Type a[],int p,int r,int k);int main()
{for(int i=0; i<9; i++){cout<<a[i]<<" ";}cout<<endl;cout<<RandomizedSelect(a,0,8,3)<<endl;
}template <class Type>void Swap(Type &x,Type &y)
{Type temp = x;x = y;y = temp;
}inline int Random(int x, int y)
{srand((unsigned)time(0));int ran_num = rand() % (y - x) + x;return ran_num;
}template <class Type>int Partition(Type a[],int p,int r)
{int i = p,j = r + 1;Type x = a[p];while(true){while(a[++i]<x && i<r);while(a[--j]>x);if(i>=j){break;}Swap(a[i],a[j]);}a[p] = a[j];a[j] = x;return j;
}template<class Type>int RandomizedPartition(Type a[],int p,int r)
{int i = Random(p,r);Swap(a[i],a[p]);return Partition(a,p,r);
}template <class Type>Type RandomizedSelect(Type a[],int p,int r,int k)
{if(p == r){return a[p];}int i = RandomizedPartition(a,p,r);int j = i - p + 1;if(k <= j){return RandomizedSelect(a,p,i,k);}else{//由于已知道子数组a[p:i]中的元素均小于要找的第k小元素//因此,要找的a[p:r]中第k小元素是a[i+1:r]中第k-j小元素。return RandomizedSelect(a,i+1,r,k-j);}
}

别急!这个算法有很大的缺陷,接下来我们来解决一下最坏时间复杂度的问题

最坏时间复杂度的问题思考

 若能在O(n)内找到一个划分基准,使得所划分的2个子数组长度,都至少为原数组长度的ε倍(0<ε <1 ),则在最坏情况下用O(n)时间完成选择任务。

 例如,若ε=9/10,算法递归调用所产生的子数组的长度至少缩短1/10。所以,在最坏情况下,算法所需的计算时间T(n)满足递归式:
           T(n)≤T(9n/10)+O(n)

           由此可得T(n)=O(n)

 是不是一下子就把最坏时间复杂度从O(n2)变成了O(n)。接下来的问题就是如何寻找划分基准?

寻找划分基准-O(n)算法

基本思想

  • 定义查找第k小元素算法为 Select(Type a[], int p, int r, int k)
  • 将n个输入元素划分成⌈n/5⌉\lceil n/5 \rceil⌈n/5⌉个组,每组5个元素,只可能有一个 组不是5个元素。
  • 用任意一种排序算法,将每组中的元素排好序,并取出每组的中位数,共⌈n/5⌉\lceil n/5 \rceil⌈n/5⌉个。
  • 递归调用算法select来对⌈n/5⌉\lceil n/5 \rceil⌈n/5⌉个组按照中位数排序,同时,找 出这⌈n/5⌉\lceil n/5 \rceil⌈n/5⌉个中位数元素的中位数
  • 如果⌈n/5⌉\lceil n/5 \rceil⌈n/5⌉是偶数,就找它的2个中位数中较大的一个。以这个 元素作为划分基准。

将中位数的中位数x,作为基准元素

      

简单划分案列

计算大于和小于基准X的元素数目

 组中除了中位数,小于基准x的元素个数有:

中位数中小于基准x的个数为:


 因此,小于基准x的元素个数至少为:

 同理,大于基准x的元素个数至少为:

红框表示的是一定小于基准X的元素
蓝框表示的是一定大于基准X的元素
两者的值理论上是相等的

要注意一下n≥75的情况

  当n≥75时,3(n-5)/10≥n/4。所以,按此基准划分所得的2个子数组的长度都至少缩短1/4

主要算法

Type Select(Type a[], int p, int r, int k)
{   if (r-p<75) {//用某个简单排序算法对数组a[p:r]排序;return a[p+k-1];};for ( int i = 0;  i<=(r-p-4)/5;  i++ ) // i代表组数{//将元素每5个分成一组,分别排序BubbleSort(a, p+5*i, p+5*i+4); //将该组中位数与a[p+i]交换位置,即: 将a[p+5*i] 至a[p+5*i+4]的第3小元素与a[p+i]交换位置;Swap(a[p+5*i+2], a[p+i]); }  //  O(n)//找中位数的中位数Type x = Select(a, p, p+(r-p-4)/5, (r-p-4)/10); // T(n/5)int i=Partition(a, p, r,  x), //  O(n)j=i-p+1;if (k<=j) return Select(a, p, i, k);else return Select(a,i+1,r,k-j);
}

复杂度分析

设找第K小元素的算法的时间复杂性是: T(n)

 找中位数的中位数:  T(n/5)

 调用快速排序partition函数对每个数组进行排序:  O(n):

 按照上述方法所选的基准x进行划分,得到的两个子数组分别至多有3n/4个元素:T(3n/4)

     

算法

//中位数线性时间选择
#include <ctime>
#include<stdlib.h>
#include <iostream>
#include<algorithm>
using namespace std;template <class Type>
void Swap(Type &x,Type &y);inline int Random(int x, int y);template <class Type>
void BubbleSort(Type a[],int p,int r);template <class Type>
int Partition(Type a[],int p,int r,Type x);template <class Type>
Type Select(Type a[],int p,int r,int k);int main()
{//初始化数组int a[100];//数字索引int k;//必须放在循环体外面srand((unsigned)time(0));for(int i=0; i<100; i++){a[i] = Random(0,500);cout<<"a["<<i<<"]:"<<a[i]<<" ";}cout<<endl;cout<<"请输入您要获取的数字索引(从1开始):";cin>>k;cout<<"第"<<k<<"个元素是"<<Select(a,0,100,k)<<endl;//重新排序,对比结果BubbleSort(a,0,100);for(int i=0; i<100; i++){cout<<"a["<<i<<"]:"<<a[i]<<" ";}cout<<endl;
}template <class Type>
void Swap(Type &x,Type &y)
{Type temp = x;x = y;y = temp;
}inline int Random(int x, int y)
{int ran_num = rand() % (y - x) + x;return ran_num;
}//冒泡排序
template <class Type>
void BubbleSort(Type a[],int p,int r)
{//记录一次遍历中是否有元素的交换bool exchange;for(int i=p; i<r-1;i++){exchange = false ;for(int j=0; j<r-1-i; j++){if(a[j]>a[j+1]){Swap(a[j],a[j+1]);exchange = true;}}//如果这次遍历没有元素的交换,那么排序结束if(false == exchange){break ;}}}template <class Type>
int Partition(Type a[],int p,int r,Type x)
{int i = p-1,j = r ;while(true){while(a[++i]<x && i<r);while(a[--j]>x);if(i>=j){break;}Swap(a[i],a[j]);}return j;
}template <class Type>
Type Select(Type a[],int p,int r,int k)
{if(r-p<75){//BubbleSort(a,p,r);sort(a + p, a + r);return a[p+k-1];}for(int i=0; i<=(r-p-4)/5; i++){//将元素每5个分成一组,分别排序,并将该组中位数与a[p+i]交换位置//使所有中位数都排列在数组最左侧,以便进一步查找中位数的中位数//BubbleSort(a,p+5*i,p+5*i+4);sort(a+p+i*5,a+p+5*i+4);Swap(a[p+5*i+2],a[p+i]);}//找中位数的中位数Type x = Select(a,p,p+(r-p-4)/5,(r-p-4)/10);int i = Partition(a,p,r,x);int j = i-p+1;if(k<=j){return Select(a,p,i,k);}else{return Select(a,i+1,r,k-j);}}

该代码还是有一些小bug,输出的值有时候有一些偏差,并且用冒泡算法代替sort()也有一些小问题。暂时不想去改了,以后有时间再看吧。

测试用例

随机产生的100个数据
a[0]:402 a[1]:436 a[2]:336 a[3]:309 a[4]:130 a[5]:154 a[6]:348 a[7]:96 a[8]:141 a[9]:168 a[10]:375 a[11]:159 a[12]:253 a[13]:269 a[14]:137 a[15]:228 a[16]:254 a[17]:385 a[18]:301 a[19]:185 a[20]:169 a[21]:48 a[22]:472 a[23]:131 a[24]:353 a[25]:457 a[26]:360 a[27]:315 a[28]:211 a[29]:278 a[30]:395 a[31]:430 a[32]:489 a[33]:296 a[34]:108 a[35]:489 a[36]:255 a[37]:78 a[38]:433 a[39]:320 a[40]:370 a[41]:213 a[42]:53 a[43]:319 a[44]:469 a[45]:294 a[46]:444 a[47]:63 a[48]:101 a[49]:351 a[50]:89 a[51]:270 a[52]:151 a[53]:306 a[54]:171 a[55]:5 a[56]:159 a[57]:164 a[58]:215 a[59]:472 a[60]:103 a[61]:78 a[62]:405 a[63]:499 a[64]:297 a[65]:314 a[66]:129 a[67]:296 a[68]:262 a[69]:27 a[70]:32 a[71]:386 a[72]:175 a[73]:136 a[74]:479 a[75]:424 a[76]:234 a[77]:434 a[78]:159 a[79]:100 a[80]:485 a[81]:397 a[82]:341 a[83]:82 a[84]:348 a[85]:234 a[86]:12 a[87]:351 a[88]:277 a[89]:486 a[90]:365 a[91]:303 a[92]:151 a[93]:381 a[94]:291 a[95]:115 a[96]:243 a[97]:324 a[98]:279 a[99]:319

输入
请输入您要获取的数字索引(从1开始):25

输出
第25个元素是151

排序好的数组
a[0]:5 a[1]:12 a[2]:27 a[3]:32 a[4]:48 a[5]:53 a[6]:63 a[7]:78 a[8]:78 a[9]:82 a[10]:89 a[11]:96 a[12]:100 a[13]:101 a[14]:103 a[15]:108 a[16]:115 a[17]:129 a[18]:130 a[19]:131 a[20]:136 a[21]:137 a[22]:141 a[23]:151 a[24]:151 a[25]:154 a[26]:159 a[27]:159 a[28]:159 a[29]:164 a[30]:168 a[31]:169 a[32]:171 a[33]:175 a[34]:185 a[35]:211 a[36]:213 a[37]:215 a[38]:228 a[39]:234 a[40]:234 a[41]:243 a[42]:253 a[43]:254 a[44]:255 a[45]:262 a[46]:269 a[47]:270 a[48]:277 a[49]:278 a[50]:279 a[51]:291 a[52]:294 a[53]:296 a[54]:296 a[55]:297 a[56]:301 a[57]:303 a[58]:306 a[59]:309 a[60]:314 a[61]:315 a[62]:319 a[63]:319 a[64]:320 a[65]:324 a[66]:336 a[67]:341 a[68]:348 a[69]:348 a[70]:351 a[71]:351 a[72]:353 a[73]:360 a[74]:365 a[75]:370 a[76]:375 a[77]:381 a[78]:385 a[79]:386 a[80]:395 a[81]:397 a[82]:402 a[83]:405 a[84]:424 a[85]:430 a[86]:433 a[87]:434 a[88]:436 a[89]:444 a[90]:457 a[91]:469 a[92]:472 a[93]:472 a[94]:479 a[95]:485 a[96]:486 a[97]:489 a[98]:489 a[99]:499

线性时间选择-分治算法相关推荐

  1. 算法设计与分析——递归与分治策略——线性时间选择

    顾名思义:这篇文章讲解的就是如果用线性时间算法来作出元素选择问题. 问题描述:给定线性序集中n个元素和一个整数k,1<=k<=n.要求找出这n个元素中第k小的元素,即如果将这个n个元素依其 ...

  2. 线性时间选择求第k小数(分治)

    目录 线性时间选择 优化 对划分进行随机数改良 取中位数进行划分 全部代码 线性时间选择 问题描述:给定线性序集中 n 个元素和一个整数 k(1 <= k <= n),要求找出着 n 个元 ...

  3. 【分治】线性时间选择(C++)

    一.线性时间选择 1. 问题描述 给定线性序集中n个元素和一个整数k,1≤k≤n,要求找出这n个元素中第k小的元素. 2. 线性时间选择-随机划分 算法步骤: 生成1个随机数 i ,将数组a[p:r] ...

  4. 算法笔记——【分治法】线性时间选择

    线性时间选择问题:给定线性序集中n个元素和一个整数k,1≤k≤n,要求找出这n个元素中第k小的元素,(这里给定的线性集是无序的). 随机划分线性选择 线性时间选择随机划分法可以模仿随机化快速排序算法设 ...

  5. 三十三、分治算法---汉诺塔问题

    一.分治算法的介绍 分治法是一种很重要的算法.字面上的解释是"分而治之",就是把一个复杂的问题分成两个或更多的相同或 相似的子问题,再把子问题分成更小的子问题--直到最后子问题可以 ...

  6. 「五大常用算法」一文图解分治算法和思想

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:硬刚一周,3W字总结,一年的经验告诉你如何准备校招! 个人原创100W+访问量博客:点击前往,查看更多 前言 分 ...

  7. 常用算法一(分治算法)

    一.基本概念 在计算机科学中,分治法是一种很重要的算法.字面上的解释是"分而治之",就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题--直到最后子 ...

  8. C语言分治算法求中位数,【算法复习】分治算法

    Outline 分治思想和递归表达式 大整数乘法 矩阵乘法的Strassen算法 快速傅里叶变化 基于分治的排序 merge-sort排序 快速排序 排序的下界问题 中位数和顺序统计量 最邻近点对 凸 ...

  9. 五大常用算法——分治算法详解及经典例题

    一.基本概念 在计算机科学中,分治法是一种很重要的算法.字面上的解释是"分而治之",就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题--直到最后子 ...

最新文章

  1. A novel evolutionary model for constructing gene coexpression networks with comprehensive features
  2. numpy 数组 维度 大小 形状
  3. 全领域涨点 | Evolving Attention在CV与NLP领域全面涨点
  4. 导入jar包和创建jar文件
  5. 玛塔留言板无刷新留言板程序
  6. 技术宅的乐趣!网友编写出《流浪地球》“春节十二响”C语言源码
  7. 学校如何搭建文件服务器,学校服务器搭建
  8. 修改路由器mac地址_你知道吗:路由器转发报文时,会剥掉MAC地址,重新封装
  9. Yii2学习笔记002---Yii2的控制器和视图
  10. LaTeX 参考文献的排版与引用
  11. visio画图小技巧记录
  12. TB6612FNG 驱动学习笔记
  13. DDoS 攻击次数和攻击流量
  14. 网站被黑总被跳转到其他网站的处理解决过程
  15. RationalDMIS基于CAD的编程测量
  16. 跳跃表的原理以及实现
  17. 监控日志loging Elasticsearch(性能优化八)
  18. 【Python】PyQt5入门
  19. Git 工作流的一些经验分享
  20. 分享上海seo统计的seo基础知识

热门文章

  1. java quartz管理,SpringBoot中使用Quartz管理定时任务的方法
  2. 大学四年,总结一套适合小白的Java自学路线和方法
  3. 集成华为DTM为您轻松实现管理广告投放效果
  4. linux系统做gost镜像,linux下如何像ghost一样备份系统
  5. 【Fortran】Sublime text 中设置Fortran语法常亮以及自动补全
  6. redis安装,救救孩子吧
  7. 【redis篇】超详细的redis安装教程,学不会你找我
  8. Android Studio配置ArcGIS Runtime SDK for Android
  9. 键盘中按键对应的键码值
  10. 中国计算机信息系统集成行业协会:信息系统集成企业应该拥有哪些资质呢?