面试 面试 算法 十大经典排序算法之快速排序 云少 2019-03-06 2019-03-06 十大经典排序算法之快速排序 master ,这是我的小站 ,欢迎访问哦~~
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好,可是这是为什么呢,我也不知道。好在我的强迫症又犯了,查了 N 多资料终于在《算法艺术与信息学竞赛》上找到了满意的答案:
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
1. 算法步骤 从数列中挑出一个元素,称为 “基准”(pivot); 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作; 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序; 递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
基本思想:(分治) 先从数列中取出一个数作为key值; 将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边; 对左右两个小数列重复第二步,直至各区间只有1个数。 辅助理解:挖坑填数 初始时 i = 0; j = 9; key=72 由于已经将a[0]中的数保存到key中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。 从j开始向前找一个比key小的数。当j=8,符合条件,a[0] = a[8] ; i++ ; 将a[8]挖出再填到上一个坑a[0]中。 这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。 这次从i开始向后找一个大于key的数,当i=3,符合条件,a[8] = a[3] ; j– ; 将a[3]挖出再填到上一个坑中。 1 2 数组:72 - 6 - 57 - 88 - 60 - 42 - 83 - 73 - 48 - 85 0 1 2 3 4 5 6 7 8 9
此时 i = 3; j = 7; key=72 再重复上面的步骤,先从后向前找,再从前向后找。 从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++; 从i开始向后找,当i=5时,由于i==j退出。 此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将key填入a[5]。 1 2 数组:48 - 6 - 57 - 88 - 60 - 42 - 83 - 73 - 88 - 85 0 1 2 3 4 5 6 7 8 9
可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。 1 2 数组:48 - 6 - 57 - 42 - 60 - 72 - 83 - 73 - 88 - 85 0 1 2 3 4 5 6 7 8 9
2. 动图演示
3. JavaScript 代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 function quickSort (arr, left, right ) { var len = arr.length , partitionIndex, left = typeof left != 'number' ? 0 : left, right = typeof right != 'number' ? len - 1 : right; if (left < right) { partitionIndex = partition (arr, left, right); quickSort (arr, left, partitionIndex-1 ); quickSort (arr, partitionIndex+1 , right); } return arr; } function partition (arr, left ,right ) { var pivot = left, index = pivot + 1 ; for (var i = index; i <= right; i++) { if (arr[i] < arr[pivot]) { swap (arr, i, index); index++; } } swap (arr, pivot, index - 1 ); return index-1 ; } function swap (arr, i, j ) { var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } functiion paritition2 (arr, low, high ) { let pivot = arr[low]; while (low < high) { while (low < high && arr[high] > pivot) { --high; } arr[low] = arr[high]; while (low < high && arr[low] <= pivot) { ++low; } arr[high] = arr[low]; } arr[low] = pivot; return low; } function quickSort2 (arr, low, high ) { if (low < high) { let pivot = paritition2 (arr, low, high); quickSort2 (arr, low, pivot - 1 ); quickSort2 (arr, pivot + 1 , high); } return arr; }
4. Python 代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def quickSort (arr, left=None , right=None ): left = 0 if not isinstance (left,(int , float )) else left right = len (arr)-1 if not isinstance (right,(int , float )) else right if left < right: partitionIndex = partition(arr, left, right) quickSort(arr, left, partitionIndex-1 ) quickSort(arr, partitionIndex+1 , right) return arr def partition (arr, left, right ): pivot = left index = pivot+1 i = index while i <= right: if arr[i] < arr[pivot]: swap(arr, i, index) index+=1 i+=1 swap(arr,pivot,index-1 ) return index-1 def swap (arr, i, j ): arr[i], arr[j] = arr[j], arr[i]
5. Go 代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func quickSort (arr []int ) []int { return _quickSort(arr, 0 , len (arr)-1 ) } func _quickSort (arr []int , left, right int ) []int { if left < right { partitionIndex := partition(arr, left, right) _quickSort(arr, left, partitionIndex-1 ) _quickSort(arr, partitionIndex+1 , right) } return arr } func partition (arr []int , left, right int ) int { pivot := left index := pivot + 1 for i := index; i <= right; i++ { if arr[i] < arr[pivot] { swap(arr, i, index) index += 1 } } swap(arr, pivot, index-1 ) return index - 1 } func swap (arr []int , i, j int ) { arr[i], arr[j] = arr[j], arr[i] }
6. C++版 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Paritition1 (int A[], int low, int high) { int pivot = A[low]; while (low < high) { while (low < high && A[high] >= pivot) { --high; } A[low] = A[high]; while (low < high && A[low] <= pivot) { ++low; } A[high] = A[low]; } A[low] = pivot; return low; } void QuickSort (int A[], int low, int high) { if (low < high) { int pivot = Paritition1 (A, low, high); QuickSort (A, low, pivot - 1 ); QuickSort (A, pivot + 1 , high); } }
7. java版 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public static void quickSort (int a[],int l,int r) { if (l>=r) return ; int i = l; int j = r; int key = a[l]; while (i<j){ while (i<j && a[j]>=key) j--; if (i<j){ a[i] = a[j]; i++; } while (i<j && a[i]<key) i++; if (i<j){ a[j] = a[i]; j--; } } a[i] = key; quickSort(a, l, i-1 ); quickSort(a, i+1 , r); }
key值的选取可以有多种形式,例如中间数或者随机数,分别会对算法的复杂度产生不同的影响。