分治法--处理数列问题

# 分治法–处理数列问题

前言

最近学了分治法,我发现分治法在求一组数列的某些数据时有着很简洁的技巧。分治算法基本思想–“分”、“治”、“合”

分治算法–二分法

金块问题

老板有一袋金块(共n块),最优秀的雇员得到其中最重的一块,最差的雇员得到其中最轻的一块。假设有一台比较重量的仪器,我们希望用最少的比较次数找出最重的金块。

算法设计:问题可简化为:在含n个元素的集合中寻找最大值和最小值。

用分治法(二分法)可用较少比较次数解决上述问题:

1)将数据等分为两组(两组数据可能差1),目的是分别选取其中的最大(小)值。

2)递归分解直到每组元素的个数≤2,可简单地找到最大(小)值。

3)回溯时,在分解的两组解中大者取大,小者取小,合并为当前问题的解。


float a[n];
maxmin (int  i, int j ,float &fmax, float &fmin)
{ int mid;  float lmax, lmin, rmax, rmin;
  if (i=j)  {
  fmax= a[i];  
  fmin=a[i];}
  else if (i=j-1)
             if(a[i]rmax)    fmax=lmax;
            else   fmax=rmax;       
            if (lmin>rmin)     fmin=rmin;
            else  fmin=lmin;}
            //合的部分因为每一小部分都有一个最大与最小值,合就可以再在前一种的情况下。
            //再找出最大最小的情况以此递归就可以找出一整组数的最大最小值。
}

利用分治法求一组数列的最大两个数和最小两个数

#include
#define N 10
void max_min(int *a,int m,int n,int *min1,int *min2,int *max1,int *max2); 
int main(void)
{
int a[N]={7,8,9,10,11,12,13,14,15,16};
int min1,min2;
int max1,max2;
max_min(a,0,N-1,&min1,&min2,&max1,&max2);
//这四个参数的大小关系为 min1rmax1)//左子数组最大数>右子数组最大数
        {
            if(lmax2>rmax1)
            {
            *max1=lmax1;
            *max2=lmax2;
            }
            else
            {
            *max1=lmax1;
            *max2=rmax1;
            }
        }
        else//右子数组最大数>左子数组最大数
            if(rmax2>lmax1)
            {
            *max1=rmax1;
            *max2=rmax2;
            }
            else
            {
            *max1=rmax1;
            *max2=lmax1;
            }
        }//合的部分,对左右两组数列中的4个数进行再一次的排序,这种循环就可以重新确定出4个值
        //两组一次进行这样的筛选,知道最后就可以找出整个数列中的4个满足要求的值。
}

分治法求一组数据的和

#include
#define N 10
int getsum(int *a,int l,int r)//传数组a的每一个的值然后便于用来加减
{
    int mid;
    if(l==r)
        return a[l];//如果数组只有一个数,返回这个数即可
    else if(l==r-1)
        return a[l]+a[r];//如果有数组只有两个数,返回值为这两个数的值相加
        //最小情况的处理
    else
    {
        mid=(l+r)/2;
        return getsum(a,l,mid)+getsum(a,mid+1,r);
        //利用递归来做分治数组求和
    }
}
int main()
{
    int c;
    int a[N]={1,2,3,4,5,6,7,8,9,10};//定义数组,拿来求和
    c=getsum(a,0,N-1);//获取要的到的值,即为数组的和
    printf("最后分治法求的和为:%d",c);
}

求最大连续子数列和

我们可以把整个序列平均分成左右两部分,答案则会在以下三种情况中:

1、所求序列完全包含在左半部分的序列中。

2、所求序列完全包含在右半部分的序列中。

3、所求序列刚好横跨分割点,即左右序列各占一部分。

前两种情况和大问题一样,只是规模小了些,如果三个子问题都能解决,那么答案就是三个结果的最大值。

以分割点为起点向左的最大连续序列和、以分割点为起点向右的最大连续序列和,这两个结果的和就是第三种情况的答案。

#include 

//N是数组长度,num是待计算的数组,放在全局区是因为可以开很大的数组
int N, num[16777216];

int solve(int left, int right)
{
    //序列长度为1时
    if(left == right)
        return num[left];
    
    //划分为两个规模更小的问题
    int mid = left + right >> 1;
    //除以2
    int lans = solve(left, mid);
    int rans = solve(mid + 1, right);
    //划分成左右两个数列递归找到,
    
    //横跨分割点的情况
    int sum = 0, lmax = num[mid], rmax = num[mid + 1];
    for(int i = mid; i >= left; i--) {
        sum += num[i];
        if(sum > lmax) lmax = sum;
    }
    //从分割点向左相加,并求和,若求出的总和大于中间分割点的那个值,就把这个和置为新的lmax,意为左边最大值。
    sum = 0;
    for(int i = mid + 1; i <= right; i++) {
        sum += num[i];
        if(sum > rmax) rmax = sum;
    }//同理求出右边的最大值

    //答案是三种情况的最大值
    int ans = lmax + rmax;
    if(lans > ans) ans = lans;
    if(rans > ans) ans = rans;

    return ans;
}

int main()
{
    //输入数据
    scanf("%d", &N);
    for(int i = 1; i <= N; i++)
        scanf("%d", &num[i]);

    printf("%d\n", solve(1, N));

    return 0;
}

求一组数中第二小的数

分治法--处理数列问题_第1张图片

补充

其实这些题都是相同的类型的,结构上都是大同小异。定义一个可以递归的函数,设置一个递归出口,这里通常有对最小情况的处理。或是递归对问题规模再进行细小的划分。划分结束后就是对每一次小的情况找出来的一些值(通常二分法分治会找到左右两个数),对两个数再进行判断,从而找出再一次满足条件的值。循环下去就可以找到最终的值。

链接:http://note.youdao.com/noteshare?id=05752e16766f50ab5a7096f1f14aaecd&sub=E67840BAC3A14C0DAC685B62E5A3EC19

你可能感兴趣的:(算法,数据结构)