原理
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
使用了分冶法。
分冶法是把一个规模为N的问题分成两个或多个较小的与原问题类型相同的子问题,通过对子问题的求解,并把子问题的解合并起来从而构成整个问题的解,即对问题各个击破,分而治之。如果子问题的的规模仍然相当大,仍不足以很容易的求得它的解,这时可以对此子问题重复的应用分冶策略。
复杂度
时间复杂度
最坏情况:最坏情况发生在每次划分过程产生的两个区间分别包含n-1个元素和1个元素的时候。O(n2),退化成冒泡排序,即每次都排好一个元素的顺序。
最好得情况:每次划分过程产生的区间大小都为n/2,则快速排序法运行就快得多了。O(nlogn)
平均情况:O(nlogn)
空间复杂度
最优的情况下空间复杂度为:O(logn) :每一次都平分数组的情况。
最差的情况下空间复杂度为:O( n ) :退化为冒泡排序的情况。
代码
老版本(没有减小重复元素的影响,可以看作不完全的三路快排)
public static void main(String[] args) { int[] n = { 2, 1, 7, 4, 8, 5 }; quickSort(n, 0, n.length - 1); for (int i : n) { System.out.print(i + " "); } } public static void quickSort(int[] list, int left, int right) { if (left < right) { int middle = getMiddle(list, left, right); // 将list数组进行一分为二 quickSort(list, left, middle - 1); // 对低字表进行递归排序 quickSort(list, middle + 1, right); // 对高字表进行递归排序 } } public static int getMiddle(int[] list, int left, int right) { int tmp = list[left]; // 数组的第一个作为中轴 while (left < right) { while (left < right && list[right] >= tmp) { right--; } list[left] = list[right]; // 比中轴小的记录移到低端 while (left < right && list[left] <= tmp) { left++; } list[right] = list[left]; // 比中轴大的记录移到高端 } list[left] = tmp; // 中轴记录到尾 return left; // 返回中轴的位置 }
二路快排(简单,最常用)
public static void quickSort2(int[] n, int left, int right) { //[left,slow) 是小于key的数据集; (slow,right]是大于key的数据集 if (n == null || n.length < 1 || left >= right) { return; } //int[] n = { 7, 1, 2, 8, 7, 2, 12, 7 }; int key = n[left];//比较的基础值(可优化:见优化点) int slow = left;//基础值的下标(最后基础值也会放在这个下标点上) int fast = left + 1;//遍历数组的指针,从基础值的下一个元素开始 while (fast <= right) { if (n[fast] < key) { //大于等于key不动,小于key跟 n[slow + 1]交换 swap(n, ++slow, fast); } fast++; } swap(n, left, slow);//基础值还在left位置,交换一下,最后放在最右边的小于基础值的下标上 //System.out.println(JSON.toJSONString(n) + " " + slow); quickSort2(n, left, slow - 1); quickSort2(n, slow + 1, right); }
public static void swap(int[] n, int left, int right) {
if (n[left] == n[right]) {
return;
}
n[left] = n[left] ^ n[right];
n[right] = n[left] ^ n[right];
n[left] = n[left] ^ n[right];
}
三路快排(有效避免重复元素的反复排序)
public static void quickSort3(int[] n, int left, int right) { //[left,slow)是小于key的数据集; (largeIndex,right]是大于key的数据集; [slow,largeIndex]是等于key的数据集 if (n == null || n.length < 1 || left >= right) { return; } //int[] n = { 7, 1, 2, 8, 7, 2, 12, 7 }; int key = n[left];//比较的基础值(可优化:见优化点) int slow = left;//基础值的下标(如果基础值有重复元素,此为最左下标) int fast = left + 1;//遍历数组的指针,从基础值的下一个元素开始 int largeIndex = right;//由于大于基础值的会被交换到后面,所以最后此值为基础值下标(如果基础值有重复元素,此为最右下标) while (fast <= largeIndex) { if (n[fast] < key) { swap(n, fast++, slow++); } else if (n[fast] > key) { swap(n, fast, largeIndex--); } else { fast++; } } //System.out.println(JSON.toJSONString(n) + " " + slow + " " + largeIndex); quickSort3(n, left, slow - 1); quickSort3(n, largeIndex + 1, right); }
优化点
中间值(解决基础值问题)
与一般的快速排序方法不同,它并不是选择待排数组的第一个数作为中轴,而是选用待排数组最左边、最右边和最中间的三个元素的中间值作为中轴。这一改进对于原来的快速排序算法来说,主要有两点优势:
(1) 首先,它使得最坏情况发生的几率减小了。
(2) 其次,未改进的快速排序算法为了防止比较时数组越界,在最后要设置一个哨点。
分区间隔小时可使用插入排序
快速排序的优化可考虑当分区间隔小的的时候转而使用插入排序。
五分钟学算法 双路、三路快排(对于有很多重复值的数据集很有效)
三路快排(解决重复元素的反复排序问题)
见代码