关于排序

142 阅读6分钟

最近在做一些面试的工作,发现大家有个不好的习惯,没有对数据进行排序处理的习惯,这种意识有点差;而如果提前对数据进行排序,会使我们的逻辑更清晰,有时候一些面试题看似复杂难解决,有很大一部分原因是因为我们没有对数据进行排序;

排序!!!很重要!!!

排序我个人整理了一些;

1、冒泡

function bubble(arr){
  for(let i = 0;i<arr.length-1;i++){ // 比较数组长度-1 轮就可以
    for(let j = 0;j<arr.length-1-i;j++){ // -1 是防止arr[j+1]溢出,-i 是除去已经排好序的最后i个
      if(arr[j] > arr[j+1]){
        [arr[j],arr[j+1]] = [arr[j+1],arr[j]]
      }
    }
  }
}

let arr = [9,4,7,3,1,0]
bubble(arr);
console.log(arr)

2、选择排序

工作原理:

初始时在未排序序列中找最小(最大)元素,放到序列的起始位置作为已排序序列;

然后再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。

以此类推。直到所有元素均排序完毕。

function sort(arr) {
  var len = arr.length;
  //已排序序列的末尾
  for (var i = 0; i < len; i++) {
      var min = i;
      //待排序序列
      for (var j = i + 1; j < len; j++) {
          //从待排序序列中找到最小值
          if (arr[j] < arr[min]) {
              min = j;
          }
      }
      if (min != i) {
          var temp = arr[i];
          arr[i] = arr[min];
          arr[min] = temp;
      }
  }
  return arr;
}

3 、插入排序

插入排序稳定,最差时间复杂度待排序列是降序序列;最优时间复杂度即待排序列是升序

  1. 从第一元素开始,该元素可以默认已经被排序
  2. 取出下一个元素,在已经排序序列中从后向前扫描
  3. 如果已排序的元素大于新元素,将下一个位置设置成该元素 4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置 5.将新元素插入到该位置 6.重复2~5
function insertSort(arr) {
  let len = arr.length;
  let preIndex, current;
  for (let i = 1; i < len; i++) {
    preIndex = i - 1; //排好序的数组最后一个下标
    current = arr[i]; //待排序的数值
    //让待排序的值与排序好的数组值进行比较
    while (preIndex >= 0 && arr[preIndex] > current) {
      arr[preIndex + 1] = arr[preIndex];
      preIndex--;
    }
    //待排序的数值大于等于排序好的值
    arr[preIndex + 1] = current;
  }
  return arr;
}

4、快速排序

nlogn 主要是交换 比循环和插入要性能好 其实任何一个值都可以做支点 最差n*n www.bilibili.com/video/av478… a. 默认pirot 设为-1 pirot 左侧都是比 arr[right] 小的值,右侧都是比arr[right] 大的值

function quick_sort(arr,left,right) {  //right 为长度-1
  if (left >= right) {
    return;
  }
  let p = partition(arr, left, right);
  quick_sort(arr, left, p - 1);
  quick_sort(arr, p + 1, right);
}

function partition(arr,left,right) {
  let value = arr[right]; // 10
  let pivot = left - 1; // -1
  
  for(let i=left; i<right; i++) { // i=left
    if(arr[i]<= value) {
      pivot += 1;
      // 交换值
      [arr[i],arr[pivot]] = [arr[pivot],arr[i]];
    } 
  }
  // 此时 pivot左侧都小于arr[right] 右侧都大于arr[right] 需将arr[right] 和pivot+1 位置的数交换
  pivot += 1; 
  [arr[right],arr[pivot]] = [arr[pivot],arr[right]];
  return pivot;
}

let arr = [1,12,9,4,10];
quick_sort(arr,0,4);
console.log(arr);

5.归并排序

归并排序采用的是分治的思想,首先是“分”,将一个数组反复二分为两个小数组,直到每个数组只有一个元素;其次是“治”,从最小数组开始,两两按大小顺序合并,直到并为原始数组大小, “治”实际上是将已经有序的数组合并为更大的有序数组, 时间复杂度为 O(nlogn)。

function mergeSort(arr) {
  const length = arr.length;
  if (length === 1) { //递归算法的停止条件,即为判断数组长度是否为1
    return arr;
  }
  const mid = Math.floor(length / 2);
  const left = arr.slice(0, mid);
  const right = arr.slice(mid, length);

  return merge(mergeSort(left), mergeSort(right)); //要将原始数组分割直至只有一个元素时,才开始归并
}

