zoukankan      html  css  js  c++  java
  • 快排,随机快排,双路快排,三路快排的理解

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

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

    代码如下

    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(n²)级别,为什么?

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

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

    我们无法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)的时间复杂度

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

  • 相关阅读:
    关于工作流程引擎中的岗位的设置的问题
    将要发布的开源CCOA的照片一览
    关于多个checkbox在IE6下自由表单设计器中的兼容问题
    ccflow流程自动发起功能增加,如何按指定的时间触发方式发起流程?
    Windows 如何远程强行关机
    Report bulder
    微软sample and code down web address
    如何查看sql server的死锁情况
    如何读取数据的所有用户表
    复制和数据库镜像
  • 原文地址:https://www.cnblogs.com/lyz1991/p/6329475.html
Copyright © 2011-2022 走看看