分治的基本概念
- 把一个任务,分成形式和原任务相同,但规模更小的部分任务(通常是两部分),分别完成,或只需要选一部分完成。然后再处理完成后的这一个或几个部分的结果,实现整个任务的完成。
实例
- 16枚硬币,有可能有一枚假币,假币比真币轻。有一架天平,用最少称量次数确定有没有假币,若有的话,假币是哪一枚。
- 解决办法
8—8——称,发现无假币,或假币所在的那8枚
4—4——称
2—2——称
1—1——称
分治的典型应用:归并排序
- 数组排序任务可以按以下步骤完成:
- 1)把前一半排序
- 2)把后一半排序
- 3)把两半归并到一个新的有序数组,然后再拷贝回原数组,排序完成。 - C语言示例:
#include<stdio.h>
void Merge(int a[], int s, int m, int e, int tmp[]){
int pb = 0, i;
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(i = 0; i < e-s+1; ++i)
a[s+i] = tmp[i];
}
void MergeSort(int a[], int s, int e, int tmp[]){
int m = 0;
if( s<e ){
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);
int i;
printf("排序前:");
for(i = 0; i < size; i++)
printf("%3d", a[i]);
printf("\n排序后:");
MergeSort(a, 0, size-1, b);
for(i = 0; i < size; i++){
printf("%3d", a[i]);
}
printf("\n");
return 0;
}
- C语言运行示例:
- 归并排序的时间复杂度分析:
对n个元素进行排序的时间:
T ( n ) = 2 ∗ T ( n / 2 ) + a ∗ n T(n) = 2*T(n/2) + a*n T(n)=2∗T(n/2)+a∗n
T ( n ) = 2 ∗ ( 2 ∗ T ( n / 4 ) + a ∗ n / 2 ) + a ∗ n T(n) = 2*(2*T(n/4)+ a*n/2) + a*n T(n)=2∗(2∗T(n/4)+a∗n/2)+a∗n
T ( n ) = 4 ∗ T ( n / 4 ) + 2 a ∗ n T(n) = 4*T(n/4) + 2a*n T(n)=4∗T(n/4)+2a∗n
T ( n ) = 4 ∗ ( 2 ∗ T ( n / 8 ) + a ∗ n / 4 ) + 2 ∗ a ∗ n T(n) = 4*(2*T(n/8) + a*n/4)+2*a*n T(n)=4∗(2∗T(n/8)+a∗n/4)+2∗a∗n
T ( n ) = 8 ∗ T ( n / 8 ) + 3 ∗ a ∗ n T(n) = 8*T(n/8)+3*a*n T(n)=8∗T(n/8)+3∗a∗n
T ( n ) = . . . T(n) = ... T(n)=...
T ( n ) = 2 k ∗ T ( n / 2 k ) + k ∗ a ∗ n T(n) = 2^k*T(n/2^k)+k*a*n T(n)=2k∗T(n/2k)+k∗a∗n
一直做到 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 ( 1 ) + k ∗ a ∗ n = 2 k ∗ T ( 1 ) + k ∗ a ∗ n = 2 k + k ∗ a ∗ n T(n) = 2^k*T(1)+k*a*n = 2^k*T(1)+k*a*n = 2^k+k*a*n T(n)=2k∗T(1)+k∗a∗n=2k∗T(1)+k∗a∗n=2k+k∗a∗n
T ( n ) = n + a ∗ ( l o g 2 n ) ∗ n T(n) = n + a*(log_2n)*n T(n)=n+a∗(log2n)∗n
最终复杂度: O ( n l o g n ) \Omicron(nlogn) O(nlogn)
分治的典型应用:快速排序
- 快速排序任务可以如下完成:
- 1)设k=a[0],将k挪动到适当的位置,使得比k小的元素都在k左边,比k大的元素都在k右边,和k相等的,不关心,在k左右出现均可( O ( n ) 时 间 完 成 \Omicron(n)时间完成 O(n)时间完成)
- 2)把k左边的部分快速排序
- 3)把k右边的部分快速排序 - C语言示例:
#include<stdio.h>
void swap( int *a, int *b){
int tmp = *a;
*a = *b;
*b = tmp;
}
void QuickSort(int a[], int s, int e){
int k, i, j;
if( s>=e )
return ;
k = a[s];
i = s;
j = e;
while( i != j){
while( j > i && a[j] >= k )
--j;
swap(&a[i], &a[j]);
while( i < j && a[i] <= k )
++i;
swap(&a[i], &a[j]);
}
QuickSort(a, s, i-1);
QuickSort(a, i+1, e);
}
int a[] = {93, 27, 30, 2, 8, 12, 2, 8, 30, 89};
int main(){
int size = sizeof(a)/sizeof(int);
int i;
printf("排序前:");
for(i = 0; i < size; i++)
printf("%4d", a[i]);
QuickSort(a, 0, size-1);
printf("\n排序后:");
for(i = 0; i < size; i++)
printf("%4d", a[i]);
printf("\n");
return 0;
}
- 运行示例
快速排序算法时间复杂度分析
- 最优情况下的时间复杂度:
快速排序最优的情况就是每一次取到的元素都刚好平分整个数组,此时算法复杂度就和归并排序算法复杂度一样 O ( n l o g n ) \Omicron(nlogn) O(nlogn) - 最坏情况下的时间复杂度:
最坏的情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序),时间复杂度就是冒泡排序的时间复杂度: T [ n ] = n ∗ ( n − 1 ) = n 2 + n T[n] = n * (n-1) = n^2 + n T[n]=n∗(n−1)=n2+n即为 O ( n 2 ) \Omicron(n^2) O(n2)。
注:文中问题及代码参考 MOOC——《程序设计与算法》(北京大学 郭炜)