分治与归并

分治算法的基本思想是将一个规模为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;
}

 

 以上是我关于分治与归并的总结!!!

 

 

 

点赞