当前位置:   article > 正文

分治算法总结_分治算法实验总结

分治算法实验总结

一、分治算法概述:

1.分治的基本思想:将一个难以直接解决的大问题,分解成一些规模较小的相同问题,以便各个击破,分而治之

2.使用分治时的要点:

(1)问题的规模缩小到一定程度可以很容易解决

(2)一个大问题能分解为若干个规模较小的相同的子问题

(3)这些子问题互相独立,即子问题之间不含公共的子问题

(4)子问题的解可以合并为该问题的解

3.分治法的基本步骤:

分治在每一层的递归上都有三个步骤

(1)分解问题

(2)递归的解各个子问题

(3)将子问题的解合并为原问题的解

4.分治算法的复杂度分析:

(1)将一个规模为n的实例划分为a个规模为n/b的实例,其中a个实例需要求解。分治算法的运行时间可由以下递推式确定:T(n)=aT(n/b)+f(n)。其中f(n)是分解与合并小问题所消耗的时间

(2)主定理

5.改善分治性能的方法:

(1)减少子问题个数,即减小a

(2)预处理,减少分解或合并的时间,即减小f(n)

二、大整数乘法

 

解决方案:

 

三、Strassen矩阵乘法

问题描述:A,B是两个n阶方阵,求A*B

 

解决方案1:

 

解决方案2:

 

四、棋盘覆盖

问题描述:

 

解决方案:

将原棋盘划分为四个大小相等的子棋盘,在4个子棋盘交界处用一个L型骨牌覆盖,再对四个各含一特殊方格的子棋盘递归求解

 

代码示例:

 

复杂度分析:

 

可得T(k)=O(4^k)

五、合并排序

1.合并排序,大致思想便是先将数组中的元素拆分成若干小部分,然后再将这些小部分按照顺序进行重新组合,从而实现排序。很明显,这里用到了分治法的思想,即将一个大问题分成若干个相同的小问题,因为问题规模变小了,所以解决问题的难度也随之减小

 

2.合并排序步骤:(1)将数组从中间一分为二,分解为两个子数组

(2)对每个子数组进行排序,当数组中元素个数为1时,排序结束

(3)将两个有序子数组合并为一个有效数组

3.代码示例:

void merge(int l,int q,int r){//对两个区间进行合并
    int n1=q-l+1;//n1为左侧区间长度,n2为右侧长度
    int n2=r-q;
    int i,j;
    for(i=0;i<n1;i++){//LR为两个临时数组分别存放左右区间的元素
        L[i]=a[l+i];
    }
    L[n1]=1e9;//临时数组的最后一个元素设为最大值,以便将另一区间的元素存入原数组
    for(i=0;i<n2;i++){
        R[i]=a[q+i+1];
    }
    R[n2]=1e9;
    i=0,j=0;
    for(int k=l;k<=r;k++){//左右两区间的元素分别比较大小存入原数组
        if(L[i]<=R[j]){
            a[k]=L[i++];
        }
        else{
            a[k]=R[j++]; 
        }
    }
}
void mergesort(int l,int r){//lr分别为待排序数组坐标的左右端点
    if(l==r) return;
    int q=(l+r)/2;//从中间一分为二
    mergesort(l,q);
    mergesort(q+1,r);
    merge(l,q,r);
}

4.时间效率分析:

递推式:

可得T(n)=O(nlogn)

5.合并排序的非递归算法

(1)过程示例:

 

(2)合并时可能出现的三种情况:

 代码示例:

void merge(int x[],int y[],int l,int m,int r){
    int i=l,j=m+1,k=1;//合并x[l,m]和x[m+1,r]到y[l,r]
    while((i<=m)&&(j<=r)){
        if(x[i]<=x[j]){
            y[k++]=x[i++];
        }
        else y[k++]=x[j++];
    }
    if(i>m){
        for(int q=j;q<=r;q++){
            y[k++]=x[q];
        }
    }
    else {
        for(int q=i;q<=m;q++){
            y[k++]=x[q];
        }
    }
}
void mergePass(int x[],int y[],int s){
    int i=0;
    while(i<=n-2*s){//合并大小为s的相邻子数组
        merge(x,y,i,i+s-1,i+2*s-1);
        i=i+2*s;
    }
    if(i+s<n-1){//剩下的元素个数小于2s但多于s
        merge(x,y,i,i+s-1,n-1);
    }
    else {//剩下的元素个数<=s
        for(int j=i;j<=n-1;j++){
            y[j]=x[j];
        }
    }
}
void mergeSort(int a[]){//对数组a进行合并,合并的初始长度s为1
    int s=1;
    while(s<n){
    mergePass(a,b,s);//将a中的数组合并到b
    s+=s;
    mergePass(b,a,s);
    s+=s;
    }
}
​

六、快速排序

1.基本思想:多趟排序。单趟排序的思想是在一个无序数组中取一个数key,每一趟排序的最终目的是:让key的左边的所有数小于key,key的右边都大于key

2.步骤:

(1)分解,将数组a[p,r]分解为两个子数组,使得数组a[p,q-1]中的元素<=a[q],a[q+1,r]中的元素>a[q]

(2)求解子问题,用同样方法递归对两个子序列排序

(3)合并,不需任何动作

图示:

 

3.代码示例:

void quick_sort(int a[],int l,int r){
    if(l>=r) return;
    int t=a[l+r>>1],i=l-1,j=r+1;//t为排序基准key
    while(i<j){
        while(a[++i]<t);
        while(a[--j]>t);
        if(i<j) swap(a[i],a[j]);
    }
    quick_sort(a,l,j);
    quick_sort(a,j+1,r);
}

