快速排序

来源:互联网 发布:机器人编程入门教程 编辑:程序博客网 时间:2024/05/21 06:02

1.快速排序的描述

   快速排序的最坏的情况的时间复杂度为 n的平方,但是期望时间是nlgn,并且他所包含的常数因子非常小,并且能够进行原址排序,因此快排通常是实际应用中最好的排序。快排依旧使用了分治的思想,与归并排序差不多,归并排序不是原址的,要使用辅助空间,原理都是将元数组划分为子数组,归并排序是平分数组,然后再将平分的数组合并排序,而快排则是,选定一个主元(主元的选定方法有很多,直接取最后一个元素,或者是随机抽取数组元素,与最后一个元素交换,或者随机取三个,选取中间的数与最后一个数交换),将数组以主元为边界划分为三部分,如图:


下面是实例:

最终形态分析:

2.快速排序代码

    快速排序的关键部分就是如何原址的生成,以主元为边界的三部分,这也是程序的关键,划分好后,对每一个子数组递归调用快排。下面代码展示了三种partition的方法,第一种是《算法导论》上的算法,这种算法比较易懂,另一种是《数据结构》严蔚敏版的算法,其中在第一种上增加了对所有元素都一样的判断,若元素全都一样则返回中间元素的位置。第三种是Hoare算法,是partition的最早算法,主元也是分界线。与第二种算法思想是一样的,但是更精炼。注意:当判断这种情况能否判断出来时,要看看你给出判断条件,是否也包含别的情况。

 /** * @param a * @param p * @param r   * 快速排序:与归并排序一样都是将数组分成若干个部分: * 这里是分成了3各部分,以最后一个元素为key,以key * 为分界线 左边的部分比key小,key右边的铁元素比key大 * 时间复杂度:最坏n的平方 * 一般情况  nlgn */public void quickSort(int a[],int p,int r)  {  if(p<r)  { // int q=partition(a, p, r);  /********************************************/ // if(r-p+1>k)//  insertSort(a,p,r); 快速排序的改进算法;当子数组小于k时接近排好序的数组,此时可以  //else                    使用插入排序  /*******************************************/  int q=partition2(a, p, r);  quickSort(a, p, q-1);  quickSort(a, q+1, r);  }  }  /** * @param a * @param p * @param r * 快速排序的关键部分,原址重排,将数组a分成左边比a[r]小右边比a[r]大,然后再将a[r]插入到 * 左边部分的后边(也就是把a[r]插到他应该在的地方) */public int  partition(int  a[],int p,int r){int key=a[r];int i=p-1;int repeat=0;for(int j=p;j<r;j++){if(a[j]<=key){i=i+1;int temp=a[i];a[i]=a[j];a[j]=temp;}if(a[j]==key){repeat++;}}//!if(i==p-1)当所有元素都重复时返回,中间元素的位置,仅用i==p-1 不能说明都重复,//!return (p+r)/2;//还有前面的元素都比key大所以要排除if(repeat==(p-r))return (p+r)/2;else{i++;int temp=a[i];a[i]=key;a[r]=temp;return i;}}public int partition2(int a[],int low ,int high){int key=a[high];while(low<high){while(low<high&&a[low]<=key) low++;a[high]=a[low];while(low<high&&a[high]>=key) high--;a[low]=a[high];}a[low]=key;return low;}   public void insertSort(int a[],int low,int high)   {   for(int i=low+1;i<=high;i++)   {   int key=a[i];   int j=i-1;   while(j>=0&&a[j]>key)   {   a[j+1]=a[j];   j--;   }   a[++j]=key;   }   }/** * @param a * @param low * @param high * @return * 最早的partition算法 */public int partitionHoare(int a[],int low ,int high){    int key=a[low];    while(true)    {        while(a[high]>key) high--;        while(a[low]<key) low++;        if(low<high)        {            int temp=a[low];            a[low]=a[high];            a[high]=temp;        }        else            return high;                }}

3.快速排序的改进算法

  可以发现,插入排序在几乎排好序的情况下,时间复杂度是相当低的,接近最好的情况,这时候你可以规定一个k,当子数组的规模等于k时,
进行插入排序。代码在上面已经给出,至于k为多少,就要分析求解了。

4.快速排序的栈深度

  上述的快速排序,使用了两个对其自身的调用,其实第二个并不是必须的,可以使用循环控制结构代替它,这一技术叫做尾递归 。第一次调用自身是对第一个子数组的递归调用,以后的循环部分是对另一半子数组的循环,这样也能给你达到递归分割数组的效果。栈深度就是递归的次数, 栈的深度就类似于递归树的深度,当数组正序时,递归的深度为Θ(n),栈的深度也为Θ(n),此时全部1—n-1个元素全纳入递归调用。

     例如A={1, 2, ... , n}

<span style="font-size:18px;">/** * @param a * @param p * @param r * 对快速排序 的尾递归算法 */public void tailQuickSort(int a[],int p,int r){while(p<r){int q=partition(a, p, r);tailQuickSort(a, p, q-1);//第一次循环是对前一次的递归p=q+1;//对这一半子数组排序}}</span>

为了使站的深度为lgn  就要尽可能的平分原数组,所以主元的选定要使用所有元素的中位数。

说到寻找中位数,

下面给出两种求解中位数的算法

1.可以使用对数组中每个元素进行迭代,设当前迭代元素为key,找出比key小的数进行计数,如果是n/2,则退出。

2.使用计数排序,这不是比较排序,通过借助辅助空间进行排序。时间复杂度为n;具体参见Beauty Of algorithms(五)

3.可以联想到《编程珠玑》中的寻找第k个小的数(中位数就是第n/2小的数),使用快排的思想,先使用partition进行分区:当N较大时 约等于 2N 也就是 O(N)

   1)如果左半部份的长度>K-1,那么这个元素就肯定在左半部份了
   2)如果左半部份的长度==K-1,那么当前划分元素就是结果了。
   3)如果。。。。。。。<K-1,那么这个元素就肯定在右半部分了。
  并且,该方法可以用尾递归实现。效率更高。 

快速排序总结:快速排序,能如此之火,也是其充分利用分治的思想,要掌握的不是快排而是这种思想。在处理一个问题时,将其划为许多子问题,关键就是怎么去划分这些子问题,1.利用主元(一个支点)划分为左右两部分 2直接从中间割开。所谓快排的关键就是patition,创建这个分区。就像应用于第k小的数字,归并排序和最大子数组的从中间划分。先 分  再 递归。



0 0
原创粉丝点击