Skip to content
New issue

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

快速排序各个版本以及边界情况(bobo 老师版) #33

Open
sl1673495 opened this issue May 27, 2020 · 0 comments
Open

快速排序各个版本以及边界情况(bobo 老师版) #33

sl1673495 opened this issue May 27, 2020 · 0 comments

Comments

@sl1673495
Copy link
Owner

sl1673495 commented May 27, 2020

初始版

最初的版本,对于 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,继续递归… 排序树依然很不平衡。

image

三路快排

最终优化版就是三路快排了,顾名思义这种快排就是把数组区分成 < v, ===v, >v 三个区间,然后把等于 v 的区间排除掉,继续对剩余的两个区间进行递归的快速排序。

image

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]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant