JavaScript 排序

JavaScript 数组自带排序算法Array.prototype.sort()

arr.sort([compareFunction])

compareFunction可选( 注意比较函数的返回值的规定)

用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。

如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:

如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前(升序);
如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);
如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前(降序)。
compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。

Array.prototype.sort 的内部实现:
Quicksort is generally considered to be efficient and fast and so is used by V8 as the implementation for Array.prototype.sort() on arrays with more than 23 items. For less than 23 items, V8 uses insertion sort[2]. Merge sort is a competitor of quicksort as it is also efficient and fast but has the added benefit of being stable. This is why Mozilla and Safari use it for their implementation of Array.prototype.sort().
参考自:https://imweb.io/topic/565cf7253ad940357eb99881
原文:https://humanwhocodes.com/blog/2012/11/27/computer-science-in-javascript-quicksort/

快速排序:

每次选定一个基准,使数组中小于基准的数都在数组的左边,大于基准的数在数组的右边,然后对左边和右边的数组也进行递归的处理,直到完成排序。

var quickSort = function(arr) {
  if (arr.length <= 1) {
    return arr
  }
  var pivotIndex = Math.floor(arr.length / 2)
  var pivot = arr.splice(pivotIndex, 1)[0]
  var left = []
  var right = []
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i])
    } else {
      right.push(arr[i])
    }
  }
  return quickSort(left).concat([pivot], quickSort(right))
}

快排时间复杂度最坏情况是O(n2),平均时间复杂度是O(nlogn)

但是上面这种写法性能上不是很好,主要有:
1、每次执行时会额外创建两个数组空间,产生空间复杂度。
博主指出:
a使用concat()方法连接,也会产生O(n)的时间复杂度,但是对比与前面for循环的时间复杂度。对整体的时间复杂度没有影响。
b:获取基准点时使用splice()方法,会对数组进行一次拷贝?然而实际上进行拷贝的应该是slice()方法,splice()是直接对原数组进行操作。

但是额外空间这一点,我觉得是可以改进的,参考《剑指Offer》提出的Partition函数思路进行以下改进:

function quickSort(arr, start, end) {
  if (
    Object.prototype.toString.call(arr).slice(8, -1) === "Array" &&
    arr.length > 0
  ) {
    //判断类型是数组且长度大于0

    start = start || 0 //数组的起终位置如果没传则默认整个数组排序
    end = end || arr.length - 1
    if (start === end) return //传入参数是否合理

    let index = partiton(arr, start, end)
    if (index > start) quickSort(arr, start, index - 1)
    if (index < end) quickSort(arr, index + 1, end)
  }
}
function partiton(arr, start, end) {
  if (arr.length === 0 || start >= end) return
  let index = Math.floor(Math.random() * (end - start + 1) + start) //Math.random()方法产生一个[start,end]之间的数
  ;[arr[index], arr[end]] = [arr[end], arr[index]] //交换这两个数 ,把基准换到数组最后, ES6 写法
  let small = start - 1 //small指向大于基准的前面一个位置
  for (index = start; index < end; ++index) {
    //从前到后遍历数组,与基准做比较
    if (arr[index] < arr[end]) {
      //小于基准的则small右移
      ++small
      if (small != index) {
        //small如果不是当前指向的那个元素,也就代表small和index正在遍历的那个数中间还有大于基准的数,把小于基准的数交换移到大于基准数的前面
        ;[arr[index], arr[small]] = [arr[small], arr[index]]
      }
    }
  }
  ++small
  ;[arr[small], arr[end]] = [arr[end], arr[small]] //最后把基准与small后面的一位交换,放在中间位置
  return small
}

另一种实现:


// 划分操作函数
function partition(array, left, right) {
    // 用index取中间值而非splice
    const pivot = array[Math.floor((right + left) / 2)]
    let i = left
    let j = right

    while (i <= j) {
        while (compare(array[i], pivot) === -1) {
            i++
        }
        while (compare(array[j], pivot) === 1) {
            j--
        }
        if (i <= j) {
            swap(array, i, j)
            i++
            j--
        }
    }
    return i
}

// 比较函数
function compare(a, b) {
    if (a === b) {
        return 0
    }
    return a < b ? -1 : 1
}

function quick(array, left, right) {
    let index
    if (array.length > 1) {
        index = partition(array, left, right)
        if (left < index - 1) {
            quick(array, left, index - 1)
        }
        if (index < right) {
            quick(array, index, right)
        }
    }
    return array
}
function quickSort(array) {
    return quick(array, 0, array.length - 1)
}

// 原地交换函数,而非用临时数组
function swap(array, a, b) {
    ;[array[a], array[b]] = [array[b], array[a]]
}
const Arr = [85, 24, 63, 45, 17, 31, 96, 50];
console.log(quickSort(Arr));
// 本版本来自:https://juejin.im/post/5af4902a6fb9a07abf728c40#heading-12

冒泡排序:
循环遍历每一对相邻的数,如果左边的大于右边的则交换位置。这样每次会使得最大的数交换到数组的最后面,小的数则会像前。像冒泡泡一样排好顺序。

简单的冒泡排序:

function bubbleSort(arr) {
  for (var i = 0; i < arr.length; i++) {
    for (var j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        ;[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]] //利用ES6的解构赋值来完成swap函数
      }
    }
  }
  return arr
}

利用ES6的解构赋值来完成swap函数

改进版本:利用一个参数记录最后交换的位置,在该位置之后的代表已经排好顺序,则省去多余的遍历步骤。

function bubbleSort1(arr) {
  var i = arr.length - 1
  while (i > 0) {
    let last = 0 //last用于记录最后一次交换的位置,先初始化为0
    for (let j = 0; j < i; j++) {
      if (arr[j] > arr[j + 1]) {
        last = j //记录最后一次交换的位置
        ;[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]] //利用ES6的解构赋值来完成swap函数
      }
    }
    i = last
  }
  return arr
}

选择排序:
每次都从无序的里面选出最小的插在有序的后面(与无序的第一个交换位置,使之有序),直到整个数组有序。

function seletionSort(arr) {
  var minIndex
  for (var i = 0; i < arr.length - 1; i++) {
    minIndex = i
    for (var j = i + 1; j < arr.length; j++) {
      //不要把循环的条件写错...
      if (arr[j] < arr[minIndex]) {
        minIndex = j
      }
    }
    ;[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
  }
  return arr
}

插入排序:

function insertionSort(array) {
  if (Object.prototype.toString.call(array).slice(8, -1) === "Array") {
    //判断类型是数组的通用方法
    for (var i = 1; i < array.length; i++) {
      var key = array[i]
      var j = i - 1
      while (j >= 0 && array[j] > key) {
        array[j + 1] = array[j]
        j--
      }
      array[j + 1] = key
    }
    return array
  } else {
    return "not an Array"
  }
}

toString方法返回一个表示该对象的字符串。
如果此方法在自定义对象中未被覆盖,toString() 返回 “[object type]”,其中 type 是对象的类型。
以下是数组对象直接调用与调用Object原型中的区别:

var arr = [6, 1, 3, 8, 7]
console.log(Object.prototype.toString.call(arr))
console.log(arr.toString())
console.log(typeof arr.toString() === "string")
		

输出是:
在这里插入图片描述

//二分改进
function insertionSortBinary(array) {
  if (Object.prototype.toString.call(array).slice(8, -1) === "Array") {
    for (let i = 1; i < array.length; i++) {
      var key = array[i],
        left = 0,
        right = i - 1 //[left,right]是查找key应该插入到的位置的区间
      while (left <= right) {
        //不满足的时候,left比right多1,left即是key应该插入的位置
        var middle = parseInt((left + right) / 2)
        if (key < array[middle]) {
          right = middle - 1
        } else {
          left = middle + 1
        }
      }
      for (var j = i - 1; j >= left; j--) {
        //将[left,i-1]的数都向后移动一位
        array[j + 1] = array[j]
      }
      array[left] = key //将key插入到left位置,完成排序
    }
    return array
  } else {
    return "not an Array"
  }
}

归并排序
步骤:
<1>.把长度为n的输入序列分成两个长度为n/2的子序列;
<2>.对这两个子序列分别采用归并排序;
<3>.将两个排序好的子序列合并成一个最终的排序序列。

function mergeSort (arr) {
    if(arr.length < 2){
        return arr
    }
    var middle = Math.floor(arr.length / 2)
    var left = arr.slice(0, middle)
    var right = arr.slice(middle)
    return merge(mergeSort(left), mergeSort(right))
}
function merge (left, right) {
    var result = []
    while(left.length && right.length){
        if(left[0] <= right[0]){
            result.push(left.shift())
        } else {
            result.push(right.shift())
        }
    }
    while(left.length){
        result.push(left.shift())
    }
    while(right.length){
        result.push(right.shift())
    }
    return result
}

测试:
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值