学习分治算法


分治的概念

人们在遇到一个难以解决的大问题时,自然会想到把它划分成一些规模较小的子问题,各个击破,分而治之。


分治题目的特征

(1)平衡子问题:子问题的规模大致相同,能把问题划分成大小差不多相等的k个子问题,最好k=2,即分成两个规模相等的子问题。子问题规模相等的处理效率比子问题规模不等的处理效率要高。
(2)独立子问题:子问题之间相互独立。这是区别于动态规划算法的根本特征,在动态规划算法中,子问题是相互联系的,而不是相互独立。

分治的解题步骤

(1)分解(Divide):把问题分解成独立的子问题。
(2)解决(Conquer):递归解决子问题。
(3)合并(Combine):把子问题的结果合并成原问题的解。

二、运用

1.归并排序

(1)思路

(1)分解。把原来无序的数列分成两部分,对每个部分再继续分解成更小的两部分------在归并排序中,只是简单地把数列分成两半。在快速排序中,是把序列分成左右两部分,左部分的元素都小于右部分的元素。分解操作是快速排序的核心操作。
(2)解决。分解到最后不能再分解,排序。
(3)合并。把每次分开的两个部分合并到一起。归并排序的核心操作是合并,其过程类似于交换排序。快速排序不需要合并操作,因为在分解过程中左右部分已经是有序的。

(2)示例

分析图片内容,归并排序的主要操作如下:
在这里插入图片描述
(1)分解。把初始序列分成长度相同的左、右两个子序列,然后把每个子序列再分成更小的两个子序列,直到子序列只包含1个数。
(2)求解子问题,对子序列排序。
在这里插入图片描述
(3)合并。经过四次比较,得到b[] = {13,34,56,94,99}。

(3)经典----逆序对问题

题目
在这里插入图片描述

代码如下:

#include <bits/stdc++.h>
const int MAXN = 100005;
typedef long long ll;
ll a[MAXN],b[MAXN],cnt;
void Merge(ll h,ll mid,ll r) {
	ll i = h,j = mid+1,t = 0;
	while(i<=mid&&j<=r) {
		if(a[i]>a[j]) {
			b[t++] = a[j++];
			cnt += mid-i+1;     //记录逆序对数量 
		}
		else b[t++] = a[i++];
	}
	//一个子序列中的数都处理完了,另一个还没有,把剩下的直接复制过来 
	while(i<=mid)  b[t++] = a[i++];
	while(j<=r)    b[t++] = a[j++];
	for(i = 0;i<t;i++)  a[h+i] = b[i];   //把排好序的b[] 复制回a[] 
}

void Mergesort(ll h,ll r) {
	if(h<r) {
		ll mid = (h+r)/2;  //平分成两个子序列 
		Mergesort(h,mid);
		Mergesort(mid+1,r);
		Merge(h,mid,r);    //合并 
	}
}

int main() {
	ll n,k;
	while(scanf("%lld%lld",&n,&k) != EOF) {
		cnt = 0;
		for(ll i = 0;i<n;i++)  scanf("%lld",&a[i]);
		Mergesort(0,n-1);                          //归并排序 
		if(cnt<=k)  printf("0\n");
		else  printf("% I64d\n",cnt - k);
	}
	return 0;
}

2.快速排序

(1)思路·

把序列分成左右两部分,使得左边所有的数都比右边的数小;递归这个过程,直到不能再分为止。

(2)例题

题目:link
代码如下(示例):

#include<stdio.h>
using namespace std;
const int N = 10010;
int data[N];
#define swap(a,b) {       //交换数据 
	int temp = a;
	a = b;
	b = temp;
}
int partition(int left,int right) {   //划分成左右两部分,以 i 指向的数为界 
	int i = left;
	int temp = data[right];           //把尾部的数看成基准数 
	for(int j =left;j<right;j++)
	  if(data[j]<temp) {
	  	swap(data[j],data[i]);
	  	i++;
	  }
	  swap(data[i],data[right]);
	  return i;                       // 返回基准数的位置 
}

void quicksort(int left,int right) {
	if(left<right) {
		int i = partition(left,right);//划分 
		quicksort(left,i-1);          //分治:i左边的继续递归划分 
		quicksort(i+1,right);         //分治:i右边的继续递归划分 
	}
}

int main() {
	int n;
	scanf("%d",&n);
	for(int i = 1;i<=n;i++)  scanf("%d",&data[i]);
	quicksort(1,n);
	printf("%d\n",data[(n+1)/2]);
	return 0;
}

总结

总体上,快速排序的代码比归并排序的代码简洁,代码中的比较,交换,复制操作很少,快速排序几乎是目前所有排序法中最快的,但快速排序比较不稳定,望谨慎使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值