分治基本概念
把一个任务,分成形式与原任务相同,但规模更小的几个部分任务(通常是两部分)
分别完成,或者只需要选一部完成。然后再处理完成后的这一个或几个部分的结果,实现整个任务的完成。
对于一些枚举,可以考虑不从头尾枚举;中间二分分成两半,一半一半得来考虑
例子——称假币
思想:二分,二分接下去分(递归的二分查找)
典型分治:归并排序
- 数组排序任务分解:
- 把前一半排序(这个会递归,把前一半的前一半排序,把前一半的后一半排序)
- 把后一半排序
- 把两半归并到一个新的有序数组,然后拷贝到原数组,排序完成
#include<iostream>
using namespace std;
void Merge(int a[],int s,int m,int e,int tmp[]){
//将数组a的局部a[s,m]和a[m+1,e]合并到tmp,并保证tmp有序,然后拷贝回a[s,m]
//归并操作的时间复杂度是O(e-m+1)即o(n)
int pb=0;
int p1=s,p2=m+1;
while(p1<=m&&p2<=e){
if(a[p1]<a[p2]) tmp[pb++]=a[p1++];
else tmp[pb++]=a[p2++];
}
while(p1<=m){
tmp[pb++]=a[p1++];
}
while(p2<=e){
tmp[pb++]=a[p2++];
}
for(int i=0;i<e-s+1;i++){
a[s+i]=tmp[i];
}//拷贝操作
}//合并_algorithm中有merge算法
void MergeSort(int a[],int s,int e,int tmp[]){
if(s<e){
int m=s+(e-s)/2;
MergeSort(a,s,m,tmp);
MergeSort(a,m+1,e,tmp);
Merge(a,s,m,e,tmp);
}
//这个递归到最后,会是两个数组只要一个元素,merge里本身有比较后才插入tmp里,因此会达到排序的效果
}//排序_algorithm中快速排序,sort
int main(){
int a[10]={13,27,19,2,8,12,2,8,30,89};
int b[10];
int size=sizeof(a)/sizeof(int);
MergeSort(a,0,size-1,b);
for(int i=0;i<size;i++){
cout<<a[i]<<",";
}
cout<<endl;
return 0;
}
典型分治:快速排序
- 数组排序任务:
- 设K=a[0],将k挪到适当位置,使得比k小的元素在k的左边,比k大的元素在k的右边;和k相等的,不关心的在k的左右出现均可,应在O(n)内完成
- 将k左边的部分快速排序
- 将k右边的部分快速排序(递归而且二分(元素个数不一定是平均二分)
- 因此,运气最坏的时候,会达到O(n^2)
- 如果十分在意,可以先把数组里的元素随机打乱
- 可以用algorithm中的random_shuffle()函数
/*难点一:将K挪到适当位置,利用奇偶数次移动次数比较大小交换值*/
#include<iostream>
using namespace std;
void swAp(int &a,int &b){
int tmp=a;
a=b;
b=tmp;
}//algorithm中有swap()函数
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(j>i&&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,s);
}
int main(){
int a[]={93,27,30,2,8,12,2,8,30,89};
int size=sizeof(a)/sizeof(a[0]);
QuickSort(a,0,size-1);
for(int i=0;i<size;i++){
cout<<a[i]<<" ";
}
cout<<endl;
return 0;
}
例题——输出前m大的函数
思路:1.排序后输出,复杂度是O(nlogn)
2.用分治处理,复杂度是O(n+mlogm)
当m比n小很多的情况下比较好用
分支处理思路:把前m大的都弄到数组的最右边,然后把这最右边m个元素排序,再输出
难点:前m大的都弄到数组的最右边,而且时间得是O(n)才有效
-
引入操作arrangeright(K):把数组前K大都弄到最右边
-
设k=a[0],将key挪到适当位置,使得比key小的元素都在key左边,比key大的都在key右边;
-
选择数组的前部或后部在进行arrangeright操作
如果右边的a个,刚好等于m个,则done;
如果右边的a个,多于m个,则arrangeright(k)
如果右边的a个,小于m个,则对左边进行arrangeright(k-a)
#include<iostream>
#include<algorithm>
using namespace std;
int m;
void SwAp(int &a,int &b){
int tmp=a;
a=b;
b=tmp;
}
void ArrangeRight(int *a,int s,int e,int k){
if(s>=e){
return;
}
int num=a[s];
int i=s,j=e;
while(i!=j){
while(j>i&&a[j]>=num){
j--;
}
SwAp(a[i],a[j]);
while(j>i&&a[i]<=num){
i++;
}
SwAp(a[i],a[j]);
}
if(e-i+1>k) return ArrangeRight(a,i+1,e,k);
else if(e-i+1<k) return ArrangeRight(a,0,i-1,k-(e-i+1));
}
int main(){
int n,num[100050];
cin>>n;
for(int i=0;i<n;i++){
cin>>num[i];
}
cin>>m;
ArrangeRight(num,0,n-1,m);
sort(num+(n-m),num+n);
for(int i=0,j=n-1;i<m;i++,j--){
cout<<num[j]<<endl;
}
}
例题——求排列的逆序数
思路:1)求左半边逆序数和右半边逆序数
2)再算有多少逆序是由左半边取一个数和右半边取一个数构成。要求O(n)
难点:在O(n)的复杂度里,实现2的种类数;
- 要求左半边和右半边都是排好序的。比如,都是从大到小排序,这样左右半边都只需要从头到尾各扫一遍,就可以找出由两边各取一个数构成逆序;
- 进行完第一步,第二步排好序是没关系的
10 8 7 3| 12 11 5 2(用i,j两个指针头,10>5的时候,j不变,数右边剩下几个元素)