分治法--处理数列问题

# 分治法–处理数列问题

前言

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

分治算法–二分法

金块问题

老板有一袋金块(共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]<a[j])  
             { fmax=a[j];fmin=a[i];}
             else {
             fmax=a[i]; fmin=a[j];}//最小情况的处理
  else {  mid=(i+j)/2;       
  maxmin (i,mid,lmax,lmin);  
  maxmin (mid+1,j,rmax,rmin);   //递归的分  
  if  (lmax>rmax)    fmax=lmax;
            else   fmax=rmax;       
            if (lmin>rmin)     fmin=rmin;
            else  fmin=lmin;}
            //合的部分因为每一小部分都有一个最大与最小值,合就可以再在前一种的情况下。
            //再找出最大最小的情况以此递归就可以找出一整组数的最大最小值。
}

利用分治法求一组数列的最大两个数和最小两个数
#include<stdio.h>
#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);
//这四个参数的大小关系为 min1<min2<max2<max1 
printf("min1=%d min2=%d\n max1=%d max2=%d\n",min1,min2,max1,max2);
return 0;
}
 
void max_min(int *a,int m,int n,int *min1,int *min2,int *max1,int *max2)
{
int lmin1,lmin2,lmax1,lmax2;
int rmin1,rmin2,rmax1,rmax2;
int mid;
 
if(m==n)//分治子数组中只有一个数
{
*min1=*min2=*max1=*max2=a[m];
}
 
else//分治子数组中不止一个数
    if(m==n-1)//分治子数组中仅有2个数
    {
        if(a[m]<a[n])
        {
            *min1=a[m];
            *min2=a[n];
            *max1=a[n];
            *max2=a[m];
        }
        else
        {
            *min1=a[n];
            *min2=a[m];
            *max1=a[m];
            *max2=a[n];
        }
    }//对最小情况的处理
 
    else//分治子数组中有超过2个数
    {
        mid=(m+n)/2;
        max_min(a,m,mid,&lmin1,&lmin2,&lmax1,&lmax2);
        max_min(a,mid+1,n,&rmin1,&rmin2,&rmax1,&rmax2);
        //递归对数列划分,减小处理规模
        //**************************************************
        //确定出数组中最小的两个数
        //**************************************************
        if(lmin1<rmin1)//左子数组最小数<右子数组最小数
        {
            if(lmin2<rmin1)
            {
            *min1=lmin1;
            *min2=lmin2;
            }
            else
            {
            *min1=lmin1;
            *min2=rmin1;
            }
        }
        else//右子数组最小数<左子数组最小数
            if(rmin2<lmin1)
            {
            *min1=rmin1;
            *min2=rmin2;
            }
            else
            {
            *min1=rmin1;
            *min2=lmin1;
            }
 
        //**************************************************
        //确定出数组中最大的两个数
        //**************************************************
        if(lmax1>rmax1)//左子数组最大数>右子数组最大数
        {
            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<stdio.h>
#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 <stdio.h>

//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;
}
求一组数中第二小的数

在这里插入图片描述

补充

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

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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值