求第k小的数、第k大的数、中位数的三种算法(nth_element()、快速选择算法、直接排序法)

题目描述:给定一个n个元素的无序序列,求出第k小的数、第k大的数、中位数。
注意:其实要求出第k大的数,只需要求出第 (n+1)-k 小的数即可,中位数同理,如果n为偶数,就是求出(第 n/2 小的数+第 n/2+1 小的数) / 2,如果n为奇数,就是求出第 n/2 + 1小的数。

下面给出三种解法

  • nth_element()
  • 快速选择法
  • 直接排序法

方法1:nth_element()函数

利用c++11自带的函数nth_element(a1,a2,a3):比如给定一个范围a.begin() 和a.end()作为参数1和3,参数2为a.begin()+5,就能求出该数组第6小的数,其实也就是排个正序以后位于下标位置5的数。

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <deque>
#include <list>
#include <utility>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <bitset>
#include <iterator>
using namespace std;

typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll  INF = 0x3f3f3f3f3f3f3f3f;
const double PI = acos(-1.0);
const double E = exp(1.0);
const int MOD = 1e9+7;
const int MAX = 1e5+5;

int main()
{
    /*
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    */

    vector <int> arr;
    for(int i = 0; i < 10; i++)
    {
        arr.push_back(i+1);
    }

    cout << "正序数组:" << endl;
    for(int i = 0; i < 10; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;

    cout << "乱序数组:" << endl;
    random_shuffle(arr.begin(),arr.end());
    for(int i = 0; i < 10; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;

    /** 方法1:调用nth_element()函数*/

    /* 求第k小的数
    for(int k = 1; k <= 10; k++)
    {
        cout << "使得第" << k << "小的数,位于第" << k  << "个位置:" << endl;
        nth_element(arr.begin(),arr.begin()+(k-1),arr.end());
        for(int i = 0; i < 10; i++)
        {
            cout << arr[i] << " ";
        }
        cout << endl;
        cout << "第" << k << "小的数为" << arr[k-1] << endl << endl;
    }*/

    /* 求第k大的数
    for(int k = 1; k <= 10; k++)
    {
        cout << "使得第" << k << "大的数,位于第" << 10-k+1  << "个位置:" << endl;
        // 第k大的数即为第n-k+1小的数
        nth_element(arr.begin(),arr.begin()+(10-k+1 -1),arr.end());
        for(int i = 0; i < 10; i++)
        {
            cout << arr[i] << " ";
        }
        cout << endl;
        cout << "第" << k << "大的数为" << arr[10-k+1 -1] << endl << endl;
    }*/

    /* 求n个数的中位数
    int n = 10;
    double mid = 0;
    if(n % 2 == 0)
    {
        // 偶数个数,中位数:(第n/2大的数 + 第n/2+1大的数) / 2
        int k = n - (n/2) + 1;
        nth_element(arr.begin(),arr.begin()+(k-1),arr.end());
        mid += arr[k-1];
        k = n - (n/2+1) + 1;
        nth_element(arr.begin(),arr.begin()+(k-1),arr.end());
        mid += arr[k-1];
        mid /= 2.0;
    }
    else
    {
        // 奇数个数,中位数:第n/2+1大的数
        int k = n - (n/2+1) + 1;// 对应的第k小的数
        nth_element(arr.begin(),arr.begin()+(k-1),arr.end());
        mid = arr[k-1];
    }
    cout << mid << endl;*/
    return 0;
}

方法2:快速选择法

其实和快速排序差不多,但是注意:只是思路差不太多,但是快排的时间复杂度为O(nlogn),快速选择算法的平均时间复杂度为O(n),最差时间复杂度和快速排序一样都是O(n2)。
具体思路:
每次Partition一次,返回得到一个分支点pivot,这个点对应的左边的元素小于 arr[pivot],右边的元素大于它,所以 arr[pivot] 对应的就是序列的第 pivot+1 小的元素。
此时要求第k小的元素:
如果k == pivot+1,直接返回即可,
如果k > pivot+1,说明要找的元素更大,就往右边递归,
如果k < pivot+1,说明要找的元素更小,就往左边递归。

代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <deque>
#include <list>
#include <utility>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <bitset>
#include <iterator>
using namespace std;

typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll  INF = 0x3f3f3f3f3f3f3f3f;
const double PI = acos(-1.0);
const double E = exp(1.0);
const int MOD = 1e9+7;
const int MAX = 1e5+5;

void Swap(vector <int>& arr,int i,int j)
{
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

int Partition(vector <int>& arr,int left,int right)
{
    /* 以arr[right]作为划分基准
    int j = left - 1;
    for(int i = left; i < right; i++)
    {
        if (arr[i] <= arr[right])
            Swap(arr, i, ++j);
    }
    Swap(arr,++j,right);
    return j;
    */

    /* 以arr[left]作为基准 */
    int val = arr[left];
    while(left < right)
    {
        while(left < right && arr[right] >= val)
            right--;
        arr[left] = arr[right];
        while(left < right && arr[left] <= val)
            left++;
        arr[right] = arr[left];
    }
    arr[left] = val;
    return left;
}

int quickSelect(vector <int>& arr,int left,int right,int k)
{
    // 在arr数组的left ~ right 的范围找出第k小的数
    while(left <= right)
    {
        int pivot = Partition(arr,left,right);
        // 比如k=5,那对应的第五小的数就在下标位置为4的地方(经过一次partition后,piovt左边的数都小于arr[pivot]、右边的数都大于arr[pivot])
        if(k-1 == pivot)
        {
            return pivot;// pivot对应的位置就是第k小的元素对应的位置
        }
        if(k-1 > pivot)
        {
            left = pivot+1;// 要找大一点的元素,要在右边找
        }
        else
        {
            right = pivot-1;// 要找小一点的元素,要在左边找
        }
    }
    return -1;
}

int main()
{
    /*
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    */

    vector <int> arr;
    for(int i = 0; i < 10; i++)
    {
        arr.push_back(i+1);
    }

    cout << "正序数组:" << endl;
    for(int i = 0; i < 10; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;

    cout << "乱序数组:" << endl;
    random_shuffle(arr.begin(),arr.end());
    for(int i = 0; i < 10; i++)
    {
        cout << arr[i] << " ";
    }
    cout << endl;

    /** 方法2:手写快速选择算法(1.应用了快速排序的思想 2.但其实就是跟nth_element差不多) */
    /* 求第k小的数 */
    int n = 10;
    for(int k = 1; k <= n; k++)
    {
        cout << "第" << k << "小的数为" << arr[quickSelect(arr,0,9,k)] << endl;
        for(int i = 0; i < n; i++)
        {
            cout << arr[i] << " ";
        }
        cout << endl << endl;
    }

    /* 求第k大的数 */
    for(int k = 1; k <= n; k++)
    {
        cout << "第" << k << "大的数为" << arr[quickSelect(arr,0,9,(n+1)-k)] << endl << endl;
        for(int i = 0; i < n; i++)
        {
            cout << arr[i] << " ";
        }
        cout << endl;
    }

    /*  求中位数:
        奇数,n/2+1小的数
        偶数:(n/2小的数 + n/2+1小的数) / 2
    */
    double mid;
    if(n % 2 == 0)
    {
        double tmp1 = arr[quickSelect(arr,0,n-1,n/2)];
        double tmp2 = arr[quickSelect(arr,0,n-1,n/2 + 1)];
        mid = (tmp1+tmp2)/2;
    }
    else
    {
        mid = arr[quickSelect(arr,0,n-1,n/2 + 1)];
    }
    cout << mid << endl;


    return 0;
}

方法3:

将数组排个序就可以了,然后直接O(1)取值,不过排序的时间复杂度也要算进来,快排的话就是O(nlogn),我就不写了。

发布了197 篇原创文章 · 获赞 18 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_41708792/article/details/103130479