js - 排序方式(八种)

958 阅读7分钟

前言

日常开发中,常常会用到排序,但是究竟有哪些常用的排序方法呢? 各位大佬请往下看

准备工作

定义常用方法

const Compare = {
  LESS_THAN: -1,
  BIGGER_THAN: 1,
  EQUALS: 0
};

//比较大小
function compareFn(a, b) {
  if (a === b) {
    return Compare.EQUALS;
  }
  return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN;
}
//交换位置
function swap(array, a, b) {
  [array[a], array[b]] = [array[b], array[a]];
}

冒泡排序

定义

比较所有相邻的两个项,如果第一个比第二个大,则交换它们.元素项向移动至正确的顺序,就好像气泡向上升至表面一样.

实现
function bubbleSort(array){
  const { length } = array
  for(let i = 0; i < length; i++){ 
    for(let j = 0; j < length - 1; j++){ 
      if(compareFn(array[j], array[j+1]) == Compare.BIGGER_THAN){   //判断当前项是否比后一项大 
        swap(array,j,j+1) // 交换位置
      }
    }
  }
  return array
}

选择排序

定义

是一种原址比较排序算法.找到数据结构中最小值并将其放置到第一位,接着找到第二小的值并将其放置到第二位,以此类推.

实现
    
function selectionSort (array){
  const { length } = array;
  let indexMin;
  for(let i = 0;i < length; i++ ){
    indexMin = i; //记录本次迭代最小值

    for(let j = i; j < length; j++){
      if(compareFn(array[indexMin],array[j]) == Compare.BIGGER_THAN){
        indexMin = j; // 
      }
      if(i !== indexMin){
        swap(array,i,indexMin)
      }
    }
  }

  return array
}

插入排序

定义

每次排一个数组项,以此方式构建最后的排序数组.假定第一项已经排好了.接着,它和第二项进行比较--第二项是应该呆在原位还是插在第一项之前呢? 接着喝第三项比较(它是该插入到第一,第二还是第三呢?),以此类推.

实现
function instertionSort (array){
  const { length } = array;
  let temp; 
  for (let i = 1; i < length; i++) {
    let j = i; // 辅助变量
    temp = array[i];

    while (j > 0 && compareFn(array[j - 1], temp) === Compare.BIGGER_THAN) {
      array[j] = array[j - 1]; // 前置赋值后值 
      j--;
    }
    array[j] = temp;  // 保存值 赋值当前循环最前
  }
  return array;
}

归并排序

定义

将原始数组切分成较小的数组,知道每个小数组只有一个位置,接着将小数组归并成较大的数组,知道最后只有一个排序完成的大数组.由于是分治法,归并排序也是递归的.

实现
 function mergeSort(array){
  if (array.length > 1) {
    const { length } = array;
    const middle = Math.floor(length / 2);
    const left = mergeSort(array.slice(0, middle));
    const right = mergeSort(array.slice(middle, length));
    array = merge(left, right);
  }
  return array;
}

function merge(left,right){
  let i = 0;
  let j = 0;
  const result = [];
  while (i < left.length && j < right.length) { // 迭代左右数组
    result.push(compareFn(left[i], right[j]) === Compare.LESS_THAN ? left[i++] : right[j++]);
  }
  return result.concat(i < left.length ? left.slice(i) : right.slice(j)); // 连接剩余数组
}

快速排序

定义

和归并排序一样,快速排序也使用分而治之的方法,将原始数组分为较小的数组(但没有像归并排序那样将它们分开)

  1. 首先,从数组中选择一个值作为 主元(pivot) ,也就是数组中间的值
  2. 创建两个指针(引用),左边一个指向数组的第一个值,右边一个指向数组最后一个值.移动左指针直到我们找到一个比主元大的值,接着,移动右指针直到找到一个比主元小的值,然后交换它们,重复这个过程,直到左指针超过了右指针.这个过程将使得比主元小的镇都排在主元之前,比主元大的值都牌子主元之后,这一步叫做 划分(partition) 操作
  3. 接着,算法对划分侯的小数组(较比主元小的组成的子数组,及比主元大的值组成的子数组),重复之前的两个步骤,直至数组已完全排序
