分治算法小结

目录

概念

步骤

例题

归并排序

快速排序

输出前m大的数

求排列的逆序数


概念

分治即“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

步骤:

  • 分解:把原问题分解成若干个规模较小,相互独立,与原问题形式相同的子问题。
  • 解决:若子问题规模较小二容易被解决则直接解,否则递归地解各个子问题。
  • 合并:将各个子问题的解合并为原问题的解。

例题

例题一:归并排序

数组排序任务可以如下完成:
(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)
       
#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);	//合并
	}
}

  • 2
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

淮也

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值