分治的思想,最经典的两个例子首先是排序的两个例子
一个是快排 一个是归并排序【请看到这里的你,去复习一下排序算法】
快速排序
先总体再局部
int partition(int nums[],int left,int right){
int i=left;
int j=right;
int temp=nums[i];
while(i<j){//注意下面内层while的顺序
while(i<j && nums[j]>=temp) {j--;}
nums[i]=nums[j];
while(i<j && nums[i]<=temp) {i++;}
nums[j]=nums[i];
}
nums[i]=temp;
return i;
}
void quickSort(int nums[],int left,int right){
if(left<right) {
int p = partition(nums, left, right);
quickSort(nums, left, p - 1);
quickSort(nums, p + 1, right);
}
}
/**使用栈的非递归快速排序**/
template<typename Comparable>
void quicksort2(vector<Comparable> &vec,int low,int high){
stack<int> st;
if(low<high){
int mid=partition(vec,low,high);
if(low<mid-1){
st.push(low);
st.push(mid-1);
}
if(mid+1<high){
st.push(mid+1);
st.push(high);
}
//其实就是用栈保存每一个待排序子串的首尾元素下标,下一次while循环时取出这个范围,对这段子序列进行partition操作
while(!st.empty()){
int q=st.top();
st.pop();
int p=st.top();
st.pop();
mid=partition(vec,p,q);
if(p<mid-1){
st.push(p);
st.push(mid-1);
}
if(mid+1<q){
st.push(mid+1);
st.push(q);
}
}
}
}
作业例题 【使用归并排序】
先局部再总体,需要一个临时数组。
F:求逆序对数
总时间限制: 500ms 内存限制: 65536kB
描述
对于一个长度为N的整数序列A,满足i < j 且 Ai > Aj.的数对(i,j)称为整数序列A的一个逆序
请求出整数序列A的所有逆序对个数
输入
输入包含多组测试数据,每组测试数据有两行
第一行为整数N(1 <= N <= 20000),当输入0时结束
第二行为N个整数,表示长为N的整数序列
输出
每组数据对应一行,输出逆序对的个数
样例输入
5
1 2 3 4 5
5
5 4 3 2 1
1
1
0
样例输出
0
10
0
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int n;
int num[20005];
int temp[20005];
int res;
void Merge(int low,int mid,int high){
int i=low;
int j=mid+1;
int tmp=low;
while (i<=mid && j<=high){
if(num[j]<num[i])
{
//如果aj
res+=mid-i+1;
temp[tmp++]=num[j++];
}
else{
temp[tmp++]=num[i++];
}
}
while (i<=mid) temp[tmp++]=num[i++];
while (j<=high) temp[tmp++]=num[j++];
for(int k=low;k<=high;k++){
num[k]=temp[k];
}
}
void mergesort(int low,int high){
if(low<high){
int mid=(low+high)/2;
mergesort(low,mid);
mergesort(mid+1,high);
Merge(low,mid,high);
}
}
int main(){
while(cin>>n && n){
for(int i=0;i<n;i++){
cin>>num[i];
}
res=0;
mergesort(0,n-1);
cout<<res<<endl;
}
return 0;
}
拓展
重要逆序对
总时间限制: 10000ms 单个测试点时间限制: 1000ms
内存限制: 65536kB
描述
给定N个数的序列a1,a2,…aN,定义一个数对(ai, aj)为“重要逆序对”的充要条件为 i < j 且 ai > 2aj。求给定序列中“重要逆序对”的个数。
输入
第一行为序列中数字的个数N(1 ≤ N ≤ 200000)。
第二行为序列a1, a2 … aN(0 ≤a ≤ 10000000),由空格分开。
输出
输出一个整数,为给序列中“重要逆序对”的个数。
样例输入
10
0 9 8 7 6 5 4 3 2 1
样例输出
16
提示
如果使用printf输出long long类型,请用%lld数据范围
对于40%的数据,有N ≤ 1000。
同样的使用归并排序,在归并时先用一个指针扫一次,左边如果有满足条件的,那么它的后面的数字都满足条件。与上题类似,单看merge。
(注意这题的输出是long long类型)
void Merge(int low,int mid,int high){
int i=low;
int j=mid+1;
int tmp=low;
int pointer=low;
while (i<=mid && j<=high){
if(num[j]<num[i])
{
while(pointer<=mid && 2*num[j]>=num[pointer]){
pointer++;
}
res+=mid+1-pointer;
// res+=mid-i+1;
temp[tmp++]=num[j++];
}
else{
temp[tmp++]=num[i++];
}
}
while (i<=mid) temp[tmp++]=num[i++];
while (j<=high) temp[tmp++]=num[j++];
for(int k=low;k<=high;k++){
num[k]=temp[k];
}
}
进阶版LeetCode 315. Count of Smaller Number After Self
求每个位置元素对应的逆序对,仍可以用归并排序。
Java merge sort solution
为了保持原数组不动,需要维护一个index数组
注意每次从right中移数,rightcount++;从left中移数,把count数组加上rightcount
如[2,3,4] [1,2,3] 左边是2的时候,rightcount=1,把2移入,对应2的count加上1。
平面最近点对
步骤1:根据点的y值和x值对S中的点排序。
步骤2:找出中线L将S划分为SL和SR
步骤3:将步骤2递归的应用解决SL和SR的最近点对问题,并令d=min(dL,dR)。
步骤4:将L-d ~ L+d内的点以y值排序,对于每一个点(x1,y1)找出y值在y1-d~y1+d内的接下来的7个点,计算距离为d’。如果d’小于d,令d=d’,最后的d值就是答案。
平面最近点对实验
作业题
输入
第一行一个整数T,表示有T组测试数据
每组测试数据第一行一个整数N(2<=N<=1e5)表示平面有N个点
接下来有N行,每行两个整数X Y(-1e9<=X,Y <=1e9)表示点的坐标
输出
输出最近点对的距离,精确到小数点后6位
样例输入
1
3
1 0
1 1
0 1
样例输出
1.000000
#include
#include
#include
#include
#define INF 1e20
using namespace std;
int t;
int n;
struct point{
double x;
double y;
};
point points[100005];
point temp[100005];
double getDistance(point point1,point point2){
return sqrt((point1.x-point2.x)*(point1.x-point2.x)+(point1.y-point2.y)*(point1.y-point2.y));
}
bool cmpxy(point a,point b)
{
if(a.x==b.x)
return a.y<b.y;
return a.x<b.x;
}
bool cmpy(point a,point b){
return a.y<b.y;
}
double getClosestPoint(int left,int right){
double dis=INF;
if(left==right)
return dis;
if(left+1==right)
return getDistance(points[left],points[right]);
int mid=(left+right)/2;
double disLeft=getClosestPoint(left,mid);
double disRight=getClosestPoint(mid+1,right);
dis=min(disLeft,disRight);
int k=0;
for(int i=left;i<=right;i++){
if(fabs(points[mid].x-points[i].x)<=dis){
temp[k++]=points[i]; //存储中间区域的点
}
}
sort(temp,temp+k,cmpy);//按照y升序排列
for(int i=0;i<k;i++){
for(int j=i+1;j<k;j++){
double d=getDistance(temp[i],temp[j]);
if(dis>d){
dis=d;
}
}
}
return dis;
}
int main(){
cin>>t;
while(t--){
cin>>n;
for(int i=0;i<n;i++){
int x,y;
cin>>x>>y;
points[i].x=x;
points[i].y=y;
}
sort(points,points+n,cmpxy);
//保留6位小数
cout<<fixed<<setprecision(6)<<getClosestPoint(0,n-1)<<endl;
}
return 0;
}
随机快排
随机选择pivot,可以避免排好序或者全逆序的worst case
三向切分快速排序,可以避免过多的重复元素带来的效率问题
维护两个指针,lo~lt小于pivot gt ~ hi大于pivot 中间等于pivot
void qsort3way ( int lo,int hi )
{
if( lo>=hi ) return; //单个元素或者没有元素的情况
int lt=lo;
int i=lo+1; //第一个元素是切分元素,所以指针i可以从lo+1开始
int gt=hi;
int v=a[lo];
while( i<=gt )
{
if( a[i]<v ) //小于切分元素的放在lt左边,因此指针lt和指针i整体右移
swap( lt++,i++ );
else if ( a[i]>v ) //大于切分元素的放在gt右边,因此指针gt需要左移
swap( i,gt-- );//这里不需要i++
else
i++;
}
//lt-gt的元素已经排定,只需对it左边和gt右边的元素进行递归求解
qsort3way( lo,lt-1 );
qsort3way( gt+1,hi );
}
对于包含大量重复元素的数组,这个算法将排序时间从O(NlogN)降到了O(N)。
median and selection problem
选取中位数(第k大的数)
Quick-select算法
T(n)=T(n/2)+ θ ( n ) \theta(n) θ(n)
int partition(int nums[],int left,int right){
int i=left;
int j=right;
int temp=nums[i];
while(i<j){//注意下面内层while的顺序
while(i<j && nums[j]>=temp) {j--;}
nums[i]=nums[j];
while(i<j && nums[i]<=temp) {i++;}
nums[j]=nums[i];
}
nums[i]=temp;
return i;
}
int quickSelect(int nums[],int left,int right,int k){
int p = partition(nums, left, right);
if(p==k-1) return nums[p];
else if(p<k-1){
return quickSelect(nums, p + 1, right);
}
else if(p>k-1){
return quickSelect(nums, left, p - 1);
}
}
LeetCode 类似题
可以用quickselect,也可以用堆
kth-largest-element in an array
书上例题以及拓展
找peak find peak element
选取一个切分点mid,这个点三种情况
①右边比它小,则向左边找②右边比它大,则向左边找③直到两个指针碰撞,则输出。 二分查找的思想。
排过序的旋转数组的最小值 find minimum in Rotated Sorted array
相当于找到那个断崖
选取一个切分点,和最后一个元素比,如果比最后一个元素大到左边,如果比最后一个元素小到右边
终止条件是元素个数和右边大于左边
股票买一次收益最大 best time to buy and sell stock
动态规划可以是线性时间复杂度的,使用分治法,可以达到nlogn
public int helper(int[]prices,int low,int high){
int max=Integer.MIN_VALUE;
if(low==high) return 0;
int mid=(low+high)/2;
int leftMax=helper(prices,low,mid);
int rightMax=helper(prices,mid+1,high);
int subMax=Math.max(leftMax,rightMax);
int leftMIN=Integer.MAX_VALUE;
for(int i=low;i<=mid;i++){
leftMIN=Math.min(leftMIN,prices[i]);
}
int rightMAX=Integer.MIN_VALUE;
for(int i=mid+1;i<=high;i++){
rightMAX=Math.max(rightMAX,prices[i]);
}
return Math.max(Math.max(0,rightMAX-leftMIN),subMax);
}
public int helper(int[]nums,int left,int right){
if(left==right) return nums[left];
int mid=(left+right)/2;
int leftmax=helper(nums,left,mid);
int rightmax=helper(nums,mid+1,right);
int crossmax=Integer.MIN_VALUE;
int temp=0;
for(int i=mid;i>=left;i--){
temp+=nums[i];
crossmax=Math.max(crossmax,temp);
}
temp=crossmax;
for(int i=mid+1;i<=right;i++){ //不能再忘记等于号了==
temp+=nums[i];
crossmax=Math.max(crossmax,temp);
}
return Math.max(Math.max(leftmax,rightmax),crossmax);
}
LeetCode 例题
Merge k Sorted Lists
也可以用优先队列做,和上面那个leetcode有点类似。