17082 两个有序数序列中找第k小

Description

已知两个已经排好序(非减序)的序列X和Y,其中X的长度为m,Y长度为n,
现在请你用分治算法,找出X和Y的第k小的数,算法时间复杂度为O(max{logm, logn})。

此题请勿采用将序列X和Y合并找第k小的O(m+n)的一般方法,要充分利用X和Y已经排好序的这一特性。



输入格式

第一行有三个数,分别是长度m、长度n和k,中间空格相连(1<=m,n<=100000; 1<=k<=m+n)。
第二行m个数分别是非减序的序列X。第三行n个数分别是非减序的序列Y。


输出格式

序列X和Y的第k小的数。


输入样例

5 6 7
1 8 12 12 21 
4 12 20 22 26 31


输出样例

20


提示

假设:X序列为X[xBeg...xEnd],而Y序列为Y[yBeg...yEnd]。

将序列X和Y都均分2段,即取X序列中间位置为 xMid (xMid = xBeg+(xEnd-xBeg)/2),也同理取序列Y中间位置为yMid。
比较X[xMid]和Y[yMid]的大小,此时记录X左段和Y左段元素个数合计为halfLen,即halfLen = xMid-xBeg+yMid-yBeg+2。

1. 当X[xMid] < Y[yMid]时,在合并的数组中,原X[xBeg...xMid]所有元素一定在Y[yMid]的左侧,
   (1) 若k < halfLen,则此时第k大的元素一定不会大于Y[yMid]这个元素,故以后没有必要搜索 Y[yMid...yEnd]这些元素,可弃Y后半段数据。
       此时只需递归的对X序列+Y序列的前半段,去搜索第k小的数。

   (2) 若k >= halfLen,则此时第k大的元素一定不会小于X[xMid]这个元素,故以后没有必要搜索 X[xBeg...xMid]这些元素,可弃X前半段数据。
       此时只需递归的对X序列的后半段+Y序列,去搜索第 k-(xMid-xBeg+1)小的数。

2. 当X[xMid] >= Y[yMid]时,在合并的数组中,原Y[yBeg...yMid]的所有元素一定在X[xMid]的左侧,
   (1) 若k < halfLen,则此时第k大的元素一定不会大于X[xMid]这个元素,故以后没有必要搜索 X[xMid...xEnd]这些元素,可弃X后半段数据。
       此时只需递归的对X序列的前半段+Y序列,去搜索第k小的数。

   (2) 若k >= halfLen,则此时第k大的元素一定不会小于Y[yMid]这个元素,故以后没有必要搜索 Y[yBeg...yMid]这些元素,可弃Y前半段数据。
       此时只需递归的对X序列+Y序列的后半段,去搜索第 k-(yMid-yBeg+1)小的数。

递归的边界,如何来写?
1) if (xBeg > xEnd) return Y[yBeg + k - 1];  //X序列为空时,直接返回Y序列的第k小元素。
2) if (yBeg > yEnd) return X[xBeg + k - 1];  //Y序列为空时,直接返回X序列的第k小元素。


效率分析:

T(m,n)表示对长度为m的有序的X序列和长度为n的有序的Y序列,搜索第k小元素的复杂度。
T(m,n)=1   m=0或n=0
T(m,n) <= max{T(m/2,n), T(m,n/2)} + O(1)

则T(m,n) = O(max{logm, logn})
#include <iostream>  
  
using namespace std;  
  
//int test_count = 0;  
int a[100010], b[100010];  
  
