在数组A中寻找第k小的元素-最坏情况为线性时间的算法

来源:互联网 发布:淘宝便宜手机优惠券 编辑:程序博客网 时间:2024/04/30 11:53

I : 平均情况下的时间复杂度为:O(n) 期望值(expected); 最坏情况下的时间复杂度为:O(n的平方)即:每次对A划分为(0:n-1)

函数命名为:find_k_th(A, p, q, k)    //int  value = find_k_th(A, p, q, k): value为从A[p]到A[q]中第k小的值

可以从 random-quicksort 算法中(复杂度分析运用了随机变量指示器:indicator of random variable)将数组A进行划分,随机选取主元pivot,得到一个返回的值r(r为数组A中<=pivot 的索引,即:A[r] = pivot,小于pivot的个数为:num = r-p+1 ):

  1. num = k, 返回A[r], 即:A[r]为所要找的A中第k小的值;
  2. num < k, 递归地返回 find_k_th(A, r+1, q, k-num), 即:A的左边有num个小于第k小的值,则第k小的值 = 右边的第 k-num 小的值;
  3. num > k, 递归地返回 find_k_th(A, p, r-1, k), 即: num > k, 第k小的值在A的左边
II: worst-case下时间复杂度为:O(n)的算法:

因为第I种算法只有平均情况下T(n)才为线性,不能避免最坏的情况;为了使最坏情况下T(n)也为线性,需要每次选择的主元pivot应该确定地处于A的中间,而非随机选择。

方法:递归的划分输入数组A

步骤:

  1. 将长度为n的数组A,划分为 n/5 个长度为5的小数组,并且选择对每个数组都取其中值,并储存在temp[ floor(n/5) ]中( 可用插入排序再返回中值,因为长度为固定的5,时间复杂度 = Theta(n) );
    int divide5ReturnMedian(int *A, int p){    int B[5];    for(int i = 0; i < 5; i++)        B[i] = A[i+p];    for(int i = 0; i < 4; i++){        int j = i+1;        int k = i;        int temp = B[j];        while( temp < B[k] && k >= 0 ){            B[k+1] = B[k];            k--;        }        B[k+1] = temp;    }    return B[2];}

  2. 递归地寻找temp数组中的中值将其作为x,即:median of the medians ,时间复杂度 = T(n/5);
     int sub_n = n/5;        if( sub_n > 0)          //A的长度 >= 5        {            int temp[sub_n];            for(int i = 0; i < sub_n; i++)                temp[i] = divide5ReturnMedian(A, p+i*5);            x = find_k_th(temp, 0, sub_n-1, sub_n/2);    //第一次递归:递归地选择中间值作为pivot        }        else           //A的长度 < 5, 直接使用插入排序得到中间值作为pivot        {            int B[n];            for(int i = 0; i < n; i++)                B[i] = A[p+i];            insertionSort(B, 0, n-1);            x = B[n/2];        }

  3. 将x作为pivot,对A进行划分,此时每次划分都在中间!而非随机!时间复杂度 = O(n);
     int r = partition(A, p, q, x);

  4. 判断num的值(方法与随机选择pivot相同)。
    int num = r-p+1;        if( num == k )            return A[r];        if( num < k )            return find_k_th(A, r+1, q, k-num);        else            return find_k_th(A, p, r-1, k);

步骤3和4与第一种方法相同,但是选择pivot不再是随机化,而是每次都在中间选择pivot,时间复杂度 = T(7n/10)。


总的时间复杂度:T(n) = T(n/5) + T(7n/10) + O(n) = O(n) 线性!

代码实现:

int divide5ReturnMedian(int *A, int p);int partition(int *A, int p, int q, int pivot);int find_k_th(int *A, int p, int q, int k);int insertionSort(int *A, int p, int q);int main() {    cout << "Hello, World!" << endl;    int A[28];    for(int i = 0; i < 28; i++)        A[i] = 28-i;    cout<<find_k_th(A, 0, 27, 2)<<endl;    insertionSort(A, 0, 27);    return 0;}int insertionSort(int *A, int p, int q){    if(p < q){        int i = p;        for(i = p; i < q+1; i++){            int j = i+1;            int k = i;            int temp_j = A[j];            while( temp_j < A[k] && k >= 0){                A[k+1] = A[k];                k--;            }            A[k+1] = temp_j;        }    }    return 0;}int divide5ReturnMedian(int *A, int p){    int B[5];    for(int i = 0; i < 5; i++)        B[i] = A[i+p];    for(int i = 0; i < 4; i++){        int j = i+1;        int k = i;        int temp = B[j];        while( temp < B[k] && k >= 0 ){            B[k+1] = B[k];            k--;        }        B[k+1] = temp;    }    return B[2];}int partition(int *A, int p, int q, int pivot){    int k = p;    while( A[k] != pivot ){ k++;}    A[k] = A[p];    A[p] = pivot;    int i = p;    for(int j = i+1; j <= q; j++){        if( A[j] < pivot ){            i++;            int temp = A[i];            A[i] = A[j];            A[j] = temp;        }    }    A[p] = A[i];    A[i] = pivot;    return i;}int find_k_th(int *A, int p, int q, int k){    if(p < q)    {        int x = 0;        //x为pivot        int n = q-p+1;        int sub_n = n/5;        if( sub_n > 0)          //A的长度 >= 5        {            int temp[sub_n];            for(int i = 0; i < sub_n; i++)                temp[i] = divide5ReturnMedian(A, p+i*5);            x = find_k_th(temp, 0, sub_n-1, sub_n/2);    //第一次递归:递归地选择中间值作为pivot        }        else           //A的长度 < 5, 直接使用插入排序得到中间值作为pivot        {            int B[n];            for(int i = 0; i < n; i++)                B[i] = A[p+i];            insertionSort(B, 0, n-1);            x = B[n/2];        }        int r = partition(A, p, q, x);        int num = r-p+1;        if( num == k )            return A[r];        if( num < k )            return find_k_th(A, r+1, q, k-num);        else            return find_k_th(A, p, r-1, k);    }    return A[p];}


0 0
原创粉丝点击