分治的概念
人们在遇到一个难以解决的大问题时,自然会想到把它划分成一些规模较小的子问题,各个击破,分而治之。
分治题目的特征
(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;
}
总结
总体上,快速排序的代码比归并排序的代码简洁,代码中的比较,交换,复制操作很少,快速排序几乎是目前所有排序法中最快的,但快速排序比较不稳定,望谨慎使用。