算法分析之分治法总结(二)

3)分类和合并同时进行
典型事例 快速排序:(以整数类型为例)
快速排序的思想是,对于输入,按照以下二个步骤进行排序
对于数组a[p:r]
1)分解(其实包括合并过程):以a[p]为基准将数组分成三段a[p:q-1]、a[q]、a[q+1:r]使得a[p:q-1]中的任意元素都小于等于a[q],a[q+1:r]中的任意元素都大于等于a[q].
2)递归求解:通过递归调用快速排序,分别完成a[p:q-1]和a[q+1:r]的排序。
程序代码:
Java代码 复制代码
  1. int partition(int a[],int p,int r)   
  2. {   
  3.  int i=p,j=r+1,x=a[p],temp;   
  4.  while(true)   
  5.  {   
  6.   while(x>a[i]){++i;}   
  7.   while(x<a[j]){--j;}   
  8.      if(i>=j) break;   
  9.   temp=a[i];   
  10.   a[i]=a[j];   
  11.   a[j]=temp;   
  12.  }   
  13.  a[p]=a[j];   
  14.  a[j]=x;   
  15.  return j;   
  16. }   
  17. void QuickSort(int a[],int p,int r)   
  18. {   
  19.  if(p < r)   
  20.  {   
  21.   int q=partition(a,p,r);   
  22.   QuickSort(a,p,q-1);   
  23.   QuickSort(a,q+1,r);   
  24.  }   
  25. }  
int partition(int a[],int p,int r)
{
 int i=p,j=r+1,x=a[p],temp;
 while(true)
 {
  while(x>a[i]){++i;}
  while(x<a[j]){--j;}
     if(i>=j) break;
  temp=a[i];
  a[i]=a[j];
  a[j]=temp;
 }
 a[p]=a[j];
 a[j]=x;
 return j;
}
void QuickSort(int a[],int p,int r)
{
 if(p < r)
 {
  int q=partition(a,p,r);
  QuickSort(a,p,q-1);
  QuickSort(a,q+1,r);
 }
}

与QuickSort类似的例子是选择问题:
我们经常遇到的选择问题是选择最大元素和最小元素的问题,这个问题解决起来比较容易,而且时间复杂度为O(n),但是当我们把这个问题一般化的时候,既选择第k小(大)的元素的时候,思考一下,最容易想到的方法是排一下序,第k个 位置就是第k小的元素,在想一想可以找一些能够在每一次能够确定一个元素的最终位置的排序方法(如冒泡法,直接选择排序法等)只需要排好k(k<=n/2时)个的元素或者n-k(k>n/2),但是这样的话其实时间复杂性仍然是O(n^2),比找最大和最小所用的时间复杂度大的多。如果采用QuickSort算法其时间复杂度为O(nlogn),对于排序的确是个非常好的算法,但是我们想QuickSort是把所有的元素都排好的时间复杂度,我们找第k小的元素,从感性上也可以知道这是比排序要简单的问题,但是找第k小的元素,k本身就是个序号问题,如果不排序似乎解决不了这个问题。似乎有一个简单的情形会给我们以启发:我们在桌子上摆了一副扑克,我们规定了各个扑克的大小,现在是找第k小的元素,人或许很容易看出,但计算机似乎有点困难,那就找个就简单的方法吧---瞢,瞢当然不是瞎瞢,瞢错了你可以去掉一些不符和条件的扑克,例如瞢到了一张红桃k,现在好办了,把比红桃k小的扑克数一数,如果小于k-1,那好把红桃k和那些扑克仍在一边,如果等于k-1,恭喜你,瞢对了,否则,把其他的扑克和k仍在一边。对剩余的扑克继续重复上述过程(当然这是k应适当的变化一下)。其实我们发现上述过程如果不是运气特差的话,一次就能够扔掉不少扑克牌。
其算法如下:
//搜索第k小的元素
Java代码 复制代码
  1. int partition(int a[],int p,int r)//分化   
  2. {   
  3.  int i=p,j=r+1,x=a[p],temp;   
  4.  while(true)   
  5.  {   
  6.   while(x>a[i]){++i;}   
  7.   while(x<a[j]){--j;}   
  8.      if(i>=j) break;   
  9.   temp=a[i];   
  10.   a[i]=a[j];   
  11.   a[j]=temp;   
  12.  }   
  13.  a[p]=a[j];   
  14.  a[j]=x;   
  15.  return j;   
  16. }   
  17. int SelectK(int a[],int p,int n,int k)//选择   
  18. {   
  19.  int q,i;   
  20.     q=partition(a,p,n);   
  21.  i=q-p+1;   
  22.  if(i<k)   
  23.       return SelectK(a,q+1,n,k-i);   
  24.  else if(i==k)   
  25.    return a[q];   
  26.  else  
  27.    return SelectK(a,p,q-1,k);   
  28. }  
int partition(int a[],int p,int r)//分化
{
 int i=p,j=r+1,x=a[p],temp;
 while(true)
 {
  while(x>a[i]){++i;}
  while(x<a[j]){--j;}
     if(i>=j) break;
  temp=a[i];
  a[i]=a[j];
  a[j]=temp;
 }
 a[p]=a[j];
 a[j]=x;
 return j;
}
int SelectK(int a[],int p,int n,int k)//选择
{
 int q,i;
    q=partition(a,p,n);
 i=q-p+1;
 if(i<k)
      return SelectK(a,q+1,n,k-i);
 else if(i==k)
   return a[q];
 else
   return SelectK(a,p,q-1,k);
}


//上述的算法当然不是优化之后的算法,虽然这个算法最坏情况下仍然是O(n^2),但平均复杂度只是o(n),虽然改进后的的算法在最坏的情况下时间复杂度线性的T(n)<=72c1n,即nlog(10^72)这个算法显然在实际上已经没有什么应用的价值,只有理论价值,因为当数据的规模在比10^72这个数量大的时候,他的速度才会超过快速排序的最坏结果。下面是改进后的算法(线性搜索)的思路.考虑上面程序算法最坏的情况在什么时候发生?当每一次你选择的元素都离你要选择的基准元素相差最远的时候。改进算法就是为了不至于每次都遇到这么差的运气,所以他在选择分化基准元素的时候,他尽量选择大小在中间大小的元素,因为这样在平均的情况下效率挺高,一次就扔掉一半,在最坏的情况下也不会很坏。所以在partition之前要在n个元素中选择中间元素,这当然也是个很难简单就解决的问题,所以不一定真的是最中间的,大体上差不多就行了,因为这样远远比最中间的元素的时间复杂度小得多。算法的步骤如下:
1)将参加划分的n个元素分成[n/r]组(r>1),每组有r个元素,将n-r[n/r]的元素扔掉,然后对每一组的元素找出中间的元素,然后再从中间元素选择中间元素,这样的元素已经满足我们的要求了,虽然不一定是真正的中间元素。二次取中间值的方法复杂度是o(n)数量级的。其他的就和上面没改进的算法步骤一样了,不再详述。
分治法作为一种算法思想和前人总结的经验,关键是理解其算法的思想,不同的题难度不一,应具体问题具体分析。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值