分治法
2.6棋盘覆盖
1、基本思想
当k>0时,可以将2^k * 2k的棋盘分割成4个2(k-1) * 2^(k-1)子棋盘。因为特殊格子肯定位于4个较小的子棋盘中,其余3个格子中没有特殊方格。为了将这3个格子变成特殊棋盘,可以用一个L型骨牌覆盖这3个较小棋盘的汇合处。从而将原问题转换为4个相同的子问题。递归地使用这种分割,直到棋盘简化为1x1的棋盘。
2、代码
2.7合并排序
1、基本思想
将待排序元素分成大小大致相同的两个子集合,分别对两个子集合进行排序,最终将排好序的子集合合并成所要求的排好序的集合。
2、算法步骤
① 分解:将待排的元素数组一分为二,直到子序列中只有一个元素。
② 求解:只有一个元素的序列就是有序序列。
③ 合并:将两两子序列合并,并按照增序或降序统一排序,直到得到原序列的有序序列。
3、举例
4、伪代码
5、复杂度
可以用主定理2求解
2.8快速排序
1、基本思想
2、算法步骤
1)分解:对于输入的子数组a[p:r],以a[p]为基准元素将a[p:r]划分为3段a[p:q-1],a[q],a[q+1,r]使a[p:q-1]中任何一个元素小于等于a[q],而a[q+1,r]中任何一个元素大于等于a[q]。下标q在划分过程中确定。
2)递归求解:通过递归调用快速排序算法分别对a[p:q-1]和a[q+1:r]进行排序。
3)合并:对于a[p:q-1]和a[q+1:r]的排序是就地进行的,所以在a[p:q-1]和a[q+1:r]排好序后,不需要进行任何计算,就已经排好序了。
3、举例
仅一轮,以第一个元素为基准元素。
4、代码
// An highlighted block
int findprivot(int array[],int begin,int end){
return (begin+end)/2; //取中间值为轴值
}
bool prior(int pivot,int &elem){
return pivot<elem; //增序
}
void swap(int array[],int i,int j){
int temp=array[j];
array[j]=array[i];
array[i]=temp;
}
int partition(int array[],int l,int r,int& pivot){
do{
while(prior(array[++l],pivot));
while((l<r)&&prior(pivot,array[--r]));
swap(array,l,r);
} while(l<r);
return l;
}
void qsort(int array[],int begin,int end){
if(end<=begin)return ;
int pivotindex = findprivot(array,begin,end);//轴值
swap(array,pivotindex,end);//把轴值放到数组的最后一个元素
int k=partition(array,begin-1,end,array[end]);//划分
swap(array,k,end);//把轴值放到正确位置
qsort(array,begin,k-1);
qsort(array,k+1,end);
}
5、复杂度
2.9线性时间选择
一、选择最大值和最小值
1、基本思想
① 分解:将原问题划分为规模较小的子问题,直到子问题中只有一个元素。
② 求解:只有一个子问题的元素既是子问题的最大值,也是子问题的最小值。
③ 合并:将子问题的解合并为原问题的解,两两合并,最小值取子问题的最小值中的最小, 最大值取子问题的最大值中的最大。直到得到原问题的最大最小值。
2、伪代码
3、复杂度分析
因为算法的比较大小值是常数级操作,将原问题划分为左右两个子问题,所以时间复杂度递归表达式为:
当n=1时,T(n)=O(1);
当n>1时,T(n)=2*T(n/2)+O(1);
所以,分治算法的时间复杂度为O(logn)。
二、查找第k小的值
1、算法思想
找到划分的基准
1、将n个输入元素划分成[n/5]个组。
2、用任意一种排序算法,将每个组中的元素排序,并取出每组的中位数,共[n/5]个。
3、递归调用select找到这[n/5]个元素的中位数。如果[n/5]是偶数,就找它的中位数中较大的一个。以这个元素作为划分的基准。
2、举例
4、复杂度
3、代码
// An highlighted block
void select(int* a,int p,int r,int k)
{
if (r-p<75) // 旧方法
{
//用某个简单排序算法对数组a[p:r]进行排序
//return a[p+k-1];
return RandSelect(a, p, r, k); //排序并返回排序后的中位数a[p+k-1]
}
else
{
// 新方法
for ( int i = 0; i<(r-p)/5; i++ ){
int s=p+5*i; // 段i 的起点
int t=s+4; // 段i 的终点
Sortysh(a,s,t); // 对本段中5 个元素排序
int tmp=a[s+2];a[s+2]=a[p+i];a[p+i]=tmp; // 第3 小到p+i 处
}
// 各段的中位数已经移到了a[p],a[p+1],…a[p+ 段数(r-p)/5]
// 用同样的算法找中位数的中位数
int x =select(p, p+(r-p)/5, (r-p)/5/2); //x 是中位数的中位数
int i=partition(p,r,x) ;// 按此进行普通划分 ,为0.75 原<0.9
j=i-p+1; // 搜索速度很快 结束条件
if (k<j) {return select(p,i,k);}
else if (k==j) { return a[i];}
else {return select(i+1,r,k-j);}
}
}
2.11循环赛日程表
1、基本思想
按照分治策略,可以将所有选手对分成两半,n个选手的比赛日程可以通过为n/2个选手设计的比赛日程表来决定。递归地用这种一分为二的策略对选手进行分割,直到只剩下两个选手时,就只要让这两个选手进行比赛了。
2、表格
n行n列的表格,第i选手在第j-1天遇到的选手。
填充原则:对角线填充
3、代码
// An highlighted block
for(int i=m+1;i<=2*m;i++) { //i控制行
for(int j=m+1;j<=2*m;j++) //j控制列
{
a[i][j+(t-1)*m*2]= a[i-m][j+(t-1)*m*2-m];
/*右下角的值等于左上角的值 */
a[i][j+(t-1)*m*2-m] =a[i-m][j+(t-1)*m*2];
/*左下角的值等于右上角的值 */
}
}
集合划分问题–引申:数组中位数O(n)时间复杂度求法
题目
设S 是n (n 为偶数)个不等的正整数的集合,要求将集合S 划分为子集S1 和S2 ,使得| S1|=| S2|=n/2 ,且两个子集元素之和的差达到最大。
基本思想
把前n/2小的元素尽可能放到S1中,剩下的元素放到s2中。可以采用类似快排的方法,只要找到了中位数将比中位数小的放到左边,把中位数大的放到右边就可以了。
利用快速排序的划分思想,设正整数集合为数组S,划分为前半个数组S1,后半个数组为S2,若第一次划分的轴值是中位数,则返回;若不是继续划分中位数所在的部分。
代码
链接: link
复杂度
类似于线性时间求解中位数,时间复杂度也为O(n)。