4.时间效率分析

最好情况:已排好序,T(n)=O(nlogn)

最坏情况:完全逆序,T(n)=O(n^2)

平均:T(n)=O(nlogn).不稳定

5.改进:对基准key取随机值,即从待排序区间内随机取一个元素

int random_number(int l,int r){
    return l+rand()%(r-l+1);
}

6.快速排序与合并排序的对比:

(1)分解问题的方式不同。快速排序:按元素值划分;合并排序:按元素位置划分

(2)合并的方式不同。快速排序:无需执行任何操作;合并排序:需要一个归并过程

(3)性能不同。快速排序:规模相同时,实例不同效率也不同;合并排序:规模相同实例不同时,时间效率相同,需要O(n)的空间

七、线性时间选择

1.问题:找数组中最大元素

解决:顺序比较找最大,比较次数W(n)=n-1

2.问题:找数组中最大和最小元素

解决:分组比赛法,将元素两两分组,分别进行1v1比较,再从所有较大的元素找最大,从所有较小的元素找最小,比较次数W(n)=n/2 + 2*n/2-2=3n/2-2

3.问题:找数组中第二大元素

解决:锦标赛法。两两分组,分别进行比较,将较小的数记录在淘汰它的较大的数的链表中。找到最大的数,再从较大的数的链表中找最大。比较次数W(n)=n-1+logn-1=n+logn-2

4.问题:找数组中第k小

解决方案:

(1)随机选择算法。利用随机划分算法思想,将原问题划分为两个子问题,根据子问题所包含元素的位置来决定选择哪一个子问题继续递归求解

代码示例:

void Partition(int l,int r){
    if(l>=r) return;
    int i=l-1,j=r+1;
    int x=a[Random(l,r)];//在区间内随机取一个元素
    while(i<j){
        while(a[++i]<x);
        while(a[--j]>x);
        if(i<j) swap(a[i],a[j]); 
    }   
    if(k<=j) Partition(l,j);//j为划分结束后,左子问题中元素个数,若k<=j,第k小就在左侧
    else Partition(j+1,r);
}

时间复杂度:

最坏O(n^2).平均O(n)

(2)线性时间选择算法

思想:找到一个划分基准,使得按该基准划分出的两个子数组的长度都至少为原数组的q倍(0<q<1),则子问题规模为qn,最坏用O(n) (T(n)=T(qn)+O(n))

步骤:

 

代码示例:

int select(int a[],int p,int r,int k){
    if(r-p<5){//小于5个元素直接排序求解
        bubbleSort(p,r);
        return a[p+k-1];
    }
    for(int i=0;i<=(r-p-4)/5;i++){//(r-p-4)/5是最多组数
        int s=p+5*i;//第i组的左端点
        t=s+4;//第i组的右端点
        bubbleSort(s,t);//对一组5个元素排序
        swap(a,p+i,s+2);//将第i组的中位数与原数组的p+i位互换
    }
    int x=select(a,p,p+(r-p-4)/5,(r-p+1)/10);//x为中位数序列的中位数
    int i=partition(a,p,r,x);//i为按x划分后左端点位置
    j=i-p+1;//j为右端点位置
    if(k<=j) return select(a,p,i,k);
    else return select(a,i+1,r,k-j);
}

时间复杂度分析:

T(n)=T(子问题1)+T(子问题2)+O(n)

子问题1:将元素分n/5组,并取出中位数,T(n/5)

子问题2:每个子问题至多包含元素个数。前部分子问题包含最多n-(3n/10-6)=7n/10+6个,后者相同,所以T(7n/10+6)

所以T(n)=T(n/5)+T(7n/10+6)+O(n) ,得T(n)=O(n)

八、最接近点对问题

问题描述:

 

解决方案:

步骤:1将点集分为大致相等得两部分A和B;2分别递归求解各自最近点对da,db;3再求出一点在A另一点在B得最近点对dab;4三者得最小值即为最近点对

其中第3步详细步骤:

(1)找n个点x坐标得中位数c,以x=c垂直线将点分为两部分

(2)令d=min(da,db),筛选x坐标在[c-d,c+d]得点

(3)对于x坐标在[c-d,c]得任一点p,若右侧[c,c+d]有一点q,pq间距离构成最短,则二者距离必须小于d,则q一定落在一个d*2d得矩形中。且最多有6个点q

伪代码:

 

 其中第2步可预处理,即先输入后排序再进行操作,以此减小时间

时间效率分析:

 

九、循环赛日程表

问题描述

 

解决方案:按分治策略,将所有选手分为两半,n个选手得比赛日程表可由n/2个选手确定,递归的执行选手分割,直到剩下2个选手。

 

代码示例:

void copyToLb(int k){
    for(int i=1;i<=k;i++){
        for(int j=1;j<=k;j++){
            a[i+k][j]=a[i][j]+k;
        }
    }
}
void copyToRt(int k){
    for(int i=1;i<=k;i++){
        for(int j=1;j<=k;j++){
            a[i][j+k]=a[i+k][j];
        }
    }
}
void copyToRb(int k){
    for(int i=1;i<=k;i++){
        for(int j=1;j<=k;j++){
            a[i+k][j+k]=a[i][j];
        }
    }
}
void Table(int n){
    if(n==2){
        a[1][1]=1;
        a[2][1]=2;
        a[1][2]=2;
        a[2][2]=1;
    }
    else {
        Table(n/2);
        copyToLb(n/2);
        copyToRt(n/2);
        copyToRb(n/2);
    }
} 

时间效率分析:

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/716366
推荐阅读
相关标签
  

闽ICP备14008679号