Wewe带你看代码 --二分法与分治法

来源:互联网 发布:太原市安全教育网络 编辑:程序博客网 时间:2024/04/29 08:35

题目1:

设X[0:n-1]和Y[0:n-1]为两个数组,每个数组中含有n个已排好序的数。设计一个O(logn)时间的算法,找出X和Y的2n个数的中位数。

分析:
中位数:位于某一序列中间的某个数或两个数的平均数。

由此可知,中位数是在一序列的中间的数,利用此特性可以使用二分法对序列进行运算。
由题已知,该两个数组都是已经排好序的,所以中位数必然是由该两个序列去掉头尾部分得到。
我们设:

Size = N;low1 = low2 = 0;high1 = high2 = size – 1;mid1 = (low1 + high1)/2;mid2 = (low2 + high2)/2;

1.奇数个元素:
这里写图片描述

if (s2[mid] >s1[mid]){    high2 = mid2;  low1 = mid1;}else if(s2[mid] <s1[mid]){    high1 = mid1;  low2 = mid2;}  else{ return s1[mid];    // 此时两个数组的中位数相等即为合并数组的中位数}

2.偶数个元素:
这里写图片描述

if ((high1 - low1) % 2 != 0){if (s1[mid1 + 1] > s2[mid2 + 1])       {        high1 = mid1 + 1;        low2 = mid2; //此处与奇数时有所不同       }      else if (s1[mid1 + 1] < s2[mid2 + 1])       {        high2 = mid2 + 1;        low1 = mid1;      }     else     {        if (s1[mid1 + 2] >= s2[mid2 + 2])         {            high1 = mid1 + 1;            low2 = mid2;         }        else         {            high2 = mid2 + 1;            low1 = mid1;         }}else{        调用奇数处理的方法(处理如下图所示情况)}

这里写图片描述

代码:

#include <iostream>#include <fstream>#include <string>using namespace std;const int N = 5;    // 数组的元素个数double oddMidNum(int s1[N], int s2[N]);double evenMidNum(int s1[N], int s2[N]);int main(){    ifstream fin("midnumber.in");    ofstream fout("midnumber.out");    int s1[N];    int s2[N];    //cout << "输入数组1: ";    for (int i = 0; i < N; i++)        fin >> s1[i];    //cout << "输入数组2: " ;    for (int i = 0; i < N; i++)        fin >> s2[i];    if (N % 2 != 0)    {        fout << oddMidNum(s1, s2) << endl;    }    else    {        fout << evenMidNum(s1, s2) << endl;    }    return 0;}double oddMidNum(int s1[N], int s2[N]){    int mid1, mid2;    int low1 = 0;    int low2 = 0;    int high1 = N - 1;    int high2 = N - 1;    while ((high1 - low1 != 1) && (high2 - low2 != 1))    {        mid1 = (high1 + low1) / 2;        mid2 = (high2 + low2) / 2;        if (s1[mid1] > s2[mid2])        {            high1 = mid1;            low2 = mid2;        }        else if (s1[mid1] < s2[mid2])        {            high2 = mid2;            low1 = mid1;        }        else        {            return s1[mid1];        }    }    if (s1[low1] >= s2[low2])    {        if (s1[high1] > s2[high2])        {            return double(s1[low1] + s2[high2]) / 2.0;        }        else        {             return double(s1[low1] + s1[high1])/2.0;        }    }    else     {        if (s2[high2] > s1[high1])        {            return double(s2[low2] + s1[high1])/2.0;        }        else        {            return double(s2[low2] + s2[high2])/2.0;        }    }}double evenMidNum(int s1[N], int s2[N]){    int mid1, mid2;    int low1 = 0;    int low2 = 0;    int high1 = N - 1;    int high2 = N - 1;    while ((high1 - low1 != 1) && (high2 - low2 != 1))    {        if ((high1 - low1) % 2 != 0)        {            // 处理偶数个子字符串            mid1 = (high1 + low1) / 2;            mid2 = (high2 + low2) / 2;            if (s1[mid1 + 1] > s2[mid2 + 1])            {                high1 = mid1 + 1;                low2 = mid2; //此处与奇数时有所不同            }            else if (s1[mid1 + 1] < s2[mid2 + 1])            {                high2 = mid2 + 1;                low1 = mid1;            }            else            {                if (s1[mid1 + 2] >= s2[mid2 + 2])                {                    high1 = mid1 + 1;                    low2 = mid2;                }                else                {                    high2 = mid2 + 1;                    low1 = mid1;                }            }        }        else        {            // 处理奇数个子字符串            mid1 = (high1 + low1) / 2;            mid2 = (high2 + low2) / 2;            if (s1[mid1] > s2[mid2])            {                high1 = mid1;                low2 = mid2;            }            else if (s1[mid1] < s2[mid2])            {                high2 = mid2;                low1 = mid1;            }            else            {                return s1[mid1];            }        }    }    if (s1[low1] >= s2[low2])    {        if (s1[high1] > s2[high2])        {            return double(s1[low1] + s2[high2]) / 2.0;        }        else        {            return double(s1[low1] + s1[high1]) / 2.0;        }    }    else    {        if (s2[high2] > s1[high1])        {            return double(s2[low2] + s1[high1]) / 2.0;        }        else        {            return double(s2[low2] + s2[high2]) / 2.0;        }    }}

复杂度分析:
此处注意二分法与分治法的不同之处,二分法是将原问题化简为更小的子问题,子问题的解即是原问题的解;而分治法是将原问题分解为多个子问题,然后根据子问题的解逐步得出原问题解。
T(n) = T(n/2) + O(1)
->T(n) = T(n/4) + 2O(1)
->T(n) = T(n/8) + 3O(1)

->T(n) = T(1) + logN * O(1)
->T(n) = O(logN)


题目2:

有一个实数序列a1, a2, a3, … ,aN;若i < j 且 ai > aj,则(ai, aj)构成了一个逆序对,请使用分治方法求整个序列中逆序对个数,并分析算法的时间复杂性。
分析:
考虑更小的逆序对个数(count)问题。
首先我们只考虑两个数的逆序对,如果有2,1两个数,则count = 1;
考虑三个数的逆序对,如果有4 , 2, 1三个数,我们可以先考虑4, 2,是逆序,则将4,2反序得到2, 4, 1,count += 1;
2, 4, 1拆分:
2 4
1
由于2 > 1, 则count += 2(因为反序后2后面的那个数4必然大于2);
再比较4 > 1, 则count += 1;
由此我们可以得出多个数的逆序对求法,详见代码部分。
代码:

#include <iostream>#include <fstream>#include <vector>using namespace std;int merge(int* arr1, int* arr2, int begin, int mid, int end){    int i = begin, j = mid + 1, k = begin, c = 0;    // 数组的划分比较    while (i != mid + 1 && j != end + 1)    {        if (arr2[i] < arr2[j])        {            arr1[k++] = arr2[i++];        }        else        {            arr1[k++] = arr2[j++];            c += (mid + 1 - i);        }    }    return c;}int separate(int* arr, int* temp, int begin, int end, bool inArr){    int count = 0;    if (begin < end)    {        if (end - begin == 1)        {            if (arr[end] < arr[begin])            {                // 对子数组的两个数从大到小排序,原始数组arr子数组微小改变                arr[begin] = temp[end];                arr[end] = temp[begin];                count++;            }        }        else        {            int mid = (begin + end) / 2;            int c1 = separate(arr, temp, begin, mid, !inArr);            int c2 = separate(arr, temp, mid + 1, end, !inArr);            int c3 = 0;            if (inArr)            {                c3 = merge(arr, temp, begin, mid, end); // 最终使用排好序的两个子数组            }            else            {                c3 = merge(temp, arr, begin, mid, end); // 子数组的子数组比较还是使用微小改变的原始数组arr            }            count = c1 + c2 + c3;        }        return count;    }    else    {        return 0;    }}int main(){    ifstream fin("reversednum.in");    ofstream fout("reversednum.out");    int size;    // 数组中数据个数    fin >> size;    int *arr, *temp;    arr = new int[size];    for (int i = 0; i < size; i++)        fin >> arr[i];    // 原始数组的备份    temp = new int[size];    memcpy(temp, arr, size*4);    fout << separate(arr, temp, 0, size - 1, true) << endl;    return 0;}

复杂度分析:
T(n) = 2T(n/2) + n
f(n) = n,由master定理, n^(logb^a ) = n^1 = f(n),则为同阶情况,T(n) = O(nlogn)。

0 0