最坏情况线性时间选择O(n)

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

思路】:如果能在线性时间内找到一个划分基准,使得按这个基准所划分出的2个子数组的长度都至少为原数组长度的ε倍(0<ε<1是某个正常数),那么就可以在最坏情况下用O(n)时间完成选择任务。
例如:若ε=9/10,算法递归调用所产生的子数组的长度至少缩短1/10。所以,在最坏情况下,算法所需的计算时间T(n)满足递归式T(n)≤T(9n/10)+O(n) 。由此可得T(n)=O(n)。

具体解题】:这里我们将所有的数(n个),以每5个划分为一组,共[n/5]组(将不足五个的那组忽略);然后用任意一种排序算法(因为只对五个数进行排序,所以任取一种排序法就可以了,这里我选用冒泡排序),将每组中的元素排好序再分别取每组的中位数,得到[n/5]个中位数;再取这[n/5]个中位数的中位数(如果n/5是偶数,就找它的2个中位数中较大的一个)作为划分基准,将全部的数划分为两个部分,小于基准的在左边,大于等于基准的放右边。
    在这种情况下,找出的基准x至少比3(n-5)/10个元素大,因为在每一组中有2个元素小于本组的中位数,中位数处于1/2*[n/5-1],即n/5个中位数中又有(n-5)/10个小于基准x。同理,基准x也至少比3(n-5)/10个元素小。而当n≥75时,3(n-5)/10≥n/4所以按此基准划分所得的2个子数组的长度都至少缩短1/4。
程序代码如下:

#include<iostream.h>

#include<stdlib.h>

#include<time.h>

#define MAX_VALUE 10000

#define random() rand()%MAX_VALUE

#define N 10000

int a[N];

class Find

{

public:

       void bubble(int first,int end) //冒泡排序

       {    

for(int flag=first;flag<end;flag++)

                     for(int i=end;i>flag;i--)

                            if(a[i]<a[i-1])

                            {     int t=a[i];

                                   a[i]=a[i-1];

                                   a[i-1]=t;

                            }

       }

       int partition(int p,int r,int x) //数组a中从a[p]a[r]的元素按照x划分,大于x的在左边,小于x的在右边

       { 

 int i,j;

              for(i=p,j=r;i<j;i++)

              {    

if(a[i]>x)

                     {    

while(i<j&&a[j]>x)

                                   j--;

                            if(i!=j){

                                   int t=a[i];

                                   a[i]=a[j];

                                   a[j]=t;

                                   j--;

                            }

                     }

              }

              return i-1;

       }

       int select(int p,int r,int k)   //寻找中位数

       {

              if(r-p<5){

                     bubble(p,r);

                     return a[p+k-1];

              }

              for(int i=0;i<(r-p-4)/5;i++)

              {

                     int s=p+5*i,t=s+4;

                     bubble(s,t);

                     int temp=a[p+i];

                     a[p+i]=a[s+2];

                     a[s+2]=temp;

              }

              int x=select(p,p+(r-p-4)/5,(r-p+6)/10);

              i=partition(p,r,x);

              int j=i-p+1;

              if(k<=j)

                     return select(p,i,k);

              else

                     return select(i+1,r,k-j);

       }

};

void main()

{

       clock_t   start,end;  

       double   elapsed;

       srand((int)time(NULL));

       for(int k=0;k<N;k++)

       {

              a[k]=random();

              cout<<a[k]<<"/t";

       }

       cout<<endl;

       start=clock();

       Find f;    

       int n=5000;

       cout<<"The No."<<n<<" is :"<<f.select(0,N-1,n)<<endl;

       end=clock();

       elapsed=((double)(end-start));///CLOCKS_PER_SEC;

       cout<<"Time: "<<elapsed<<endl;

}

这个题目关键在寻找划分基准,从而提高寻找效率,时间复杂度为o(n);
本文固定链接为:

### 关于最坏情况 O(n) 时间复杂度的选择算法 #### 选择算法概述 选择算法是一种用于查找数组中第 k 小元素的高效方法。一种常见的实现方式是基于快速排序的思想——分治法,称为 **Quickselect** 算法。然而,标准 Quickselect 的最坏时间复杂度为 \(O(n^2)\)[^1],因为它依赖于每次分区操作的结果。 为了改进这一点并确保最坏情况下仍能达到线性时间复杂度 \(O(n)\),可以采用 **Median of Medians** 方法作为优化策略[^4]。这种方法通过更精确地选取划分点(pivot),使得每一步都能减少一定比例的数据规模,从而达到严格的线性时间复杂度。 --- #### Median of Medians 方法的核心思想 Median of Medians 是一种确定性算法,其目标是从给定数组中选出一个接近中间位置的数作为 pivot,以保证分割后的子问题大小不会过大或过小。具体过程如下: 1. **分组**: 将输入数组分成若干小组,通常每组包含 5 个元素。 2. **求中位数**: 对每一小组分别找出其中位数。 3. **递归找中位数**: 使用同样的方法对所有中位数组成的新数组继续寻找中位数,直到剩下少量元素可以直接处理为止。 4. **选定 Pivot**: 找到最终的中位数作为全局 pivot。 5. **分区与递归**: 利用此 pivot 进行分区,并针对较小部分或较大部分递归调用选择算法。 由于上述步骤中的 pivot 更加平衡,因此可以证明整个算法时间复杂度为 \(O(n)\)。 --- #### 实现代码示例 以下是 Python 中的一个简单实现,展示了如何利用 Median of Medians 来构建具有严格 \(O(n)\) 复杂度的选择算法: ```python def median_of_medians(arr, i): """ 寻找数组arr中的第i小元素 (0-based index) :param arr: 输入数组 :param i: 需要找到的位置索引 :return: 第i小的元素 """ def partition(x_arr, pivot_x_index): """根据指定的pivot重新排列数组""" pivot_value = x_arr[pivot_x_index] # 将pivot移到最后 x_arr[-1], x_arr[pivot_x_index] = x_arr[pivot_x_index], x_arr[-1] store_idx = 0 for j in range(len(x_arr)-1): if x_arr[j] < pivot_value: x_arr[store_idx], x_arr[j] = x_arr[j], x_arr[store_idx] store_idx += 1 # 把pivot放到正确的位置 x_arr[-1], x_arr[store_idx] = x_arr[store_idx], x_arr[-1] return store_idx def select_recursion(x_arr, stat_i): """递归执行选择算法""" if len(x_arr) == 1: assert(stat_i == 0) return x_arr[0] medians = [] sublists = [x_arr[k:k+5] for k in range(0, len(x_arr), 5)] for sublist in sublists: sorted_sublist = sorted(sublist) medians.append(sorted_sublist[len(sublist)//2]) pivot = select_recursion(medians, len(medians)//2) # 获取pivot在原数组中的index pivot_idx = None for idx, val in enumerate(x_arr): if val == pivot: pivot_idx = idx break pvt_idx = partition(x_arr, pivot_idx) if stat_i == pvt_idx: return x_arr[stat_i] elif stat_i < pvt_idx: return select_recursion(x_arr[:pvt_idx], stat_i) else: return select_recursion(x_arr[pvt_idx+1:], stat_i-pvt_idx-1) return select_recursion(arr.copy(), i) ``` --- #### 性能分析 对于任意输入序列,该算法始终能够在最坏情况下保持 \(O(n)\)时间复杂度。这是因为 Median of Medians 提供了一个良好的 pivot 选择机制,使每次分区后剩余待解决的部分不超过原始长度的约 \(\frac{7}{10}\)。由此可得总工作量构成几何级数收敛的形式,即总体复杂度为线性级别。 尽管如此,实际应用中可能更倾向于随机化版本的 Quickselect,因其常数因子较低且平均表现优异;但在需要绝对保障时,Median of Medians 显示出了无可比拟的优势。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值