目录
概念
分治即“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
步骤:
- 分解:把原问题分解成若干个规模较小,相互独立,与原问题形式相同的子问题。
- 解决:若子问题规模较小二容易被解决则直接解,否则递归地解各个子问题。
- 合并:将各个子问题的解合并为原问题的解。
例题
例题一:归并排序
数组排序任务可以如下完成:
(1)把前一半排序
(2)把后一半排序
(3)把两半归并到一个新的有序数组,然后再拷贝回原数组,排序完成。
#include <iostream>
using namespace std;
//归并排序,复杂度是O(nlogn)
int a[10]={2,8,12,66,36,24,30,27,48,15};
int t[10];
//将a[s,m]和a[m+1,e]合并到t,再拷贝回a,合并的复杂度为O(n)
void Merge(int a[],int s,int m,int e,int t[]){
int p1=s,p2=m+1;//分别指向前一半和后一半的第一个数字
int p=0;
while(p1<=m&&p2<=e){
if(a[p1]<a[p2])
t[p++]=a[p1++];
else
t[p++]=a[p2++];
}
while(p1<=m)
t[p++]=a[p1++];
while(p2<=e)
t[p++]=a[p2++];
for(int i=0;i<e-s+1;i++){
a[s+i]=t[i];
}
}
//排s~e这一段
int MergeSort(int a[],int s,int e,int t[]){
if(s<e){
int m=s+(e-s)/2;
MergeSort(a,s,m,t);//前一半排序
MergeSort(a,m+1,e,t);//后一半排序
Merge(a,s,m,e,t);//合并
}
}
int main()
{
int size=sizeof(a)/sizeof(int);
MergeSort(a,0,size-1,t);
for(int i=0;i<size;i++)
cout<<a[i]<<" ";
cout<<endl;
return 0;
}
例题二:快速排序
数组排序任务可以如下完成:
(1)设k=a[0],将k挪到适当位置,使得比k小的元素都在k左边,比k大的元素都在k右边,和k相等的,不关心,在k左右出现均可(0 (n)时间完成)
(2)把k左边的部分快速排序
(3)把k右边的部分快速排序
#include <iostream>
using namespace std;
//快排
void swap(int & a,int & b){
int t=a;
a=b;
b=t;
}
void QuickSort(int a[],int s,int e){
if(s>=e)
return;
int k=a[s];
int i=s,j=e;
while(i!=j){
while(i<j&&a[j]>=k)
j--;
swap(a[i],a[j]);
while(i<j&&a[i]<=k);
i++;
swap(a[i],a[j]);
}//处理完成后,a[i]=k
QuickSort(a,s,i-1);
QuickSort(a,i+1,e);
}
int main()
{
int a[10]={27,9,12,60,36,24,15,25,22,20};
QuickSort(a,0,9);
for(int i=0;i<10;i++)
cout<<a[i]<<" ";
cout<<endl;
return 0;
}
例题三:输出前m大的数
描述:
给定一个数组包含n个元素,统计前m大的数并且把这m个数从大到小输出。
输入:
第一行包含一个整数n,表示数组的大小。n < 100000。
第二行包含n个整数,表示数组的元素,整数之间以一个空格分开每个整数的绝对值不超过00000000。
第三行包含一个整数m,m < n。
输出:
从大到小输出前m大的数,每个数一行。
排序后再输出:复杂度0 (nlogn)
用分治处理:复杂度0 (n+mlogm)
思路:把前m大的都弄到数组最右边,然后对这最右边m个元素排序,再输出
关键:O(n)时间内实现把前m大的都弄到数组最右边
引入操作arrangeRight (k) :把数组(或数组的一部分)前k大的都弄到最右边
- 设key=a[0], 将key挪到适当位置,使得比key小的元素都在key左边,比key大 的元素都在key右边(线性时间完成)
- 选择数组的前部或后部再进行arrangeRight操作
-
- a=k,结束
a> k,对此a个元素再进行arrangeRigth(k) .
a< k,对左边b个元素再进行arrangeRight(k-a)
- a=k,结束
#include <iostream>
using namespace std;
int a[10]={8,12,16,10,64,24,27,36,72,22};
void swap(int &a,int &b){
int t=a;
a=b;
b=t;
}
void arrangeRight(int m,int s,int e){
if(s>=e)
return;
int k=a[s];
int i=s,j=e;
while(i!=j){
while(i<j&&a[j]>=k)
j--;
swap(a[i],a[j]);
while(i<j&&a[i]<=k)
i++;
swap(a[i],a[j]);
}
int a=e+1-j;
if(a==m)
return;
else if(a>m)//在右边再次取m个
arrangeRight(m,j+1,e);
else//在左边再取m-a个
arrangeRight(m-a,s,j);
}
int main()
{
arrangeRight(3,0,9);
for(int i=9,j=0;j<3;j++,i--)
cout<<a[i]<<" ";
return 0;
}
例题四:求排列的逆序数
考虑1,2,……,n (n <= 100000) 的排列i1,i2,……,in,如果其中存在j,k,满足j < k且ij> ik, 那么就称(ij, ik)是这个排列的一个逆序。
一个排列含有逆序的个数称为这个排列的逆序数。例如排列263451 含有8个逆序(2,1), (6,3), (6,4), (6,5), (6,1), (3,1), (4,1), (5,1),因此该排列的逆序数就是8。
现给定1,2, ... n的一个排列,求它的逆序数。
分治:0 (nlogn)
1) 将数组分成两半,分别求出左半边的逆序数和右半边的逆序数
2)再算有多少逆序是由左半边取一个数和右半边取一个数构成(要求0(n)实现)
- 左半边和右半边都是排好序的。比如,都是从大到小排序的。这样,左右半边只需要从头到尾各扫一遍,就可以找出由两边各取一个数构成的逆序个数
#include<iostream>
#include<cstdio>
using namespace std;
const int N=100005;
int a[N];
int b[N]; //储存中间数组
long long ans=0; //逆序数的计数
void ccount(int a[],int s,int m,int e,int b[]); //求逆序数且进行排序
void array(int a[],int s,int e,int b[]); //分治
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
array(a,0,n-1,b);
cout<<ans;
return 0;
}
void ccount(int a[],int s,int m,int e,int b[]){
int i=s,j=m+1;
while(i<=m&&j<=e){
if(a[j]>=a[i]){
j++;
}
else if(a[j]<a[i]){
ans+=e-j+1;
i++;
}
}
i=s,j=m+1;
int p=0;
//排序
while(i<=m&&j<=e){
if(a[i]>a[j]){
b[p++]=a[i++];
}else{
b[p++]=a[j++];
}
}
while(i<=m)
b[p++]=a[i++];
while(j<=e)
b[p++]=a[j++];
for(int k=0;k<e-s+1;k++)
a[s+k]=b[k];
}
void array(int a[],int s,int e,int b[]){
if(s<e){
int m=s+(e-s)/2; //分治
array(a,s,m,b);
array(a,m+1,e,b);
ccount(a,s,m,e,b); //合并
}
}