快排,随机快排,双路快排,三路快排的理解

再讲快排之前,首先对于任何一个数组,无论之前是多么杂乱,排完之后是不是一定存在一个数作为分界点(也就是所谓的支点),在支点左边全是小于等于这个支点的,然后在这个支点右边的全是大于等于这个支点的,快排过程就是寻找这个支点过程

先看普通的快排(普通单路快排)

代码如下

let findIndex = (arr, l, len) => {
  let par = arr[l], j = l
  for (let i = l + 1; i <= len; i++) {
    if (arr[i] < par) {
      swap(arr, ++j, i)
    }
  }
  swap(arr, j, l)
  return j
}
let _quick = (arr, l, len) => {
  if (l >= len) {
    return
  }
  let p = findIndex(arr, l, len)
  _quick(arr, l, p)
  _quick(arr, p + 1, len)
}
let quick = (arr) => {
  let len = arr.length
  _quick(arr, 0, len - 1)
  return arr
}

这是一个普通单路快排实现的代码,如果是一般杂乱的数组,测试之后这个代码的运行时间是很短的,但是这里存在一个问题,就是如果我待排序的数组是一个顺序性很大数组(比如[1,2,3,4,5,6,7]),那么这个代码将会退化到O(n2)级别,为什么?

首先快排是个遍历下个数字并确定支点过程,首先先假设第一个就是支点,那么后面的书如果比支点大,说明支点不需要移动(因为右边统一比支点大),如果后面的数比支点小,说明这个数字应该在支点前面,对不对,这个时候支点实质上应该向左移动一位(因为之前那个位置让给比他小的那个数字了,注意这里实质上,因为本轮排序没结束,还没有找到支点应该在的准确位置,所以支点还是第一个),然后将加1后支点所在位的当前数字和当前数交换(因为新的位置已经被支点占据了,而原支点位置是比支点小的数字),依次类推,最后找到所有混乱数组里面,最后一个小于支点的数字,统计出所有小于支点的总数是k,那么这个k就是支点应该在这个混乱数组里的具体位置!然后再依此支点为分界点,递归排序

ok,那么上面代码存在什么问题呢?假设待排序数组(比如[1,2,3,4,5,6,7]),默认取第一个,可是往后面遍历的时候,后面数字全是大于1的,第一轮循环结束,时间复杂度n,再取第二个2,结果发现后面的又是全大于2的,依次循环,不难发现用上述代码是n2的复杂度

我们无法100%完全避免这种退化现象的,但是我们可以尽量避免。看下面随机单路快排代码

let findIndex = (arr, l, len) => {
  let idx = Math.floor(Math.random() * (len -l) + l)
  swap(arr, l, idx)
  let par = arr[l], j = l
  for (let i = l + 1; i <= len; i++) {
    if (arr[i] < par) {
      swap(arr, ++j, i)
    }
  }
  swap(arr, j, l)
  return j
}
let _quick = (arr, l, len) => {
  if (l >= len) {
    return
  }
  let p = findIndex(arr, l, len)
  _quick(arr, l, p)
  _quick(arr, p + 1, len)
}
let quick = (arr) => {
  let len = arr.length
  _quick(arr, 0, len - 1)
  return arr
}

这个时候,每次虽然仍然是取第一位作为支点,但是呢,我们的支点是经过随机化处理的,也就是说如果有n个数字,第一次正好取到最小的,概率是1/n,第二次又正好是最小的也就是1/n-1,可以这样处理让快排退化的概率是很低的,当然如果真的出现了那种情况,那只能认吧,因为快排本身是期望复杂度O(log2N),这是我们的期望值

乍一看,似乎现在随机快排已经很不错了,是的吗?

乍一看似得,可是假设我们的待排序数组是一个有许多重复数值的数组呢?比如[4,2,2,2,3,6,5],那么我们数组又将会分成两个不平衡的两部分,怎么避免,双路快排登场

let findIndex = (arr, l, r) => {
  swap(arr, l, Math.floor(Math.random() * (r - l + 1) + l))
  let j =r, i = l + 1
  let begin = arr[l]
 // [l+1, i), (j, r]
  while (i <= j) {
    while (i <= r && arr[i] < begin) {
      i++
    }
    while (j >= l + 1 && arr[j] > begin) {
      j--
    }
    swap(arr, i++, j--)
  }
  swap(arr, l, j)
  return j
}
let insert = (arr, l, end) => {
  for (let i = l + 1; i <= end; i++) {
    let e = arr[i]
    let j
    for (j = i; j > 0 && e < arr[j - 1]; j--) {
      arr[j] = arr[j - 1]
    }
    arr[j] = e
  }
  return arr
}
let _quick = (arr, l, len) => {
  if (l >= len - 15) {
    return insert(arr, l, len)
  }
  let p = findIndex(arr, l, len)
  _quick(arr, l, p - 1)
  _quick(arr, p + 1, len)
}
let quick = (arr) => {
  let len = arr.length
  _quick(arr, 0, len - 1)
  return arr

}

