递归算法学习系列之三(快速排序)

       上两片第归算法学习:

1) 递归算法学习系列一(分而治之策略)
2) 递归算法学习系列二(归并排序) 

     上一篇学习中介绍了了递归算法在排序中的一个应用:归并排序,在排序算法中还有一种算法用到了递归,那就是快速排序,快速排序也是一种利用了分而治之策略的算法,它由C.A.R发明,它依据中心元素的值,利用一系列递归调用将数据表划分成越来越小的子表。在每一步调用中,经过多次的交换,最终为中心元素找到最终的位置。与归并算法不同,快速排序是就地排序,而归并排序需要把元素在临时向量中拷贝,下面通过对以下向量进行排序来理解和加深快速排序算法的步骤:

 v={800,150,300,650,550,500,400,350,450,400,900};

利用快速排序算法对此数据表进行排序的第0级划分过程如下: 向量v的索引范围为:[first,last) = [0,10),则中心点的索引为mid = (0+10)/2=5,中心点的值为v[5] = 500

快速排序算法的第一次划分的目的就是将向量v依据v[5]的值划分成两个子表subList1和subList2,其中subList1中的值都小于v[5],而subList2中的值都大于v[5],我们将subList1称为左子表,subList2称为右子表,并且确定v[5]的最终位置:下面就是实现这一目的需要我们作出的工作步骤:

1)首先将中心元素与起始位置的元素进行交换。

2)分别扫描左子表和右子表,左子表扫描起始位置为 first+1, 右子表从last-1开始。左子表从左向右扫描扫描,右子表从右向左扫描。直到左子表扫描位置大于或者等于右子表扫描位置时候结束。

在第一个步骤中,得到如下的数据表

500  150  300 650 550 800 400 350 450 400  image ,而此时的左子表扫描位置处于索引1处,右子表扫描位置处于索引9处,先从左子表扫描,直到找到数据值大于中间值500的位置停止扫描,然后扫描右子表,直到找到数据值小于中间值500并且右子表的扫描位置(scanDown)要小于左子表开始位置,防止数据溢出。找到之后,交换左子表与右子表中中扫描位置的元素,图示如下:image ,在交换v[3](650>500)与v[8](450<500)后,继续扫描左子表和右子表,如图(image )直到满足条件scanUp>=scanDown,然后scanDown所在位置就是中心元素500的最终位置,交换v[0]与v[scanDown)=v[5],第一次划分级别的最终结果数据集为:400,150,300,450,350,500,800,550,650,900,此时得到的左子表为:400,150,300,450,350,右子表为:800,550,650,900

下一个划分级别是处理上一级别产生的子表,按照相同的处理方法分别处理左子表和右子表,左子表索引位置[0,5),右子表索引位置[6,10),按照上面的处理步骤处理左子表(400,150,300,450,350)得到的最终结果为:150,300,400,450,350 右子表最终处理结果为:550,650,800,900 在处理结果中300与650分别是中心值,他们现在的位置就是最终位置

在接下来的处理中,总是处理上一步骤中留下的子表,当子表数目<=1的时候就不用处理子表了,而子表有两个元素的时候,比较大小,然后交换两元素位置即可。

大于2个元素的子表都和上面的处理步骤一样,我们将上面的处理过程编写出一个函数

private int PivotIndex(int[] v, int first, int last),那么快速排序算法就是对此函数的递归调用

 

 /// <summary>
       
/// 交换位置
       
/// </summary>
       
/// <param name="v"></param>
       
/// <param name="index1"></param>
       
/// <param name="index2"></param>

       private void Swrap(int[] v, int index1, int index2)
       
{
           
int temp = v[index1];
           v[index1] 
= v[index2];
           v[index2] 
= temp;
       }

       
/// <summary>
       
/// 将向量V中索引{first,last)划分成两个左子表和右子表
       
/// </summary>
       
/// <param name="v">向量V</param>
       
/// <param name="first">开始位置</param>
       
/// <param name="last">结束位置</param>

       private int PivotIndex(int[] v, int first, int last)
       