function merge(left, right) { // 主要负责合并两个有序数组
  const result = [];
  let il = 0;
  let ir = 0;

  //left, right本身肯定都是从小到大排好序的
  while (il < left.length && ir < right.length) {
    if (left[il] < right[ir]) {
      result.push(left[il]);
      il++;
    } else {
      result.push(right[ir]);
      ir++;
    }
  }
  //不可能同时存在left和right都有剩余项的情况, 要么left要么right有剩余项, 把剩余项加进来即可
  while (il < left.length) {
    result.push(left[il]);
    il++;
  }
  while (ir < right.length) {
    result.push(right[ir]);
    ir++;
  }
  return result;
}

6、具体的例子

班上有五个同学,分别考了 5分、3分、5分、2分、8分,满分为10分,需要用桶排序的方法实现分数从小到大排列。

只能排序大于0的 比较浪费内存 如果最大的数比较大的话 适合对数据比较了解 桶排序简单版 简单版不支持小数

let bucketSort = () => {
  let arr = new Array(11);
  let marks = [5, 3, 5, 2, 8];
  let newArr = [];
  for (init = 0; init < arr.length; init++) {
    arr[init] = 0;
  };
  for (i = 0; i < marks.length; i++) {
    arr[marks[i]]++;
  };
  for (j = 0; j < arr.length; j++) {
    if (arr[j] == 0) {
      continue;
    } else {
      for (l = 0; l < arr[j]; l++) {
        newArr.push(j);
      };
    };
  };
  return newArr;
}

// 也可以用一个二维数组来放置相同的数

function Bucket_sort(arr,range) {
  let buckets = Array.from({length: range},()=>[]);
  // 下表算法
  const indexFun = val => {
    return Math.floor(val)
  };

  arr.forEach(item =>{
    let idx = indexFun(item);
    if(idx === undefined) {
      throw new Error(`没有${item}下标`)
    } 
    buckets[indexFun(item)].push(item);
  });
  let valueArr =  buckets.filter((sub)=>{ // 过滤有值的
    return sub.length > 0
  })
  valueArr.map(bucket=>{
    // 桶里再加一次排序
    return bucket.sort((x,y)=>x-y)
  })
  return valueArr.reduce((prev,cur)=>{
    return prev.concat(cur)
  },[])
}

let arr = Bucket_sort([2,1,9,0,6.1],10)
console.log(arr)

复杂版

计算并设置固定数量的空桶

将数据放入对应的桶中

对桶中的数据进行排序

把每个桶的数据进行合并

//插入排序
function insertion_sort(A){
    for(let i=1; i<A.length; i++){
        let p = i-1
        const x = A[i]
        while(p>=0 && A[p]>x){
            A[p+1] = A[p]
            p--
        }
        A[p+1] = x
    }
}

执行函数的作用主要为 匿名 和 自动执行,代码在被解释时就已经在运行了。

function bucket_sort(A, k, s){ //A排序数组,k桶子数量,s桶子空间尺度
    // es6 Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。
    const buckets = Array.from({length:k}, ()=>[]) //创建桶子
    //把元素放入对应桶子
    for(let i=0; i<A.length; i++){
        //计算需要放入桶子序号
        const idx = ~~(A[i]/s) 
        buckets[idx].push(A[i])
    }
    
    //对每个桶子进行排序
    for(let i=0; i<buckets.length; i++){
        //此处选取插入排序, 空间消耗少,元素少常数时间消耗短
        insertion_sort(buckets[i])
    }
    
    //把每个桶子数据合并
    return [].concat(...buckets)
}

7、二分查找

条件: 数组是排序的 非排序的直接for查找

function find(arr, num, start, end) {
  if (!arr || !num) {
    throw new Error('no illegal arguments');
  }
  if (arr.length == 0) {
    return arr[0] === num;
  }
  if (!start) {
    start = 0;
  }
  if (!end) {
    end = arr.length;
  }

  let mid = Math.floor((start + end) / 2);
  if (num === arr[mid]) {
    return mid
  }
  if (num < arr[mid]) {
    return find(arr, num, 0, mid); // 想得到下标这里不能用slice 新数组下标会变
  } else {
    return find(arr, num, mid, end);
  }
  return false;
}
let arr = [0, 4, 8, 9, 12, 16, 30];
console.log(find(arr, 16))