排序3 - 快速排序 - 第K大数

第K大数的问题

就是求解一个序列中第K大的数

常见做法:将原序列排序,然后访问第n - k个数即可,总体时间复杂度O(nlogn)

而本题其实只需要求第K大数,而对于其余的数是否有序,我们并不关心,

因此可以利用快速排序,在线性的O(n)时间内获得第K大数

先回顾一下快速排序的算法思想:

1. 在原序列中随机找一个中心值pivot,将小于中心值的元素放在其左边,大于中心值的元素放在其右边

   一次快排,中心值位置就唯一确定了

2.对左右两边进行同样的操作

#include
#include
#include //rand()
#include//swap()

using namespace std;

const int MAXN = 1000 + 10;
int arr[MAXN];

int Partition(int left, int right){
    //将原始的无序序列找一个值作为中心值
    //并且将他的位置固定下来
    //即将原始序列中小于中心值的数放在它左边,大于放右边
    int random = left + rand() % (right - left + 1);
    //rand()会随机生成一个值,之后对于()取模,能保证值在0~right-left
    //+left能保证值在left到right之间,并且这两个值也能取到

    swap(arr[left],arr[random]);

    while(left < right){//自己想象那个数组和指针
        while(left < right && arr[left] <= arr[right]){//此时中心值在左边,
            //指针从右往左找到第一个小于中心值的
            right--;
        }
        swap(arr[left],arr[right]);
        while(left < right && arr[left] <= arr[right]){
            left++;
        }
        swap(arr[left],arr[right]);
    }
    return left;//此时 left == right

}

void QuickSort(int left,int right){ //快排的参数是待排序的两个端点
    //当只有一个元素存在的情况下,整个序列是有序的
    //因此只处理多个元素存在的序列
    if(left < right){
          int position = Partition(left,right); //先获取中心值的位置
          QuickSort(left,position -1);
          QuickSort(position +1,right);

    }
}


int main(){
    int n;
    scanf("%d",&n);
    for(int i = 0; i < n; ++i){
        scanf("%d",&arr[i]);
    }

    QuickSort(0, n - 1);

    for(int i = 0; i < n; ++i){
        printf("%d ",arr[i]);
    }
    return 0;
}

如何利用快速排序高效地求得第K大数呢?

在快排过程中,每一步都会将随机选择出来的中心值的位置唯一确定

第K大数就是原始序列在有序(从小到大)的情况下的下标index = n - k 的数

所以在快排过程中,你确定的中心值的下标正好是 n - k ,那么这个中心值正是第K大数

而如何一步步筛选得到最终结果呢?

若此时中心值下标 < n - k,而由于快排的特性,左边都比中心值小,右边都比中心值大,

那么第K大数就一定在此时的中心值的右边,那么下一次的随机取值,就从右边取了!这样可以缩小搜索空间

时间复杂度为 n + n/2 + n/4 +......+1 = O(n) < O(nlogn)

#include
#include
#include //rand()
#include//swap()

using namespace std;

const int MAXN = 1000 + 10;
int arr[MAXN];

int Partition(int left, int right){
    //将原始的无序序列找一个值作为中心值
    //并且将他的位置固定下来
    //即将原始序列中小于中心值的数放在它左边,大于放右边
    int random = left + rand() % (right - left + 1);
    //rand()会随机生成一个值,之后对于()取模,能保证值在0~right-left
    //+left能保证值在left到right之间,并且这两个值也能取到

    swap(arr[left],arr[random]);

    while(left < right){//自己想象那个数组和指针
        while(left < right && arr[left] <= arr[right]){//此时中心值在左边,
            //指针从右往左找到第一个小于中心值的
            right--;
        }
        swap(arr[left],arr[right]);
        while(left < right && arr[left] <= arr[right]){
            left++;
        }
        swap(arr[left],arr[right]);
    }
    return left;//此时 left == right

}

int QuickSort(int left,int right,int p){ //快排的参数是待排序的两个端点
    //当只有一个元素存在的情况下,整个序列是有序的
    //因此只处理多个元素存在的序列
    //p是目标位置
      int position = Partition(left,right); //先获取中心值的位置
      if(position < p){
        return QuickSort(position + 1,right, p);
      }else if ( position > p){
        return QuickSort(left,position - 1, p);
      }else{
        return arr[p];
      }
}


int main(){
    int n;
    scanf("%d",&n);
    int k;
    scanf("%d",&k);
    for(int i = 0; i < n; ++i){
        scanf("%d",&arr[i]);
    }

    int answer = QuickSort(0, n - 1, n - k);
    printf("%d\n",answer);

    return 0;
}

主要修改了quicksort函数的代码,p是目标位置。

 

你可能感兴趣的:(计算机上机复试)