分治
算法思想
把一个任务,分成形式和原任务相同,但规模更小的几个部分任务(通常是两个部分),分别完成,或只需要选一部分完成。然后再处理完成后的这一部分或几个部分的结果,实现整个任务的完成。
基本例题
case 1:归并排序
**排序思想:**数组排序任务分为以下三步完成
- 把前一半排序
- 把前一半的前一半排序
- 把前一半的后一半排序
- 把后一半排序
- 把后一半的前一半排序
- 把后一半的后一半排序
- 把两半归并到一个新的有序数组,然后在拷贝回原数组,排序完成
示例代码
#include <iostream>
using namespace std;
// 将数组a的局部a[s,m]和a[m+1,e]合并到tmp,并保证tmp有序,然后再拷贝回a[s,e]
void Merge(int a[], int s, int m, int e, int tmp[]) {
int pb = 0;
int p1 = s, p2 = m+1;
while(p1 <= m && p2 <= e) {
if(a[p1] < a[p2]) {
tmp[pb++] = a[p1++];
} else {
tmp[pb++] = a[p2++];
}
}
while(p1 <= m) {
tmp[pb++] = a[p1++];
}
while(p2 <= e) {
tmp[pb++] = a[p2++];
}
for(int i=0; i<e-s+1; i++) {
a[s+i] = tmp[i];
}
}
void MergeSort(int a[], int s, int e, int tmp[]) {
if(s < e) {
int m = s + (e-s)/2;
MergeSort(a,s,m,tmp);
MergeSort(a,m+1,e,tmp);
Merge(a,s,m,e,tmp);
}
}
int a[10] = { 13,27,19,2,8,12,2,8,30,89 };
int b[10];
int main() {
int size = sizeof(a)/sizeof(int);
MergeSort(a,0,size-1,b);
for(int i=0; i<size; i++) {
cout<<a[i]<<" ";
}
cout<<endl;
return 0;
}
注意
- 要注意拆分和合并的顺序,先对数组进行不断拆分的操作,直到拆分后的数组中只有1个元素,然后在进行合并。拆分数组容量从大到小,合并数组容量从小到大,两者是一个互逆的过程。
- 在合并时要确保
a[s,m]
和a[m+1,e]
是有序的
时间复杂度分析
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ T(n) &= 2*T…
- 当 n / 2 k = 1 n/2^k=1 n/2k=1时, k = l o g 2 n k=log_2n k=log2n
- T ( n ) = 2 k ∗ T ( n / 2 k ) + k ∗ a ∗ n = 2 k ∗ T ( 1 ) + k ∗ a ∗ n = 2 k + k ∗ a ∗ n = n + a ∗ l o g 2 n ∗ n T(n)=2^k*T(n/2^k)+k*a*n=2^k*T(1)+k*a*n=2^k+k*a*n=n+a*log_2n*n T(n)=2k∗T(n/2k)+k∗a∗n=2k∗T(1)+k∗a∗n=2k+k∗a∗n=n+a∗log2n∗n
- 故复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
case 2 :快速排序
**排序思想:**数组排序按以下步骤完成
- 设
k=a[0]
,将k
移动到适当的位置,使得比k
小的元素都位于k
的左边,比k
大的元素都位于k
的右边,和k
相等的,无所谓。 O ( n ) O(n) O(n)时间完成 - 把
k
左边的部分快速排序 - 把
k
右边的部分快速排序
示例代码
#include <iostream>
using namespace std;
// 交换变量a,b的值
void swap(int &a, int &b) {
int tmp =a;
a = b;
b = tmp;
}
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 a[10] = { 13,27,19,2,8,12,2,8,30,89 };
int main() {
// sizeof(int) 计算一个 int 型变量占内存多少单元。
// sizeof(a) 计算整型数组里元素占用内存多少单元。
int size = sizeof(a)/sizeof(int); // 计算数组中的元素数量的个数
QuickSort(a,0,size-1);
for(int i=0; i<size; i++) {
cout<<a[i]<<" ";
}
cout<<endl;
return 0;
}
while
循环中采用两个指针i
和j
分别指向当前元素,并和k
比较,知道确定k
的位置- 时间复杂度和归并排序相同: O ( n l o g n ) O(nlogn) O(nlogn)
case 3:输出前m大的数
**问题描述:**给定一个数组包含 n n n个元素,统计前 m m m大的数并且把其输出
**解题思路:**基本的解法就是将数组先排序再输出,这种做法的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),采用分治思想处理,可降低复杂度为 O ( n + m l o g ( m ) ) O(n+mlog(m)) O(n+mlog(m)),思路及关键步骤如下:
- 思路:把前 m m m大的都弄到数组最右边,然后对这最右边的 m m m个元素排序,再输出
- 关键:
O
(
n
)
O(n)
O(n)时间内实现把前
m
m
m大的都弄到数组最右边
- 引入操作
arrangeRight(k)
:把数组(或数组的一部分)前 k k k大的都弄到最右边- 设置
key=a[0]
,将key
挪到适当的位置,使得比key
小的元素都在key
左边,比key
大的元素都在key
的右边(线性时间内完成:快排) - 选择数组的前部或后部再进行
arrangeRight
操作,分为以下三种情况(假定key
左端有元素b
个,右端有元素a
个)a=m
:完成a>m
:对右端a
个元素再进行arrangeRight(m)
操作a<m
:对左端b
个元素进行arrangeRight(m-a-1)
- 设置
- 引入操作
示例代码
#include <iostream>
using namespace std;
// 交换变量a,b的值
void swap(int &a, int &b) {
int tmp =a;
a = b;
b = tmp;
}
void Print(int a[], int s, int e) {
for(int i=s; i<=e; i++) {
cout<<a[i]<<" ";
}
}
void arrangeRight(int a[],int s,int e, int m) {
if(s >= e) {
return ;
}
int k = a[0];
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]);
}
if(e - i == m) {
Print(a,i+1,e);
} else if(e - i > m) {
arrangeRight(a,i+1,e,m);
} else {
Print(a,i,e);
arrangeRight(a,s,i-1,m-e+i-1);
}
}
int a[10] = { 13,27,19,2,8,12,2,8,30,89 };
int main() {
int size = sizeof(a)/sizeof(int);
arrangeRight(a,0,size-1,8);
return 0;
}
case 4:求排列的逆序数
**题目描述:**考虑 1 , 2 , 3 , . . . , n ( n ≤ 100000 ) 1,2,3,...,n(n\leq100000) 1,2,3,...,n(n≤100000)的排列 i 1 , i 2 , . . . , i n i_1,i_2,...,i_n i1,i2,...,in,如果其中存在 j , k j,k j,k,满足 j < k j<k j<k且 i j > i k i_j>i_k ij>ik,那么就称 ( i j , i k ) (i_j,i_k) (ij,ik)是这个排列的一个逆序。一个排列含有逆序的个数称为这个排列的逆序数。例如排列 263451 263451 263451含有 8 8 8个逆序 ( 2 , 1 ) , ( 6 , 3 ) , ( 6 , 4 ) , ( 6 , 5 ) , ( 6 , 1 ) , ( 3 , 1 ) , ( 4 , 1 ) , ( 5 , 1 ) (2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1) (2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1),因此该排列的逆序数就是 8 8 8。现在给定一个排列,求它的逆序数。
**解题思路:**基本想法是双重循环遍历,但是在 n n n足够大时,该方法会超时。现采用分治思想,结合归并排序求解。具体方法如下
- 将数组分成两半,分别求出左半边的逆序数和右半边的逆序数
- 再算有多少逆序是由左半边取一个数和右半边取一个数构成(要求 O ( n ) O(n) O(n)实现)。由于左右半边都是排好序的,因此左右半边只需从头到尾各扫一遍,就可以找出由两边各取一个数构成的逆序个数
示例代码
#include <iostream>
using namespace std;
void Merge(int a[], int s, int m, int e, int tmp[], int &count) {
int pb = 0;
int p1 = s, p2 = m+1;
while(p1 <= m && p2 <= e) {
if(a[p1] <= a[p2]) {
tmp[pb++] = a[p1++];
} else {
count += (e-p2+1);
tmp[pb++] = a[p2++];
}
}
while(p1 <= m) {
tmp[pb++] = a[p1++];
}
while(p2 <= e) {
tmp[pb++] = a[p2++];
}
for(int i=0; i<e-s+1; i++) {
a[s+i] = tmp[i];
}
}
void MergeSortAndCount(int a[], int s, int e, int tmp[], int &count) {
if(s < e) {
int m = s + (e-s)/2;
MergeSortAndCount(a,s,m,tmp,count);
MergeSortAndCount(a,m+1,e,tmp,count);
Merge(a,s,m,e,tmp,count);
}
}
int count = 0;
int a[6] = {2,6,3,4,5,1};
int b[6];
int main() {
int size = sizeof(a)/sizeof(int);
MergeSortAndCount(a,0,size-1,b,count);
cout<<"该排列的逆序数为 : "<<count<<endl;
return 0;
}
最后有需要工程文件的朋友可以在评论里说明(记得指明邮箱),小编看到后会第一时间发送到指定邮箱。文章如有不严谨之处,欢迎大家指正!!!