{
           
if (last == first)
           
{
               
return last;
           }

           
if (last - first == 1)
           
{
               
return first;
           }

           
int mid = (first + last) / 2;
           
int midVal = v[mid];
           
//交换v[first]和v[mid]
           Swrap(v, first, mid);
           
int scanA = first + 1;
           
int scanB = last - 1;
           
for (; ; )
           


               
while (scanA <= scanB && v[scanA] < midVal)
               
{
                   scanA
++;
               }

               
while (scanB > first && midVal <= v[scanB])
               
{
                   scanB
--;
               }

               
if (scanA >= scanB)
               
{
                   
break;
               }

               Swrap(v, scanA, scanB);
               scanA
++;
               scanB
--;
           }

           Swrap(v, first, scanB);
           
return scanB; 

       }

       
public void Sort(int[] v, int first, int last)
       
{
           
if (last - first <= 1)
           
{
               
return;
           }

           
if (last - first == 2)
           
{
               
//有两个元素的子表
               if (v[first] > v[last - 1])
               
{
                   Swrap(v, first, last 
- 1);
               }

               
return;
           }

           
else
           
{
               
int pivotIndex = PivotIndex(v, first, last);
               Sort(v, first, pivotIndex);
               Sort(v, pivotIndex 
+ 1, last);
           }

       }
 


      快速排序因为每次划分都能将中心值元素找到最终的位置,并且左边值都小于中心值,右边都大于中心值,它的时间复杂度平均和归并算法一致为O(nlog2n);

任何一种基于比较的排序算法的时间复杂度不可能小于这个数,除非不使用比较的方法进行排序。

算法程序:/Files/jillzhang/QuickSort.rar


上两片第归算法学习:

1) 递归算法学习系列一(分而治之策略)
2) 递归算法学习系列二(归并排序)



-------------------------------------------------------
人老了,脑袋不好用了,偶尔用算法来练练脑子,可以防止早衰。呵呵
                                                        jillzhang jillzhang@126.com

作者:jillzhang
出处:http://jillzhang.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2007-09-23 07:44 Robin Zhang 阅读(3328) 评论(10)  编辑 收藏 网摘 所属分类: 算法

  回复  引用    
#1楼2007-09-23 11:34 | .net新人王[未注册用户]
是不是少给了一个数900啊。
  回复  引用  查看    
#2楼2007-09-23 15:57 | MS的明天      
楼主继续,俺可一直在看呢!
  回复  引用  查看    
#3楼2007-09-23 23:14 | Cat Chen      
快排其实并不需要递归吧?
  回复  引用  查看    
#4楼[楼主]2007-09-24 08:34 | jillzhang      
@Cat Chen
我知道的是递归方法的实现
不需要递归的写法您写一个,我学习学习

  回复  引用  查看    
#5楼2007-09-24 09:50 | Zhuang miao      
有什么好的数据结构视频教程推荐一下吧
  回复  引用  查看    
#6楼2007-09-24 11:34 | 追求卓越      
个人意见:
在排序这种性能考虑优先的场合,递归算法并不合适.
而且它的系统开销是个隐患.如果排序内容很多,甚至有可能产生溢出.

  回复  引用  查看    
#7楼[楼主]2007-09-24 11:41 | jillzhang      
@追求卓越
请给出意见的同时给出更好的解决方案,否则的话,光是意见意义就不大了

  回复  引用  查看    
#8楼2007-09-24 14:47 | 追求卓越      
@jillzhang
如果你一定是要这种见解的话,那下次意见也没有了.

  回复  引用    
#9楼2007-09-25 01:30 | Colorful[未注册用户]
任何递归调用都可以用非递归的方式表达出来。

但我想说的是,这里完全没有必要使用非递归来表现快速排序。
此外,高级编译器会优化递归调用。至少Java编译器做了优化。
我想,C#编译器也应该差不到哪里去吧。
如果真的对性能很在乎,那我们可能不会使用.NET。

我给出几个优化建议:

1.关于选择中心点。
采用median-of-three partitioning方法,即三个元素中值划分。
第一个,中间一个及最后一个位置上元素的中值来作为中心点。

楼主的代码采用的是仅选择中间元素来作为中心点。
这是一个安全的选择,虽然合理却是消极的选择。

2.对于小规模范围内的排序,快速排序反而不如插入排序等其他排序方法,所以快速排序,一般规模降到一定范围之内,我们会调用插入排序来辅助排序。

  回复  引用  查看    
#10楼[楼主]2007-09-25 09:07 | jillzhang      
@追求卓越
您的意见我接受,如果您有更好的解决方案,分享出来,更是欢迎




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 902970




相关文章:

相关链接: