分治——归并排序、快速排序

分治的基本概念
  • 把一个任务,分成形式和原任务相同,但规模更小的部分任务(通常是两部分),分别完成,或只需要选一部分完成。然后再处理完成后的这一个或几个部分的结果,实现整个任务的完成。
实例
  • 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)=2T(n/2)+an
    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(2T(n/4)+an/2)+an
    T ( n ) = 4 ∗ T ( n / 4 ) + 2 a ∗ n T(n) = 4*T(n/4) + 2a*n T(n)=4T(n/4)+2an
    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(2T(n/8)+an/4)+2an
    T ( n ) = 8 ∗ T ( n / 8 ) + 3 ∗ a ∗ n T(n) = 8*T(n/8)+3*a*n T(n)=8T(n/8)+3an
    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)=2kT(n/2k)+kan
    一直做到 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)=2kT(1)+kan=2kT(1)+kan=2k+kan
    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(n1)=n2+n即为 O ( n 2 ) \Omicron(n^2) O(n2)

注:文中问题及代码参考 MOOC——《程序设计与算法》(北京大学 郭炜)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值