[算法设计与分析]4.3.5非等分分治(一组数的第二小+一组数的第k小)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Pecony/article/details/80065252
#include<stdio.h>
#include<iostream>
#include<cstring>

using namespace std;

const int N = 7;

int SecondSmall(int a[], int n);
void FindTwo(int a[], int i, int j, int &fmin1, int &fmin2);
int FindK(int a[], int n, int k);
void Swap(int &left, int &right);
int Select(int a[], int left, int right, int k);

int main ()
{
    int a[N] = {2, 4, 1, 6, 5, 9, 0};
    //cout << SecondSmall(a, N) << " is the second small number" << endl;

    int k = 3;
    cout << FindK(a, N, k) << " is the " << k << " small number" << endl;
}

//利用查找最小的两个数 才可以将原问题进行分解
int SecondSmall(int a[], int n)
{
    int min1, min2;
    FindTwo(a, 0, n - 1, min1, min2);
    return min2;
}

void FindTwo(int a[], int i, int j, int &fmin1, int &fmin2)
{
    int lmin1, lmin2, rmin1, rmin2;
    int mid;

    //递归出口
    if(i == j)
        fmin2 = fmin1 = a[i];
    else if(i == j - 1)
    {
        fmin2 = a[i] > a[j] ? a[i] : a[j];//fmin2存储较大
        fmin1 = a[i] > a[j] ? a[j] : a[i];
    }
    else
    {
        mid = (i + j) / 2;
        FindTwo(a, i, mid, lmin1, lmin2);
        FindTwo(a, mid + 1, j, rmin1, rmin2);

        if(lmin1 < rmin1)
        {
            fmin1 = lmin1;
            if(lmin1 != lmin2 && lmin2 < rmin1)//此处一定要加一个判断条件 以避免递归出口为i==j的情况min1和min2的值相同
                fmin2 = lmin2;//Eg:2 3 1 则rmin1=rmin2=1 此时经过判读 则fmin1=fmin2=1 显然是错误的
                                //因此该判断取值是否相等
            else
                fmin2 = rmin1;
        }
        else
        {
            fmin1 = rmin1;
            if(rmin1 != rmin2 && rmin2 < lmin1)
                fmin2 = rmin2;
            else
                fmin2 = lmin1;
        }
    }
}

int FindK(int a[], int n, int k)//k代表要找第k小的数
{
    if(k < 1 || k > n)
        return 0;
    return Select(a, 0, n - 1, k);//数组下标从0开始
}

int Select(int a[], int left, int right, int k)//在left-right之间选择第k小的数
{
    int pivot, i, j;
    if(left >= right)
        return a[left];

    pivot = a[left];//将最左边的元素作为枢轴

    i = left + 1;//i是从左到右的指针 此时i指向枢轴元素的下一个元素
    j = right;//j是从右到左的指针 初始时指向数组的最后一个元素

    while(1)//将左边大于枢轴元素的元素与右侧小于枢轴元素的元素进行交换
    {
       while(i <= right && a[i] < pivot)
       {
            i++;
       }
       //若是不满足while条件 则i此时指向的是大于枢轴元素的元素

       while(j >= left && a[j] > pivot)
       {
           j--;
       }

        if(i >= j)//此时left与right相遇
            break;
        Swap(a[i], a[j]);//a[i]作为低端元素大于枢轴元素 而a[j]作为高端元素小于枢轴元素 此时需要进行交换
    }
    
    //经过一次快速排序之后 此时j代表的是小于枢轴元素的最后一个元素的位置下标 因此j-left+1就是小于等于枢轴元素的个数
    if(j - left + 1 == k)//j-left实质上就代表了一次快速排序之后左子段中的元素个数 若是恰好为k - 1个 则pivot即为所求
    {
        return pivot;
    }

    //此时证明小于枢轴元素的数少于所要求的k 因此需要继续进行递归调用
    //将枢轴元素放到合适的位置 与快排相似 即枢轴元素之前的是小于它的 之后是大于它的 
    a[left] = a[j];
    a[j] = pivot;
    
    if(j - left + 1 < k)//此时证明所找出的元素数小于k个
        return Select(a, j + 1, right, k - j - 1 + left);//k-(j-left+1) 已有j-left+1个元素是已知小于第k个元素的
    else//找出的元素数大于k个 因此需要在小于枢轴元素的子序列中在进行查找
        return Select(a, left, j - 1, k);
}

void Swap(int &left, int &right)//要进行引用传值 直接对数组元素进行交换
{
    int t;
    t = left;
    left = right;
    right = t;
}

猜你喜欢

转载自blog.csdn.net/Pecony/article/details/80065252
今日推荐