注意双路快排指针还是一个,但是是从两边夹攻,他的结果就是即使你是和指针相等的,我也交换,这样就避免了,不平衡的出现,我们的快排又回到O(nlog2N)的时间复杂度

相比较双路快排是找大于或者等于对应位置,三路快排是说我找的是一个区间,找的是一个等于指针的那个区间

时间: 2024-10-13 00:58:22

快排,随机快排,双路快排,三路快排的理解的相关文章

快速排序—三路快排 vs 双基准

快速排序被公认为是本世纪最重要的算法之一,这已经不是什么新闻了.对很多语言来说是实际系统排序,包括在Java中的Arrays.sort. 那么快速排序有什么新进展呢? 好吧,就像我刚才提到的那样(Java 7发布两年后)快速排序实现的Arrays.sort被双基准(dual-pivot)排序的一种变体取代了.这篇文章不仅展示了为什么这个变化如此优秀,而且让我们看到Jon Bentley和Joshua Bloch的谦逊. 我当时做了什么? 与所有人一样,我想实现这个算法并且对一千万个数值排序(随机

普林斯顿大学算法课 Algorithm Part I Week 3 重复元素排序 - 三路快排 Duplicate Keys

很多时候排序是为了对数据进行归类,这种排序重复值特别多 通过年龄统计人口 删除邮件列表里的重复邮件 通过大学对求职者进行排序 若使用普通的快排对重复数据进行排序,会造成N^2复杂度,但是归并排序和三路快排就没有这样的问题. 归并排序对重复数据排序的比较在1/2NlgN和NlgN之间 三路快排 目标:将数据分成三个区间(3-way partitioning) lt和gt区间内的元素都和比较元素v相等 lt左边的元素都比v小 gt右边的元素都比v大 性能 三路快排的复杂度比普通快排小,主要取决于数据

普林斯顿公开课 算法3-3:三路快排

很多时候排序是为了对数据进行归类,比如对城市进行排序,对员工的职业进行排序.这种排序的特点就是重复的值特别多. 如果使用普通的快排对这些数据进行排序,会造成N^2复杂度,但是归并排序和三路快排就没有这样的问题. 三路快排 三路快排的基本思想就是,在对数据进行分区的时候分成左中右三个部分,中间都是相同的值,左侧小于中间,右侧大于中间. 性能 三路快排的复杂度比普通快排小,主要取决于数据中重复数据的数量.重复数据越多,三路快排的复杂度就越接近于N. 代码 public class Quick3 {

leetcode 75 Sort Colors 计数排序,三路快排

解法一:计数排序:统计0,1,2 的个数 时间复杂度:O(n) 空间复杂度:O(k)    k为元素的取值范围, 此题为O(1) class Solution { public: void sortColors(vector<int>& nums) { int count[3] = {0}; //存放0,1,2三个元素的频率 for(int i=0;i<nums.size();i++){ assert(nums[i] >=0 && nums[i]<=2

三路快排

1 void partition(int arr[], int l, int r, int num)//三路快排 2 { 3 int less = l - 1; 4 int more = r + 1; 5 int cur = 0; 6 while (cur < more) 7 { 8 if (arr[cur] < num)//左边交换,直接下一个数 9 { 10 swap(arr[++less], arr[cur++]); 11 } 12 else if (arr[cur] > num)

算法——快速排序(单路、双路、三路)

单路 1 # include <iostream> 2 # include <ctime> 3 # include <algorithm> 4 # include "InsertionSort.h" 5 6 //对arr[l...r]部分进行partition操作 7 // 返回p,使arr[l...p-1] < arr[p]; arr[p+1...r] > arr[p] 8 template <typename T> 9 i

双路快速排序法

1.算法出现的背景 之前讲的,当我们排序的是一个近乎有序的序列时,快速排序会退化到一个O(n^2)级别的排序算法,而对此的改进就是 引入了随机化快速排序算法:但是当我们的排序的是一个数值重复率非常高的序列时,此时随机化快速排序算法就不再起作用 了,而将会再次退化为一个O(n^2)级别的排序算法,那为什么会出现这种情况呢?且听下面的分析: 如上图所示就是之前分析的快速排序算法的partition的操作原理,我们通过判断此时i索引指向的数组元素e>v还是<v, 将他放在橙色或者是紫色两个不同的位置

高级排序算法之双路快速排序

双路快速排序算法分析 对于具有大量重复数据的排序按照之前的方式性能会很低,现在我们增加两个标志,想办法把大量重复的数据分到两部分,例如设置v作为标志数据,让等于v的数据分为两部分,如下图所示,这样可以避免两边的数据出现一边倒的情况. 根据以上算法的思想,代码修改如下: //双路快速排序算法:解决具有大量重复源数据排序慢的问题 template<typename T> int _partition2Ways(T arr[], int l, int r) { //优化点2:通过随机选择元素标志,防

TC358775XBG:MIPI DSI转双路LVDS芯片简介

TC358775XBG是一颗MIPI DSI转双路LVDS芯片,通信方式:IIC/MIPI command mode,分辨率1920*1200,封装形式:BGA64.