实现
function quickSort(array){
  return quick(array, 0, array.length - 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 partition(array,left,right){
  const pivot = array[Math.floor((right + left ) / 2)]; // 中间值
  let i = left; //低
  let j = right; // 高
  
  while(i <= j){ // 没有相互交错
    while(compareFn(array[i],pivot) === Compare.LESS_THAN ){ //如果比中间值小 i++  继续判断下一个 
      i++
    }
    while(compareFn(array[j],pivot) === Compare.BIGGER_THAN ){ //如果比中间值大 j--  继续判断上一个 
      j--;
    }
    if(i <= j){ //左指针索引没有右指针索引大时 意思是左项比右项大  
      swap(array,i,j) // 交换他们
      i++;  //继续重复
      j--
    }
  }
  return i
}

计数排序

定义

是一个分布式排序算法,使用已组织好的辅助数据结构(称为桶),然后进行合并,得到排好序的数组.计数排序使用一个用来存储每个元素在原始数组中出现次数的临时数组.在所有元素都计数完成后,临时数组已排好序并迭代以构建排序后得结果数组. 他是一个整数排序算法, 时间复杂度为O(n+k),其中k是临时计数数组的大小,需要更多的内存来存放临时数组

实现
function countingSort(array){
  if(array.length < 2){
    return array
  }

  const maxValue = findMaxValue(array);
  const counts = new Array(maxValue + 1);// 创建计数数组


  array.forEach(ele =>{
    if(!counts[ele]){
      counts[ele] = 0 // 初始
    }

    counts[ele] ++; //计数
  })
  
  let sortedIndex = 0;
  console.log(counts)
  counts.forEach((count, i)=>{
    while(count > 0){ 
      array[sortedIndex++] = i // 加入数组
      count--; //减少计数器
    }
  })

  return array

}

function findMaxValue(array){
  let max = array[0]
  for(let i = 1; i < array.length; i++){
    if(array[i] > i){
      max = array[i]
    }
  }
  return max
}

桶排序

也被称为箱排序,也是分布式排序算法,它将元素分为不同的桶(较小的数组),在使用一个简单的排序算法,例如插入排序(用来排序小数组不错的算法),来对每个桶进行排序.然后,它将所有的桶合并为结果数组

实现
 function createBuckets(array,bucketSize){
  let minValue = array[0];
  let maxValue = array[0];

  for(let i = 1; i< array.length; i++){ // 迭代 找出最大和最小值
    if(array[i] < minValue){
      minValue = array[i]
    }else if(array[i] > maxValue){
      maxValue = array[i]
    }
  }

  const bucketCount = Math.floor((maxValue-minValue) / bucketSize) +1; // 公式 --计算每个桶中需要分布的元素个数
  const buckets = [];

  for(let i = 0 ;i < bucketCount; i++){ // 初始化
    buckets[i] = []
  }

  for(let i = 0; i< array.length; i++){
    const bucketIndex = Math.floor((array[i]- minValue) / bucketSize ) // 计算放到哪个桶中
    buckets[bucketIndex].push(array[i]) 
  }

  return buckets

}

function sortBuckets(buckets){
  const sortedArray = [];
  for(let i = 0; i< buckets.length ; i++){
    if(buckets[i] != null){
      instertionSort(buckets[i]); // 排序
      sortedArray.push(...buckets[i])  // 合并
    }
  }

  return sortedArray
  
}

function bucketSort(array,bucketSize = 5){
  if(array.length < 2){
    return array
  }

  const buckets = createBuckets(array, bucketSize)
  return sortBuckets(buckets)
}

基数排序

定义

基数排序也是一个分布式排序算法,它根据数字的有效位或基数将整数分布到桶中.基数是基于数组中值得记数制的. 比如对于十进制使用的基数是10.因此,算法将会使用10个桶来分布元素并且首先基于个位数字进行排序

const getBucketIndex = (value, minValue, significantDigit, radixBase) =>{
    return Math.floor(((value - minValue) / significantDigit) % radixBase);
}

const countingSortForRadix = (array, radixBase, significantDigit, minValue) => {
    let bucketsIndex;
    const buckets = [];
    const aux = [];
    for (let i = 0; i < radixBase; i++) { // 初始化桶
      buckets[i] = 0;
    }
    
    //基于数组中的有效位 进行计数排序 
    for (let i = 0; i < array.length; i++) {  
      bucketsIndex = getBucketIndex(array[i], minValue, significantDigit, radixBase);
      buckets[bucketsIndex]++;
    }
    
    // 计算积累结果得到的正确计数值
    for (let i = 1; i < radixBase; i++) {
      buckets[i] += buckets[i - 1];
    }
    //对原始数组 获取它的有效位 并将值移动到aux 数组中
    
    for (let i = array.length - 1; i >= 0; i--) {
      bucketsIndex = getBucketIndex(array[i], minValue, significantDigit, radixBase);
      aux[--buckets[bucketsIndex]] = array[i];
    }
    
    for (let i = 0; i < array.length; i++) {
      array[i] = aux[i];
    }
    return array;
};

function radixSort(array, radixBase = 10) {
    if (array.length < 2) {
      return array;
    }
    
    let minValue = array[0];
    let maxValue = array[0];
    
    for(let i = 1; i< array.length; i++){ // 迭代 找出最大和最小值
      if(array[i] < minValue){
        minValue = array[i]
      }else if(array[i] > maxValue){
        maxValue = array[i]
      }
    }
    
    let significantDigit = 1; // 最后一位开始排序所有的数 
    while ((maxValue - minValue) / significantDigit >= 1) {
    
      array = countingSortForRadix(array, radixBase, significantDigit, minValue);
      significantDigit *= radixBase; // 下次迭代时 基于第二有效位进行排序(十位数字),第三有效位 以此类推
    }
    return array;
}

结语

本次分享就到这里了 前端界的一枚小学生!!!