zoukankan      html  css  js  c++  java
  • 最坏情况为线性时间的选择算法

    求给定输入中第k大的数的算法。

    这是一个常见面试题,通常的解法也很明显,使用类似快排的思想。

    每趟运行,把数组的值分成两部分,一部分比pivot大,一部分比pivot小,因为我们知道pivot在数组中的位置,所以比较k和pivot的位置就知道第k大的值在哪个范围,我们不断的进行recursion, 直到pivot就是第k大的值。

    这个算法的时间预期是O(n)。这里需要注意的是讲的仅限于它的预期,对于这个算法,其在最差情况下,时间复杂度则为n的平法。

    参阅快速排序的无敌对手一文,我们是可以构建出一个这样的序列的。最简单的情况,每趟快排的时候我们以第一个为主元,那么对于一个已经排序好的序列,我们要找最大的数,最后的时间花费就退化成了n的平方。

     

    《算法导论》9.3章给出了一个最差情况也为线性O(n)的算法。

     Step 1:把数组划分为若干个子数组,每个子数组里包含5个数,因为会有无法整除的可能,所以最后一个子数组会小于5.

    Step 2:用插入排序把这5个数排序,然后找出中位数,也就是第3个。

    Step 3:把获得的中位数又排序(这个地方错误,不是排序,应该递归调用SELECT),找出中位数的中位数x(如果有偶数个中位数,为了方便,约定x是较小的中位数)。

    Step 4:把原来的数组使用类似快排的方法,分成两个部分。让k比划分的低区中的元素数目多1,因此x是第k小元素,并且有n-k个元素在划分的高区.

    Step 5:如果i =k,返回x。如果 i < k, 则在低区递归调用来找出第i小的元素.如果i> k,则在高区递归查找第i- k小的元素.

     

    整个过程中,第1,2,4步所需时间为O(n), 注意第2步的复杂度不为O(n^2),第3步的复杂度为 T(n/5),第五步的复杂度为 T(7n/10)。

     

    注意这里第2步虽然我们使用的是插入排序,但是待排的序列长度为常数5,所以对一组的排序时间花费为O(1),对于n/5个组,其时间预期是O(n/5),即O(n)。

     

    时间预期为:

     

             T(n) <= T( n/5 ) + T(7n/10+6) + O(n)

     

    (书中通过数学方法最后推得时间预期是O(n)。因为需要较多的数学准备知识,这里不继续介绍。) 

     

    在这章的习题中,基于这个算法,要求证明原先Step 1中划分为每组3个和7个的情况的复杂度。7个的情况证明结果和5是一样的。但是对于3的情况,其结果最后可以证明出复杂度并非O(n)。

     

     

    尝试证明关键步骤如下:

     

    对于划分为3个元素的情况,可以得到递推式(过程略):

     

             T(n) <= T( n/3 ) + T(2n/3+4) + O(n)

     

    假设存在某个适当大的常数c,使得T(n)<=cn(为什么这样可查阅《算法导论》第一章),用an替代O(n)(因为O(n)代表的这部分的时间花费是线性的,那么必然存在一个常数a,使得an为这部分时间花费)用cn代换掉式中的T(n)那么有:

     

    T(n)<= c(n/3) + c(2n/3+4) + an <= cn/3 + c + 2cn/3 + 4c + O(n)= cn + 5c + an

     

    根据假设,T(n)的最大值是cn,那么又有:

     

             cn + 5c + an <= cn

     

            5c + an <=0

     

    显然又 a, n > 0,那么欲使等式成立,必有c<=0。与我们假设的矛盾。所以我们的假设不成立。

     

    因此,当我们尝试用3划分的时候,该算法的无法在线性复杂度内运行。 

     

    这个算法的实现代码比较复杂。对于每组划分5个元素的情况, 实现代码如下(该代码输出的是第i大的元素,上面的解释是输出第i小的元素):

      1 #include <stdlib.h>
      2 #include <stdio.h>
      3 #define swap(a,b) (a)^=(b);(b)^=(a);(a)^=(b)
      4 #define MAX 1000
      5 
      6 void sort(int* input, int size){
      7     printf ( "sort arry size = %d
    ", size );
      8     int i,j;
      9     for(i = 0; i< size ; i++){
     10         for(j = 0; j<size-i-1;j++){
     11             if(input[j]<input[j+1]){
     12                 swap(input[j],input[j+1]);
     13             } 
     14         }
     15     }
     16 }
     17 void output(int * input, int size){
     18     for(;size>0 && *input;size--,input++){
     19         printf("%d ", *input);
     20     }
     21     printf("
    ");
     22 
     23 }
     24 
     25 int partion(int *input, int size, int key){
     26     printf ( "--------------Step4---------------
    " );
     27     printf("key = %d 
    ", input[key]);
     28     int *head, *tail;
     29     head = input;
     30     tail = head + size - 1;
     31     swap(*head, input[key]);
     32 
     33     int *k = head;
     34     while(head<tail){
     35         while(*tail && *k >= *tail){
     36             tail--;
     37         }
     38         if(tail<=head) break;
     39         swap(*k,*tail);
     40         k = tail;
     41         while(*head && *k < *head)
     42             head++;
     43         if(head>=tail) break;
     44         swap(*k,*head);
     45         k = head;
     46     }
     47     output(input, size);
     48     printf ( "--------------Step4 done--------------
    " );
     49     return k-input+1;
     50 }
     51 
     52 int kselect(int *input, int size, int k){
     53     printf ( "start element : %d 
    ", *input );
     54     if(size<=5){
     55         sort(input, size);
     56         return input[k-1];
     57     }
     58     int mid[MAX] = {0};
     59     int midvalue[MAX] = {0};
     60     int groups = size/5;
     61     int i;
     62 
     63     printf ( "-----------------step 1, 2--------------
    " );
     64     for(i = 0; i<groups;i++){
     65         sort(input+i*5, (i*5+5 > size) ? (size-1):5);
     66         printf ( "sorted group %d:
    ", i );
     67         output(input+i*5, 5);
     68         mid[i] = i*5 + 2;
     69         midvalue[i] = input[i*5 + 2];
     70     }
     71 
     72     printf ( "-----------------step 1, 2 done--------------
    " );
     73 
     74     printf ( "---------step3-------------
    " );
     75     sort(midvalue, groups);
     76     printf ( "---------step3 done-------
    " );
     77     int m = -1;
     78     for(i = 0; i<5;i++){
     79         if(input[mid[i]] == midvalue[groups/2]){
     80             m = partion(input, size, mid[i]);
     81         }
     82     }
     83     if(m == k){
     84         return input[m-1];
     85     }
     86     if(k<m){
     87         return kselect(input,m,k);
     88     }
     89     else{
     90         return kselect(input+m, size - m, k-m);
     91     }
     92     return 0xffff;
     93 }
     94 
     95 int main(){
     96     int input[] = {1,3,2,10,5,11, 12, 8 ,6, 7};
         /*输出第7大的元素.*/
    97 int r = kselect(input,sizeof(input)/sizeof(int), 7); 98 printf("result %d ", r); 99 return 0; 100 }

     下面这个算法比较靠谱:

      1 #include <iostream>
      2 #include <time.h>
      3 using namespace std;
      4 
      5 const int num_array = 13;
      6 const int num_med_array = num_array / 5 + 1;
      7 int array[num_array];
      8 int midian_array[num_med_array];
      9 
     10 //冒泡排序(晚些时候将修正为插入排序)
     11 /*void insert_sort(int array[], int left, int loop_times, int compare_times)
     12 {
     13     for (int i = 0; i < loop_times; i++)
     14     {
     15         for (int j = 0; j < compare_times - i; j++)
     16         {
     17             if (array[left + j] > array[left + j + 1])
     18                 swap(array[left + j], array[left + j + 1]);
     19         }
     20     }
     21 }*/
     22 
     23 /*
     24 //插入排序算法伪代码
     25 INSERTION-SORT(A)                              cost    times
     26 1  for j ← 2 to length[A]                      c1      n
     27 2       do key ← A[j]                          c2      n - 1
     28 3          Insert A[j] into the sorted sequence A[1 ‥ j - 1].     0...n - 1
     29 4          i ← j - 1                           c4      n - 1
     30 5          while i > 0 and A[i] > key           c5      
     31 6             do A[i + 1] ← A[i]               c6      
     32 7             i ← i - 1                        c7      
     33 8          A[i + 1] ← key                      c8      n - 1
     34 */
     35 //已修正为插入排序,如下:
     36 void insert_sort(int array[], int left, int loop_times)
     37 {
     38     for (int j = left; j < left+loop_times; j++)
     39     {
     40         int key = array[j];
     41         int i = j-1;
     42         while ( i>left && array[i]>key )
     43         {
     44             array[i+1] = array[i];
     45             i--;
     46         }
     47         array[i+1] = key;
     48     }
     49 }
     50 
     51 int find_median(int array[], int left, int right)
     52 {
     53     if (left == right)
     54         return array[left];
     55     
     56     int index;
     57     for (index = left; index < right - 5; index += 5)
     58     {
     59         insert_sort(array, index, 4);
     60         int num = index - left;
     61         midian_array[num / 5] = array[index + 2];
     62     }
     63     
     64     // 处理剩余元素
     65     int remain_num = right - index + 1;
     66     if (remain_num > 0)
     67     {
     68         insert_sort(array, index, remain_num - 1);
     69         int num = index - left;
     70         midian_array[num / 5] = array[index + remain_num / 2];
     71     }
     72     
     73     int elem_aux_array = (right - left) / 5 - 1;
     74     if ((right - left) % 5 != 0)
     75         elem_aux_array++;
     76     
     77     // 如果剩余一个元素返回,否则继续递归
     78     if (elem_aux_array == 0)
     79         return midian_array[0];
     80     else
     81         return find_median(midian_array, 0, elem_aux_array);
     82 }
     83 
     84 // 寻找中位数的所在位置
     85 int find_index(int array[], int left, int right, int median)
     86 {
     87     for (int i = left; i <= right; i++)
     88     {
     89         if (array[i] == median)
     90             return i;
     91     }
     92     return -1;
     93 }
     94 
     95 int q_select(int array[], int left, int right, int k)
     96 {
     97     // 寻找中位数的中位数
     98     int median = find_median(array, left, right);
     99     
    100     // 将中位数的中位数与最右元素交换
    101     int index = find_index(array, left, right, median);
    102     swap(array[index], array[right]);
    103     
    104     int pivot = array[right];
    105     
    106     // 申请两个移动指针并初始化
    107     int i = left; 
    108     int j = right - 1;  
    109     
    110     // 根据枢纽元素的值对数组进行一次划分
    111     while (true)
    112     {  
    113         while(array[i] < pivot)
    114             i++;
    115         while(array[j] > pivot)
    116             j--;
    117         if (i < j) 
    118             swap(array[i], array[j]); 
    119         else   
    120             break;   
    121     }
    122     swap(array[i], array[right]); 
    123     
    124     /* 对三种情况进行处理:(m = i - left + 1)
    125     1、如果m=k,即返回的主元即为我们要找的第k小的元素,那么直接返回主元a[i]即可;
    126     2、如果m>k,那么接下来要到低区间A[0....m-1]中寻找,丢掉高区间;
    127     3、如果m<k,那么接下来要到高区间A[m+1...n-1]中寻找,丢掉低区间。
    128     */
    129     int m = i - left + 1;    
    130     if (m == k)
    131         return array[i];
    132     else if(m > k)  
    133         //上条语句相当于if( (i-left+1) >k),即if( (i-left) > k-1 ),于此就与2.2节里的代码实现一、二相对应起来了。
    134         return q_select(array, left, i - 1, k);  
    135     else  
    136         return q_select(array, i + 1, right, k - m);
    137 }
    138 
    139 int main()
    140 {
    141     //srand(unsigned(time(NULL)));
    142     //for (int j = 0; j < num_array; j++)
    143     //array[j] = rand();
    144     
    145     int array[num_array]={0,45,78,55,47,4,1,2,7,8,96,36,45};
    146     // 寻找第k最小数
    147     int k = 4;
    148     int i = q_select(array, 0, num_array - 1, k);
    149     cout << i << endl;
    150     
    151     return 0;
    152 }
  • 相关阅读:
    Linux 文件权限
    Linux 查看磁盘使用情况
    绑定到外部验证服务LDAP、配置 autofs
    创建逻辑卷
    查找一个字符串
    查找用户目录下的指定文件
    配置NTP时间服务器
    通过Roslyn构建自己的C#脚本(更新版)(转)
    Elon Musk
    可能改变世界的13个“终结”(上)
  • 原文地址:https://www.cnblogs.com/yyxayz/p/4028290.html
Copyright © 2011-2022 走看看