分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。
求出子问题的解,就可得到原问题的解。就像前面的我说过的二分查找,也有类似的思想。
其实,学习中也有许多类似的问题可以用到分治与归并,快排与归并排序就是这样。
下里面容我记录一下我昨天下午敲的快排与归并的代码:
//快排
#include<iostream>
using namespace std;
const int maxn=1000;
int arr[maxn];
void quickshort(int arr[],int l,int r){
int st=l,ed=r;
if(st>=ed)
return ;
int temp=arr[l];
while(st<ed){
while(st<ed&&arr[ed]>=temp){
ed--;
}
arr[st]=arr[ed];
while(st<ed&&arr[st]<=temp){
st++;
}
arr[ed]=arr[st];
}
arr[st]=temp;
quickshort(arr,l,st-1);
quickshort(arr,st+1,r);
}
int main(){
cout<<"请输入要排序数的个数:(不大于1000)"<<endl;
int n;
cin>>n;
cout<<"请输入要排序的数:"<<endl;
for(int i=0;i<n;i++)
cin>>arr[i];
quickshort(arr,0,n-1);
cout<<"排序后的数:";
for(int i=0;i<9;i++)
cout<<arr[i]<<" ";
return 0;
}
#include<iostream>
#include<string.h>
using namespace std;
const int maxn=1000;
int arr[maxn];
//归并
void merge(int beg,int mid,int end){
if(end<=beg)
return ;
int beg1=beg,beg2=mid,a[end-beg];
int i=0;
while(beg1<mid&&beg2<end){
if(arr[beg1]<=arr[beg2])
a[i++]=arr[beg1++];
if(arr[beg1]>arr[beg2])
a[i++]=arr[beg2++];
}
while(beg1<mid){
a[i++]=arr[beg1++];
}
while(beg2<end){
a[i++]=arr[beg2++];
}
memcpy(arr+beg,a,sizeof(a));
}
//排序
void order(int n) {
for(int length=1;length<=n;length*=2){
int beg=0,mid=beg+length,end=mid+length;
for(;beg<n;beg+=end,mid=beg+length,end=mid+length){
if(mid>n)
mid=n;
if(end>n)
end=n;
merge(beg,mid,end);
}
}
}
int main(){
cout<<"请输入要排序数的个数:(不大于1000)"<<endl;
int n;
cin>>n;
cout<<"请输入要排序的数:"<<endl;
for(int i=0;i<n;i++)
cin>>arr[i];
order(n);
cout<<"排序后的数:";
for(int i=0;i<n;i++)
cout<<arr[i]<<" ";
return 0;
}
接下里接入正题,分治与归并能解决的问题。
逆序对
定义:
设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同。
如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。
求的时候呢,具体思路是
在归并的过程中计算每个小区间的逆序对数,进而计算出大区间的逆序对数
代码入下:
#include<iostream>
using namespace std;
int a[10000],l[10000],r[10000];
long long t;
void cmp(int ll,int mid,int rr)
{
int m=0,n=0,i,j,k;
for(i=ll;i<=mid;i++)
l[m++]=a[i];
for(i=mid+1;i<=rr;i++)
r[n++]=a[i];
i=j=0;
k=ll;
while(i<m&&j<n){
if(l[i]<=r[j])
a[k++]=l[i++];
else{
a[k++]=r[j++];
t+=m-i;//小区间逆序个数
}
}
while(i<m)
a[k++]=l[i++];
while(j<n)
a[k++]=r[j++];
}
void mergesort(int l,int r)
{
int mid;
if(l<r){
mid=(l+r)/2;
mergesort(l,mid);
mergesort(mid+1,r);
cmp(l,mid,r);
}
}
int main()
{
int n,i;
while(cin>>n){
for(i=0;i<n;i++)
cin>>a[i];
t=0;
mergesort(0,n-1);
cout<<t<<endl;
}
return 0;
}
HUD1394
Minimum Inversion Number
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 6743 Accepted Submission(s): 4112
Problem Description
The inversion number of a given number sequence a1, a2, …, an is the number of pairs (ai, aj) that satisfy i < j and ai > aj.
For a given sequence of numbers a1, a2, …, an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following:
a1, a2, …, an-1, an (where m = 0 – the initial seqence)
a2, a3, …, an, a1 (where m = 1)
a3, a4, …, an, a1, a2 (where m = 2)
…
an, a1, a2, …, an-1 (where m = n-1)
You are asked to write a program to find the minimum inversion number out of the above sequences.
Input
The input consists of a number of test cases. Each case consists of two lines: the first line contains a positive integer n (n <= 5000); the next line contains a permutation of the n integers from 0 to n-1.
Output
For each case, output the minimum inversion number on a single line.
Sample Input
10 1 3 6 9 0 8 5 7 4 2
Sample Output
16
题意:
一个由0..n-1组成的序列,每次可以把队首的元素移到队尾,
求形成的n个序列中最小逆序对数目
超时思路:刚开始我以为会挺简单,就直接枚举每一次移动新序列的逆序对数取最小就行,可是这样做肯定会超时,我提交的时候就想到了(哈哈哈,勇于尝试)。
AC思路:如果求出第一种情况的逆序列,其他的可以通过递推来搞出来,一开始是t[1],t[2],t[3]….t[N]
它的逆序列个数是N个,如果把t[1]放到t[N]后面,逆序列个数会减少t[1]个,相应会增加N-(t[1]+1)个
例如:0123 逆序对数:0
1230 逆序对数:0-0+4-(0+1)=3
2301逆序对数:3-1+4-(1+1)=4
……
注意,这道题是从0~n的序列才有这种推论。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=5000;
int cnt=0;
int a[maxn],b[maxn],temp[maxn];
void merge(int beg,int mid,int end){
if(end<=beg)
return ;
int beg1=beg,beg2=mid,i=0;
while(beg1<mid&&beg2<end){
if(b[beg1]<=b[beg2]){
temp[i++]=b[beg1++];
}
else{
temp[i++]=b[beg2++];
cnt+=mid-beg1;
}
}
while(beg1<mid)
temp[i++]=b[beg1++];
while(beg2<end)
temp[i++]=b[beg2++];
memcpy(b+beg,temp,sizeof(int) * (end-beg));
}
void order(int n){
for(int length=1;length<=n;length*=2){
for(int beg=0,mid=beg+length,end=mid+length;beg<n;beg=end,mid=beg+length,end=mid+length){
if(mid>n)
mid=n;
if(end>n)
end=n;
merge(beg,mid,end);
}
}
}
int main(){
int n;
while(cin>>n){
for(int i=0;i<n;i++){
cin>>a[i];
b[i]=a[i];
}
cnt=0;
int tcnt=999999999;//这个9必须多,第一次我提交9写少了,WA了。所以,以后用0x3f3f3f吧
order(n);
tcnt=min(tcnt,cnt);
for(int i=0;i<n;i++)
{
cnt = cnt - a[i] + n - 1 - a[i];
tcnt=min(tcnt,cnt);
}
cout<<tcnt<<endl;
}
return 0;
}
最大和连续子序列问题:
给定K个整数的序列{ N1, N2, …, NK },其任意连续子序列可表示为{ Ni, Ni+1, …, Nj },其中 1 <= i <= j <= K。最大连续子序列是所有连续子序列中元素和最大的一个,
例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和 为20。
要求编写程序得到最大和。
这种题解法很多,这里主要说一下分治解法:
直接看代码:
#include <iostream>
using namespace std;
struct MaximumSubarray{
int low;
int high;
int sum;
};
MaximumSubarray findMaxCrossingSubarr(int A[],int low,int mid,int high){
int left_sum=0x80000000;//最小负数
int right_sum=0x80000000;//最小负数
int max_left=0;
int max_right=0;
int sum=0;
MaximumSubarray maxSubarr={0};
for(int i=mid;i>=low;--i){
sum+=A[i];
if(sum>left_sum){
left_sum=sum;
max_left=i;
}
}
sum=0;
for(int i=mid+1;i<=high;++i){
sum+=A[i];
if(sum>right_sum){
right_sum=sum;
max_right=i;
}
}
maxSubarr.low=max_left;
maxSubarr.high=max_right;
maxSubarr.sum=left_sum+right_sum;
return maxSubarr;
}
/***********************************
func:求给定数组的最大子数组
para:A:数组;low:左下标;high:右下标
***********************************/
MaximumSubarray findMaxSubarr(int A[],int low,int high){
MaximumSubarray maxSubarrLeft={0};
MaximumSubarray maxSubarrRight={0};
MaximumSubarray maxSubarrCross={0};
if(high==low){ //特殊情况:只有一个元素
maxSubarrLeft.low=low;
maxSubarrLeft.high=high;
maxSubarrLeft.sum=A[low];
return maxSubarrLeft;
}
int mid=(low+high)/2;
maxSubarrLeft=findMaxSubarr(A,low,mid);
maxSubarrRight=findMaxSubarr(A,mid+1,high);
maxSubarrCross=findMaxCrossingSubarr(A,low,mid,high);
if(maxSubarrLeft.sum>maxSubarrRight.sum&&maxSubarrLeft.sum>maxSubarrCross.sum)
return maxSubarrLeft;
else if(maxSubarrRight.sum>maxSubarrLeft.sum&& maxSubarrRight.sum>maxSubarrCross.sum)
return maxSubarrRight;
else return maxSubarrCross;
}
int main(int argc,char* argv[]){
int n,arr[100000];
while(cin>>n){
for(int i=0,j=-1;i<n;j++,i++){
cin>>arr[i];
if(i!=0){
arr[j]=arr[i]-arr[i-1];
}
}
// for(int i=0;i<n;i++)
// cout<<arr[i]<<" ";
MaximumSubarray resMaxSubarr=findMaxSubarr(arr,0,n-2);
// cout<<"maximum subarray is:"<<endl;
cout<<resMaxSubarr.low<<endl;
cout<<resMaxSubarr.high<<endl;
cout<<resMaxSubarr.sum<<endl;
}
return 0;
}
以上是我关于分治与归并的总结!!!