快速排序(QuickSort)

 

快速排序是现有的比较排序算法中,效率最好的一种排序算法.

所谓比较排序,是对排序对象的值的比较, 它不特定于排序对象本身的额外特征或排序对象因特定的数据结构而获得的额外条件.

快速排序主要体现分而治之的思想, 主要做法是不断的"选取基准点 - 划分子序列",直至子序列长度为1.

假定数组A[p.......r] - p,r都是数组下标.

 

算法的C#实现:

public void QuickSort( int[] A, int begin, int end )
{
    if ( begin < end )
    {
        int q = Partition( A, begin ,end );
        
        QuickSort( A, begin, q-1 );
        QuickSort( A, q, end );
    }
}
public int Partition( int[] A, int bengin, int end )
{
    int judgeValue = A[ end ]; 
    int i = begin -1;
    
    for( int j = begin ; j < end ; j ++ )
    {
        if( A[j] <= judgeValue )
        {
            i += 1;
            int tmp = A[i];
            A[i] = A[j];
            A[j] = tmp;
        }
    }
    int tmp = A[ i+1 ];
    A[i+1] = A[end];
    A[end] = tmp;

return i+1;

}

 

以上Partition函数的一次排序的过程如下:

原数组: 2 1 7 8 3 4 5

1.选中最后一个元素 5 为判断标准, 此时 bengin = 0 ; end = 6; i = -1; j 从0 到5

2. j = 0 : 判断A[0]小于5, i此时为0, 交换A[0]自身.

    2 1 7 8 3 4 5

3. j = 1 : 判断A[1]小于5, i此时为1, 交换A[1]自身.

    2 1 7 8 3 4 5

4. j = 2 : 判断A[2]大于5, i此时为1, 没有交换行为.

    2 1 7 8 3 4 5

5. j = 3 : 判断A[3]大于5, i此时为1, 没有交换行为.

    2 1 7 8 3 4 5

6. j = 4 : 判断A[4]小于5, i此时为2, 交换A[2]和A[4], 即交换7和3;

    2 1 3 8 7 4 5

7. j = 5 : 判断A[5]小于5, i此时为3, 交换A[3]和A[5], 即交换8和4;

    2 1 3 4 7 8 5

8. 完成循环, 交换A[3+1]和A[6];

    2 1 3 4 5 8 7

此时数组分成 {2, 1, 3, 4}, {5}, {8,7} 三个部分.

 

Note: 由Step 2, 3 可以看出, 当开始的元素小于基准值时, 自身被迫与自身交换. 可以做简化至 判断 j 和 i当前是否相等 决定是否进行交换. 不过这事实上不会影响排序的效率.

快速排序的平均时间复杂度是O(nlgn), 最坏可能达到O(n2)

 

上述的Partition算法是我们比较常用的. 它采用每个序列的最后一个元素来作为划分基准. 没有任何理由判定划分以后的两个子序列是否平衡(长度是否相差不多或者相等). 理论上来说, 越平衡的划分, 快速排序的时间复杂度就越无限逼近O(nlgn). 所以优化快速排序的基本方向,就是追求更加平衡的partition函数.

另外一种我们教材常见的partition函数由Hoare给出, C#实现如下:

public void Partition( int[] A, int begin, int end )
{
 
    int judgeValue = A[begin];
    int i = begin - 1 ;
    int j = end + 1 ;
 
    while(true)
    {
        do
        {
            j --; 
        }while( A[j] > judgeValue )
        do
        {
            i ++;
        }while( A[i] < judgeValue ) 
 
         if ( i < j )
         {
            int tmp = A[i];
            A[i] = A[j];
            A[j] = tmp;
         }
         else
            return j;
    }
}
 

以上Partition函数的一次排序的过程如下:

原数组: 3 1 7 8 2 4 5 (为了示例方便,调整了一下上面的示例数组)

1.选中第一个元素 3 为判断标准, 此时 bengin = 0 ; end = 6; i = -1; j = 7

2. j从最后向前寻找第一个不大于3的数, 找到A[4] = 2; 此时j = 4;

    i从最前向后寻找第一个不小于3的数, 找到A[2] = 7; 此时i = 2;

3. 交换A[4]和A[2], 数组成为 3 1 2 8 7 4 5

4. j继续向前寻找不大于3的数, 找到A[2]= 1; 此时j = 2;

    i继续向后寻找不小于3的数,找到A[3] = 8; 此时i = 3;

5. 此时i > j, 数组被分为3, 1,2 和8, 7, 4, 5两部分.返回2.

 

实际上,在寻求优化分区函数的过程中,常使用随即函数来在当前数组中随机取得一个判断基准点. 这样可以使快速排序更好的接近平均效率.

一个可能的版本是:

public void RandomPartition( int[] A, int begin , int end )
{
    int i = Random( begin , end ) ;
 
    // exchange the random element with the last element.
    int tmp = A [i];
    A[i] = A[end];
    A[end] = tmp;
 
    return Partition( A, begin, end );
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值