zoukankan      html  css  js  c++  java
  • 基于比较的算法之四:快速排序

    算法思想:分治法,把一个序列分成两个,其中一边的元素小于另一边的元素。一直这样分下去,直到只有一个元素的时候返回。然后回推往前看,所有的元素已经按大小归位。算法的难点在于将一个序列分成两列的过程,使得一边元素小于另一边,下面给予说明。

    设数组array对下标 leftIndex到rightIndex之间的元素进行划分Partition

    初始状态leftIndex指向数组的第一个元素,rightIndex指向数组的最后一个元素 

    1.取第一个元素array[leftIndex]定为分界的基准记为Pivot。

    2.注意第一步已经把leftIndex的位置空闲下来了(赋值了Pivot变量)。

    这时从rightIndex从右往左寻找比Pivot小的元素,使rightIndex指向这个元素,然后把这个元素移动到第一步空闲下来的leftIndex位置(此步骤决定了算法是不稳定的)。

    观察此时的状态:这时rightIndex的位置空闲了下来,因为它的元素已经放到了leftIndex的位置。此时leftIndex自增1,因为原来的位置已经被填入了新的比pivot小的元素。

    3.从leftIndex往右寻找大于等于Pivot的元素,并使leftIndex指向这个元素。然后把这个元素移动到步骤2空闲下来的rightIndex位置(此步骤决定了算法是不稳定的)。

    观察此时的状态:leftIndex的位置重新空闲了下来,此时rightIndex自减1,因为原来rightIndex的位置已经填入了大于等于Pivot的元素。

    4.重复做步骤2,3,直到leftIndex和rightIndex相遇/相等 ,这是把Pivot放入leftIndex位置。

    最后得到的结果是,leftIndex==rightIndex,此时leftIndex左侧的元素全部小于Pivot,右侧全部大于等于Pivot,所以完成了划分。

    时间复杂度分析:

    最好情况:

    假设每次划分都正好把数组分成均分的两个部分,其中P(n)为Partition()的时间复杂度,P(n)最好时候是比较次数是3n,交换次数为0 

    T[n] = 2T[n/2] + P(n) =2[2T[n/4]+P(n/2)]+P(n)=2[2T[n/4]+(3/2)n]+3n=2[2T[n/4]]+3n+3n=3n+3n+.(logn个)..+3n=3n*Log

    所以此时时间复杂度O(nLogn

    最坏情况:

    数组是排好序的,每次划分都是划分成 1和n-1个元素,此时P(n)=3n,T[1]=0

    T[n] = T[n-1] + T[1] + P(n)=T[n-1] + T[1] +3n=(T[n-2]+T[1]+ 3n) + T[1] +3n=3n*n=3n2

    所以此时时间复杂度O(n2 

    下面给出C#的通用快速排序算法实现:

    startIndex为排序区间的数组元素下标,通常为0,
    endIndex为排序区间的数组元素下标,通常为array.Length-1
     class QuickSort<T> where T : IComparable<T>
        {
            public void Sort(T[] array, int startIndex, int endIndex)
            {
                if (startIndex < endIndex)
                {
                    int pivot = Partition(array, startIndex, endIndex);
                    Sort(array, startIndex, pivot - 1);
                    Sort(array, pivot + 1, endIndex);
                }
            }
            public static int Partition(T[] array, int leftIndex, int rightIndex)
            {
                //leftIndex的位置空出来了
                T pivotValue = array[leftIndex];
                //将所有<pivotValue的值移动到pivotValue的左边(不稳定排序,因为相等值得相对位置可能被此步骤改变)
                //将所有>=pivotValue的值移到右边
                //移动的结果就是所有<pivotValue的值都在pivotValue的左边,>=它的都在右边
                //记录之后pivotValue所在位置,返回该位置,完成一次分区划分。
                while (leftIndex < rightIndex)
                {
                    //因为是leftIndex先空出来位置,所以第一步要从右侧rightIndex向左寻找小于pivotValue的数值位置
                    while (leftIndex < rightIndex && array[rightIndex].CompareTo(pivotValue) >= 0) rightIndex--;
                    //将找到的小于pivotValue的位置的元素放到空出的leftIndex位置,leftIndex++
                    if (leftIndex < rightIndex) array[leftIndex++] = array[rightIndex];
                    //leftIndex向右寻找>=pivotValue的值的位置
                    while (leftIndex < rightIndex && array[leftIndex].CompareTo(pivotValue) < 0) leftIndex++;
                    //将找到的>=pivotValue的位置的leftIndex元素放到上一步空出的rightIndex位置
                    //此时leftIndex所在位置变成待插入位置,重新回到外圈循坏的初始状态
                    if (leftIndex < rightIndex) array[rightIndex--] = array[leftIndex];
                }
                //最后while循环结束的位置就是leftIndex==rightIndex,并且这个位置是空出来的,正好把pivotValue放到这个位置
                //这就是轴的概念,轴两边的值时由轴正好分开的,一边小于轴,一边大于等于轴
                array[leftIndex] = pivotValue;
                return leftIndex;
            }
        }

    作者:Andy Zeng

    欢迎任何形式的转载,但请务必注明出处。

     http://www.cnblogs.com/andyzeng/p/3684297.html

  • 相关阅读:
    Java学习个人备忘录之继承
    Java学习个人备忘录之文档注释
    Java学习个人备忘录之数组工具类
    Java学习个人备忘录之关键字static
    Java学习个人备忘录之构造函数&this
    Java学习个人备忘录之面向对象概念
    Java学习个人备忘录之数组
    Java学习个人备忘录之入门基础
    hdoj1162-Eddy's picture(kruskal)
    hdoj1102-Constructing Roads(Kruskal)
  • 原文地址:https://www.cnblogs.com/andyzeng/p/3684297.html
Copyright © 2011-2022 走看看