基于比较的算法之四:快速排序

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

设数组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

posted @ 2014-04-23 23:42  AndyZeng  阅读(317)  评论(0编辑  收藏  举报