分治法

分治法

基本思想

将一个难以直接解决的大问题,分解为规模较小的相同子问题,直至这些子问题容易直接求解,并且可以利用这些子问题的解求出原问题的解。各个击破,分而治之。

适用特征

  • 该问题的规模小到一定的程度就可以容易解决
  • 该问题可以分解为若干个规模较小的相同问题
  • 利用该问题分解出的子问题的解可以合并为该问题的解
  • 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题

基本步骤:分解、求解子问题、合并

divide-and-conquer(P)
{
	if(|P|<=n_0)adhoc(P);//求解最小的子问题
	divide P into smaller subinstances P_1,P_2,...,P_k;//分解
	for(i=1;i<=k;i++)
		y_i = divide-and-conquer(P_i);//递归求解子问题
    return merge(y_1,...,y_k);//合并
}

求解方法

  • 迭代法
  • 递归树

例题

1.全排列问题

问题

R R R是由n个元素构成的序列集合, R = { r 1 , r 2 , . . . , r n } R=\{r_1,r_2,...,r_n\} R={r1,r2,...,rn},求R的全排列 p e r m ( R ) perm(R) perm(R)

思路

  • 若R中只有一个元素 r {r} r,则 p e r m ( R ) = ( r ) perm(R)=(r) perm(R)=(r)

  • 若R中只有两个元素 { r 1 , r 2 } \{r_1,r_2\} {r1,r2},则 p e r m ( R ) = ( r 1 ) p e r m ( R 1 ) ∪ ( r 2 ) p e r m ( R 2 ) perm(R)=(r_1)perm(R_1)\cup (r_2)perm(R_2) perm(R)=(r1)perm(R1)(r2)perm(R2),其中, R i = R − { r i } R_i=R-\{r_i\} Ri=R{ri}

  • 若R中有3个元素 { r 1 , r 2 , r 3 } \{r_1,r_2,r_3\} {r1r2r3},则 p e r m ( R ) = ( r 1 ) p e r m ( R 1 ) ∪ ( r 2 ) p e r m ( R 2 ) ∪ ( r 3 ) p e r m ( R 3 ) perm(R)=(r_1)perm(R_1)\cup (r_2)perm(R_2)\cup (r_3)perm(R_3) perm(R)=(r1)perm(R1)(r2)perm(R2)(r3)perm(R3)
    p e r m ( R ) = { r n = 1 ∪ ( r i ) p e r m ( R i ) n > 1 perm(R)= \begin{cases} r & n=1\\ \cup(r_i)perm(R_i)& n>1 \end{cases} perm(R)={r(ri)perm(Ri)n=1n>1

代码

template<class Type>
inline void Swap(Type& a,Type& b){
    Type temp = a;
    a = b;
    b = temp;
}


template<class Type>
void Perm(Type list[],int k,int m){             //产生list[k:m]的所有排列
    if(k == m){                                 //只剩下一个元素
        for(int i=0;i<=m;i++)
            cout<<list[i];
        cout<<endl;
    }
    else{
        for(int i=k;i<=m;i++){                  //递归产生排序
            Swap(list[k],list[i]);
            Perm(list,k+1,m);
            Swap(list[k],list[i]);
        }
    }
}

2.整数划分问题

问题

将正整数 n n n表示成一系列正整数之和, n = n 1 + n 2 + . . . + n k n=n_1+n_2+...+n_k n=n1+n2+...+nk,其中 n 1 > = n 2 > = . . . > = n k > = 1 , k = 1 n_1>=n_2>=...>=n_k>=1,k=1 n1>=n2>=...>=nk>=1,k=1

求正整数 n n n的不同划分个数 p ( n ) p(n) p(n)

思路

q ( n , m ) q(n,m) q(n,m):表示最大加数 n 1 n_1 n1不大于m的划分个数
q ( n , m ) = { 1 n = 1 , m = 1 q ( n , n ) m > n 1 + q ( n , n − 1 ) m = n q ( n , m − 1 ) + q ( n − m , m ) 1 < m < n q(n,m)= \begin{cases} 1 & n = 1,m = 1\\ q(n,n) & m > n\\ 1+q(n,n-1) & m = n\\ q(n,m-1)+q(n-m,m)& 1 < m < n \end{cases} q(n,m)=1q(n,n)1+q(n,n1)q(n,m1)+q(nm,m)n=1,m=1m>nm=n1<m<n

代码

int q(int n,int m){         //n的最大加数不超过m
    if(n == 1||m == 1)return 1;
    else if(m > n)return q(n,n);
    else if(m == n)return 1+q(n,m-1);
    else if (m>1 &&m<n)return q(n,m-1)+q(n-m,m);
    else return 0;
}

3.汉诺塔问题

4.二分搜索

问题

给定已按升序排好序的 n n n个元素 a [ 0 : n − 1 ] a[0:n-1] a[0:n1],现要在这 n n n个元素中找出一个特定元素 x x x

代码

template<class Type>
int BinarySearch(Type a[],const Type& x, int n){    //搜索x
    int left = 0;
    int right = n-1;
    while(left <= right){
        int middle = (left+right)/2;
        if(a[middle] == x)return middle;
        if(a[middle] > x)right = middle-1;
        else left = middle+1;

    }
    return -1;
}

5.合并排序

问题

给定一个可排序的 n n n个元素序列,对他们按照非降序方式重新排列

思想

将待排序元素分成大致相同的两个子集,分别进行排序后进行合并

代码

template<class Type>
void Merge(Type a[],int left,int i,int right){

    int length = right-left+1;
    int list_l = left;
    int list_r = i+1;
    int k;
    Type *b = new Type[length];
    for(k = 0;k<length&&list_l<i+1&&list_r<right+1;k++){
        if(a[list_l]<a[list_r]){
            b[k] = a[list_l];
            list_l += 1;
        }
        else{
            b[k] = a[list_r];
            list_r += 1;
        }
    }
    //处理剩余的某个数组
    if(list_l !=i+1){//第一个数组有剩余
        for(;k<length;k++)b[k] = a[list_l++];
    }
    if(list_r != right+1){//第二个数组有剩余
        for(;k<length;k++)b[k] = a[list_r++];
    }

    for(int j=0;j<length;j++){
        a[left+j] = b[j];
    }
    delete[] b;
}


template<class Type>
void MergeSort(Type a[],int left,int right){
    if(left<right){
        int i = (left+right)/2;
        MergeSort(a,left,i);
        MergeSort(a,i+1,right);
        Merge(a,left,i,right);
    }
}

6.快速排序

问题

给定一个可排序的 n n n个元素序列,对他们按照非降序方式重新排列

思想

对待排序数组不断拆分,拆分的同时不断交换元素,归为分裂点元素。记录的比较和交换是从两端向中间进行,记录每次移动的距离较大,因而总的比较和移动次数较少

代码

template<class Type>
int Partition(Type a[],int p,int r){
    int i = p,j = r+1;
    Type x = a[p];      //基准元素
    //将小于x的元素交换到左边区域,将大于x的元素交换到右边区域
    while(true){
        while(a[++i] < x && i < r);
        while(a[--j] > x);
        if(i >= j)break;
        Swap(a[i],a[j]);
    }
    a[p] = a[j];
    a[j] = x;
    return j;
}

template<class Type>
void QuickSort(Type a[],int p,int r){
    int q = Partition(a,p,r);   //以p为分裂点,整理p点两边的元素
    QuickSort(a,p,q-1);
    QuickSort(a,q+1,r);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愤怒的卤蛋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值