zoukankan      html  css  js  c++  java
  • 查找数组第k大的元素

    方法一:

      直接排序法,这是一种最直观最容易理解的方法,先将数组用某种方法进行排序,然后选取第k大的数,算法的好坏取决与排序算法,常见的排序有,冒泡排序(n^2),擦入排序(n^2),堆排序(nlgn),快速排序(nlgn),归并排序(nlgn),计数排序(n+k),基数排序(n),由于方法简单这里就不给出代码了。

    方法二:

    // find_kth.cpp : 定义控制台应用程序的入口点。
    //方法类似于归并排序,平均时间复杂度O(n),最坏时间复杂度O(n^2)

    #include "stdafx.h"
    #include<iostream>
    using namespace std;

    int find_the_kth_num(int *a,int k,int front,int end){
      int comp = a[front];
      if(k > (end + 1)){
        cout<<"数组越界!!"<<endl;
        return -1;
       }else{
        if(front == end){
          return a[front];
        }else{
          int i = front;
          int j = front + 1;
          for(;j <= end;++j){
            if(a[j] < comp){
              ++i;
              int temp = a[i];
              a[i] = a[j];
              a[j] = temp;

            }
          }
          if(i == k - 1){
            return comp;
          }else{
            a[front] = a[i];
            a[i] = comp;
            if(i < k - 1){
              return find_the_kth_num(a,k,i + 1,end);
            }else{
              return find_the_kth_num(a,k,front,i - 1);
            }
          }

        }
      }

    }

    int _tmain(int argc, _TCHAR* argv[])
    {
      int a[] = {1,2,3,5,6,7,8,9,4,5,6,7,8,9};
      cout<<find_the_kth_num(a,8,0,13);
      while(1);
      return 0;
    }

     方法三:

    最坏情况线性时间的选择法(原理部分转):

    基本原理:

      SELECT算法的思想是要保证对数组的划分是个好的划分,对PARTITION过程进行了修改。现在通过SELECT算法来确定n个元素的输入数组中的第i小的元素,具体操作步骤如下:

    (1)将输入数组的n个元素划分为n/5(上取整)组,每组5个元素,且至多只有一个组有剩下的n%5个元素组成。

    (2)寻找每个组织中中位数。首先对每组中的元素(至多为5个)进行插入排序,然后从排序后的序列中选择出中位数。

    (3)对第2步中找出的n/5(上取整)个中位数,递归调用SELECT以找出其中位数x。(如果是偶数去下中位数)

    (4)调用PARTITION过程,按照中位数x对输入数组进行划分。确定中位数x的位置k。

    (5)如果i=k,则返回x。否则,如果i<k,则在地区间递归调用SELECT以找出第i小的元素,若干i>k,则在高区找第(i-k)个最小元素。

    关于复杂度:设复杂度T(n)则:

      T(n) <=   T(n/5) + T(n/2+(n/2)*(2/5)) + O(n) <= T(n/5) + T(7n/5) + O(n)

      容易的到:T(n) = O(n)

    这里我们将讨论一个有意思的问题:为什么选5,选3行吗?

      答案是肯定的:不行!!

      分析:如果选3作为划分基准,那么

      T(n) =  T(n/3) + T(n/2+(n/2)*(n/3)) + O(n) <= T(n/3) + T(2n/3) + O(n) = O(nlgn)

    (注意:这里我们用到了一个结论,对于T(n) <= T(an) + T(bn) +O(n),若a+b<1则T(n) = O(n)若a+b = 1则T(n) = O(nlgn))

    实际上基准为7及其以上也是可以的

    SELECT算法通过中位数进行划分,可以保证每次划分是对称的,这样就能保证最坏情况下运行时间为θ(n)。举个例子说明此过程,求集合A={32,23,12,67,45,78,10,39,9,58,125,84}的第5小的元素,操作过程如下图所示:

    现在采用C语言实现上面的例子,完整程序如下所示:

    /*Filename:select_middle_good.cpp */
    /*从一个数组中选取第k大的数值并返回其值 */
    /*思路: */
    #include<iostream>
    using namespace std;

    int partition(int *Data,int front,int end,int com){//划分函数,一直数组起点终点 以及数组中的数com用com这个数作为划分点进行划分,左边的数都小于或等于com,右边的数都大于com
        int i = front;
        int j = front;
        int temp = 0;
        int mark = front;
        for(;j < end;++j){
            if(Data[j] <= com){
                temp = Data[j];
                Data[j] = Data[i];
                Data[i] = temp;
                if(Data[i] == com){//标记最后一次遇到与com值相等的数的交换后被放到的位置
                    mark = i;
                }
                ++i;
            }
        }
        --i;
        temp = Data[i];
        Data[i] = Data[mark];
        Data[mark] = temp;
        return i;
    }

    void sort(int *Data,int front,int end){//数组局部排序,冒泡法
        int temp = 0;
        if(end - front == 0)return;
        for(int i = 0;i < (end - front + 1);++i){
            for(int j = front;j < end - i;++j){
                if(Data[j] > Data[j + 1]){
                    temp = Data[j];
                    Data[j] = Data[j + 1];
                    Data[j + 1] = temp;
                }
            }
        }
    }

    int select(int *Data,int front,int end,int k){
        cout<<front<<"--"<<end<<endl;//输出每次select函数被调用时数组的起点与终点
        /*处理长度为5以内的数组*/
        if((end - front) < 5){//数组长度不足5的时候,特殊处理
            if(k > (end - front + 1)){
                cout<<"越界!!!";
                return -1;
            }else{
                sort(Data,front,end);
                return Data[front + k -1];
            }
        }
        int sub_len = (end - front + 1)/5;
        int sub_len_a = (end - front + 1 - sub_len * 5) > 0?sub_len + 1:sub_len;
        int *p = new int[sub_len_a];
        for(int i = 0;i < sub_len;++i){
            sort(Data,front + 5*i,5*i + 4);//数组每五个数字进行排序(冒泡法)
            p[i] = Data[front + 5*i +2];
        }
        if(sub_len_a > sub_len){
            p[sub_len_a - 1] = Data[front + 5*sub_len + 1];    //数组长度不是5的整数倍,中值序列最后一位取尾子数组第一个值(这是随意取的一个值 不回影响算法性能)
        }
        int com = select(p,0,sub_len_a - 1,sub_len_a/2);//递归选取中值的中值
        int d = partition(Data,front,end,com);//用中值的中值作为划分边界进行一次划分,实属组左边的数值都小于该值,右边的值都大于该值
        if(d == (k - 1)){
            return Data[d];//划分值恰好是要查找的那个数
        }else if(d < (k - 1)){
            return select(Data,d + 1,end,k - (d - front + 1));//划分数位置左边的数个数小于要求值的位置
        }else{
            return select(Data,front,d - 1,k);//划分数位置大于要求值的位置
        }
    }

    int main(){
        int test[] = {7,3,2,6,5,4,2,1,9,11};
        cout<<select(test,0,9,6)<<endl;
        for(int i = 0;i <= 8;++i){
            cout<<test[i]<<" ";
        }
        return 0;
    }

  • 相关阅读:
    49. 字母异位词分组
    73. 矩阵置零
    Razor语法问题(foreach里面嵌套if)
    多线程问题
    Get json formatted string from web by sending HttpWebRequest and then deserialize it to get needed data
    How to execute tons of tasks parallelly with TPL method?
    How to sort the dictionary by the value field
    How to customize the console applicaton
    What is the difference for delete/truncate/drop
    How to call C/C++ sytle function from C# solution?
  • 原文地址:https://www.cnblogs.com/candycloud/p/3352188.html
Copyright © 2011-2022 走看看