算法思想:分治法,把一个序列分成两个,其中一边的元素小于另一边的元素。一直这样分下去,直到只有一个元素的时候返回。然后回推往前看,所有的元素已经按大小归位。算法的难点在于将一个序列分成两列的过程,使得一边元素小于另一边,下面给予说明。
设数组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*Logn
所以此时时间复杂度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
欢迎任何形式的转载,但请务必注明出处。