void search_i(int need_i, int a_left, int a_right, int b_left, int b_right)  
{  
  
    int a_mid = (a_left + a_right + 1)/2;// +1 是关键,因为数组下标从1开始放数据  
    int b_mid = (b_left + b_right + 1)/2;// +1 是关键,因为数组下标从1开始放数据  
    int half = a_mid - a_left +1   +    b_mid - b_left + 1;// half为每次处理时数据个数的一半,根据mid和left计算得出  
  
    if(a[a_mid] < b[b_mid])// a中间数 < b中间数  
    {  
        if(need_i < half)// 此处不能为 <=,因为此处b_mid - 1,会提前导致right < left  
        {  
            // 舍弃[b_mid]~b[b_right]  
            b_right = b_mid - 1;  
        }  
        else  
        {  
            // 舍弃a[a_left]~a[a_mid]  
            need_i -= (a_mid - a_left + 1);  
            a_left = a_mid + 1;  
        }  
    }  
    else  
    {  
        if(need_i < half)// 此处不能为 <=,因为此处b_mid - 1,会提前导致right < left  
        {  
            // 舍弃a[a_mid]~a[a_right]  
            a_right = a_mid - 1;  
  
        }  
        else  
        {  
            // 舍弃b[b_left]~b[b_mid]  
            need_i -= (b_mid - b_left + 1);  
            b_left = b_mid + 1;  
        }  
    }  
  
    if(a_right < a_left)  
    {// a数组已不在范围内,从b数组取结果即可  
        cout << b[b_left - 1 + need_i];  
        return;  
    }  
    if(b_right < b_left)  
    {// b数组已不在范围内,从a数组取结果即可  
        cout << a[a_left -1 + need_i];  
        return;  
    }  
  
    search_i(need_i, a_left, a_right, b_left, b_right);//递归  
}  
  
/* 
 
5 6 8 
1 8 12 12 21 
4 12 20 22 26 31 
 
*/  
  
int main()  
{  
    int first_nums,second_nums,need_i;  
    cin >> first_nums;  
    cin >> second_nums;  
    cin >> need_i;  
  
    //int a[100010],b[100010];  
    int a_left = 1, a_right = first_nums;  
    int b_left = 1, b_right = second_nums;  
  
  
    int i;  
    for(i = 1; i <= first_nums; i++)  
    {  
        cin >> a[i];  
    }  
    for(i = 1; i <= second_nums; i++)  
    {  
        cin >> b[i];  
    }  
    // 以上为初始化输入数据  
  
  
    //预处理  
    if(need_i == 1)  
    {  
        cout << min(a[1],b[1]);  
    }  
    else if(need_i == (first_nums + second_nums))  
    {  
        cout << max(a[first_nums], b[second_nums]);  
    }  
    else  
    {  
        search_i(need_i, a_left, a_right, b_left, b_right);  
    }  
  
    cout << endl;  
  
    return 0;  
}  


在C语言计算两个有序序列位数,通常涉及到合并这两个有序序列,并间值。如果序列的长度相等,位数就是两序列合并后的间元素;如果一序列比另一序列长,那么位数则是较长序列的第一个大于等于较短序列间位置的元素。 以下是一个简单的算法步骤: 1. 初始化两个指针,分别指向两个序列的起始位置。 2. 比较两个指针所指的元素,将较小的元素添加到结果数组并移动相应的指针。 3. 当其一个指针到达序列末尾时,将另一个序列剩余部分复制到结果数组相应位置。 4. 如果两个序列的总长度是奇数,结果数组的最后一个元素即为位数;如果是偶数,位数则是结果数组两个元素的平均值。 这里举一个伪代码示例: ```c double merge_and_find_median(int *arr1, int len1, int *arr2, int len2) { int i = 0, j = 0; double* merged = (double*)malloc((len1 + len2) * sizeof(double)); int k = 0; while (i < len1 && j < len2) { if (arr1[i] <= arr2[j]) { merged[k++] = (double)arr1[i++]; } else { merged[k++] = (double)arr2[j++]; } } // 将剩余的序列添加到结果 while (i < len1) merged[k++] = (double)arr1[i++]; while (j < len2) merged[k++] = (double)arr2[j++]; // 计算位数 double median; if ((len1 + len2) % 2 == 0) { median = (merged[(len1 + len2) / 2 - 1] + merged[(len1 + len2) / 2]) / 2.0; } else { median = merged[(len1 + len2) / 2]; } free(merged); return median; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值