We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
最初的版本,对于 partition 这一步中的取基准值,我们直接取最左边的值,然后把小于这个值的划分在它的左边,把大于等于这个值的划分在它右边,然后分别对左和右再进一步做递归的快速排序。
partition
它的问题在于,如果数组是个近似有序的数组的话,它的时间复杂度会退化到接近 On² 的级别。排序分治形成的二叉树会非常不平衡,退化成接近链表。
function partition(arr, left, right) { // 取一个基准值 取第一项 let pivot = arr[left] // arr[left+1...index] < pivot, arr[index+1...i) > pivot let index = left for (let i = left + 1; i <= right; i++) { let num = arr[i] if (num < pivot) { swap(arr, index + 1, i) index++ } } swap(arr, left, index) return index }
对于上述的近似有序数组的问题,我们可以选择每次的基准值选取一个随机值,这样退化成 On² 的可能性就趋近于零了。
function partition(arr, left, right) { // 取一个基准值 取随机值 let rand = random(left, right) swap(arr, left, rand) let pivot = arr[left] // arr[left+1...index] < pivot, arr[index+1...i) > pivot let index = left for (let i = left + 1; i <= right; i++) { let num = arr[i] if (num < pivot) { // 如果当前值小于基准值的话,就交换到index + 1的位置去。 // 扩充了index的范围 [index...], pivot, [...right] swap(arr, index + 1, i) index++ } } swap(arr, left, index) return index }
但是这样还是会有问题,对于重复元素很多的数组,只会简单的把大于等于基准值的元素简单粗暴的划分到右侧范围里,然后继续递归的快排,浪费了大量时间。 比如 [8, 9, 9, 9, 9, 9, 9, 10],下一步只会拿掉左边的 [8],进一步的去排 [9, 9, 9, 9, 9, 9, 10],然后很有可能又只是拿出了一个 9,继续递归… 排序树依然很不平衡。
[8, 9, 9, 9, 9, 9, 9, 10]
[9, 9, 9, 9, 9, 9, 10]
最终优化版就是三路快排了,顾名思义这种快排就是把数组区分成 < v, ===v, >v 三个区间,然后把等于 v 的区间排除掉,继续对剩余的两个区间进行递归的快速排序。
< v
===v
>v
function sortArray(arr) { _quickSort(arr, 0, arr.length - 1) return arr } /** * 对 arr[l...r] 部分进行快速排序 * @param {number[]} arr * @param {number} l 左边界 * @param {number} r 右边界 */ function _quickSort(arr, l, r) { if (l >= r) { return } let [p, q] = partition(arr, l, r) _quickSort(arr, l, p) _quickSort(arr, q, r) } function random(low, high) { return Math.round(Math.random() * (high - low)) + low } function swap(arr, i, j) { let temp = arr[i] arr[i] = arr[j] arr[j] = temp } /** * 对 arr[l...r] 部分进行快速排序 * @param {number[]} arr * @param {number} l 左边界 * @param {number} r 右边界 * @returns {number} 返回索引值p,使得arr[l...p-1] < arr[p] < arr[p+1...r] */ function partition(arr, left, right) { // 取一个基准值 取随机值 let rand = random(left, right) swap(arr, left, rand) let pivot = arr[left] // 三路 注意看注释里的区间 let lt = left // arr[left + 1...lt] < v let gt = right + 1 // arr[gt...r] > v let index = left + 1 // arr[lt + 1...index) === v while (index < gt) { let num = arr[index] if (num < pivot) { swap(arr, index, lt + 1) lt++ index++ } else if (num > pivot) { swap(arr, index, gt - 1) gt-- } else if (num === pivot) { index++ } } swap(arr, left, lt) return [lt - 1, gt] }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
初始版
最初的版本,对于
partition
这一步中的取基准值,我们直接取最左边的值,然后把小于这个值的划分在它的左边,把大于等于这个值的划分在它右边,然后分别对左和右再进一步做递归的快速排序。它的问题在于,如果数组是个近似有序的数组的话,它的时间复杂度会退化到接近 On² 的级别。排序分治形成的二叉树会非常不平衡,退化成接近链表。
随机数优化版
对于上述的近似有序数组的问题,我们可以选择每次的基准值选取一个随机值,这样退化成 On² 的可能性就趋近于零了。
但是这样还是会有问题,对于重复元素很多的数组,只会简单的把大于等于基准值的元素简单粗暴的划分到右侧范围里,然后继续递归的快排,浪费了大量时间。
比如
[8, 9, 9, 9, 9, 9, 9, 10]
,下一步只会拿掉左边的 [8],进一步的去排[9, 9, 9, 9, 9, 9, 10]
,然后很有可能又只是拿出了一个 9,继续递归… 排序树依然很不平衡。三路快排
最终优化版就是三路快排了,顾名思义这种快排就是把数组区分成
< v
,===v
,>v
三个区间,然后把等于 v 的区间排除掉,继续对剩余的两个区间进行递归的快速排序。The text was updated successfully, but these errors were encountered: