排序算法

2022-04-01  本文已影响0人  郝同学1208

文章序

排序算法作为算法的入门,也是在日常开发中十分常用的,故在此整理出十大排序算法,方便自己和需要的朋友查阅

先定义交换函数

  //不使用额外空间交换算法,不能自己跟自己换
  function swap(array, i, j) {
    if (i === j) {
      return array;
    }
    let arr = array;
    arr[i] = arr[i] + arr[j];
    arr[j] = arr[i] - arr[j];
    arr[i] = arr[i] - arr[j];
    return arr;
  }

  //使用额外空间交换算法,可以自己跟自己换
  function swap(array, i, j) {
    let arr = array;
    let temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
    return arr;
  }

1冒泡排序

原理:数组长度为 n,遍历 n - 1 趟,每趟从第 1 个元素向后两两比较,如果左边的元素大于右边的元素则将两个元素据交换,直到左边的元素为第 n - 1 个元素,交换完毕后,此时数组最右端的元素即为该数组中最大的元素。接着对该数组剩下的 n-1 个元素继续冒泡排序,直到整个数组有序排列
特性:时间复杂度:O(n^2),空间复杂度:O(1) ,稳定排序 ,原地排序
方法:两层循环嵌套,外层共遍历 n - 1 趟,内层遍历从第 1 个元素到 n - 1 - i 个元素,进行两两比较交换

  function bubbleSort(arr) {
    let res = arr;
    let n = arr.length;
    for(let i = 0; i < n - 1; i++) {
      for(let j = 0; j < n - 1 - i; j++) {
        if(res[j] > res[j + 1]) {
          res = swap(res, j, j + 1);
        }
      }
    }
    return res;
  }

2选择排序

原理:遍历 n - 1 趟,每趟遍历找到剩余最小的元素放在前面,依次类推,直到数组有序排列
特性:时间复杂度:O(n^2) ,空间复杂度:O(1) ,非稳定排序,原地排序
方法:两层循环嵌套,外层共遍历 n - 1 趟,内层遍历从第 i 个元素开始,找到最小的元素和第 i 个元素交换

  function selectionSort(arr) {
    let res = arr;
    let n = arr.length;
    let minIndex = 0;
    for(let i = 0; i < n - 1; i++) {
      minIndex = i;
      for(let j = i + 1; j < n; j++) {
        if(res[j] < res[minIndex]) {
          minIndex = j;
        }
      }
      if(minIndex !== i) {
        res = swap(res, i, minIndex);
      }
    }
    return res;
  }

3插入排序

原理:从第 1 个元素开始,将后面的元素与该元素比较,大则放在右边,小则放在左边,此时该两元素构成数组有序,剩余元素构成的数组无序,依次将剩余的元素向前面已有序数组中插入,直到整个数组有序
特性:时间复杂度:O(n^2),空间复杂度:O(1) ,稳定排序 ,原地排序
方法:两层循环嵌套,外层共遍历 n - 1 趟代表插入 n - 1 个元素,内层遍历前面已有序数组直到找到合适的插入的元素的位置

  function insertSort(arr) {
    let res = arr;
    let n = arr.length;
    for (let i = 1; i < n; i++) {
      for (let j = i - 1; j >= 0; j--) {
        if (res[j] < res[j + 1]) {
          break;
        }
        swap(res, j, j + 1);
      }
    }
    return res;
  }

4希尔排序

原理:定义增量(gap) h,将待排数组分割成为 h 个子数组分别进行插入排序,操作之后减小 h 的值,再次分别进行插入排序,直到 h = 1,则再进行最后一次插入排序,完成后整个数组排序完成
特性:时间复杂度:O(nlogn) ,空间复杂度:O(1) ,非稳定排序,原地排序
方法:三层遍历,最外层每次减少增量h的值,内两层做插入排序,注意间隔不再是 1 而是增量 h

  function shellSort(arr) {
    let res = arr;
    let n = arr.length;
    let h = parseInt(n / 2);
    while (h >= 1) {
      //进行插入排序
      let i = 0;
      let temp = 0;
      while(temp < h) {
        for (let j = i - h; j >= 0; j = j - h) {
          if (res[j] < res[j + h]) {
            break;
          }
          swap(res, j, j + h);
        }
        i = i + h;
        if(i >= n) {
          temp++;
          i = temp;
        }
      }
      h = parseInt(h / 2);
    }
    return res;
  }

5快速排序

原理:选择一个元素,将比他大的都放到右侧,比它小的都放在左侧,此时数组分成两个数组,再次对两个数组进行快排,这样递归下去直到整个数组有序
特性:时间复杂度:O(nlogn),空间复杂度:O(nlogn) ,非稳定排序,原地排序
方法:设指针 cur 指向第 1 个元素开始,指针 i 从 cur 指向元素向右走,指针 j 从最后一个元素向前走,j 先走,当 j 找到比 cur 小的元素停止,当 i 找到比 cur 大的元素停止,交换 i j指向的元素,直到i j相遇,交换此时cur 和 i 指向元素的位置,对此时指针左右两侧数组递归进行快排

  function quickSort(arr) {
    let n = arr.length;
    handleSort(arr, 0, n - 1);
    return arr;
  }
  const handleSort = (arr, start, end) => {
    if (start >= end) {
      return;
    }
    let cur = start,
      i = start + 1,
      j = end;
    let mid = start;
    while (true) {
      while (j >= start && j > i) {
        if (arr[j] < arr[cur]) {
          break;
        }
        j--;
      }
      while (i <= end && i < j) {
        if (arr[i] > arr[cur]) {
          break;
        }
        i++;
      }
      if (i === j) {
        if (arr[i] < arr[cur]) {
          swap(arr, cur, i);
          mid = i;
        }
        break;
      }
      swap(arr, i, j);
    }
    handleSort(arr, start, mid - 1);
    handleSort(arr, mid + 1, end);
  };

6归并排序

原理:将数组除2拆分,直到拆分到仅有一个元素的数组,此时该数组有序,将该数组与上一次拆分的另一个数组合并,此时本次拆分的数组有序,继续向上和上一次拆分的另一个数组合并,直到整个数组合并完毕,整个数组有序
特性:时间复杂度:O(nlogn),空间复杂度:O(n),稳定排序,非原地排序
方法:递归操作,先定义拆分函数mergeSort,再定义合并函数merge,拆分函数负责将入参数组除2拆分,并对拆分过的数组继续调用拆分函数,当两次拆分函数都调用完毕返回结果则调用合并函数

  function mergeSort(arr) {
    let n = arr.length;
    if (n < 2) {
      return arr;
    }
    let mid = parseInt(n / 2);
    let left = arr.slice(0, mid);
    let right = arr.slice(mid, n);
    let sortedLeft = mergeSort(left);
    let sortedRight = mergeSort(right);
    return merge(sortedLeft, sortedRight);
  }
  const merge = (left, right) => {
    let res = [];

    while (left.length > 0 && right.length > 0) {
      if (left[0] < right[0]) {
        res.push(left.shift());
      } else {
        res.push(right.shift());
      }
    }
    if (left.length > 0) {
      res = res.concat(left);
    }
    if (right.length > 0) {
      res = res.concat(right);
    }
    return res;
  };

7堆排序

原理:大顶堆为二叉树数结构,任意节点的值均大于左右子节点,数组可以构建一颗二叉树结构,将此数组调整为二叉树符合大顶堆的顺序,再依次交换第 0 个元素和第 n 个元素,并重新调整堆使之符合大顶堆定义,n依次递减直到n = 1,此时排序完成
特性:时间复杂度:O(nlogn),空间复杂度:O(1),非稳定排序,原地排序
方法:parseInt(n / 2)该元素为最后一个非叶子节点的数据,从这里开始遍历,构建大顶堆。大顶堆构建完成后将第 0 个元素和最后一个元素交换,此时剩下的堆不符合大顶堆定义,调整堆使符合大顶堆定义,再次交换第 0 个元素和此时最后一个元素,循环 n - 1 次结束获得有序数组

  function heapSort(arr) {
    let n = arr.length;

    //从最后一个非叶子节点开始遍历,构建大顶堆
    for (let i = parseInt(n / 2); i >= 0; i--) {
      heapify(arr, i, n);
    }
    //大顶堆构建完毕,开始从最后一个元素开始交换
    for (let j = arr.length - 1; j > 0; j--) {
      n--;
      swap(arr, 0, j);
      heapify(arr, 0, n);
    }
    return arr;
  }
  function heapify(arr, i, n) {
    //调整堆
    let leftChild = 2 * i + 1;
    let rightChild = 2 * i + 2;
    let largest = i;

    if (leftChild < n && arr[leftChild] > arr[largest]) {
      largest = leftChild;
    }
    if (rightChild < n && arr[rightChild] > arr[largest]) {
      largest = rightChild;
    }

    if (largest !== i) {
      swap(arr, i, largest);
      heapify(arr, largest, n);
    }
  }

8计数排序

原理:开辟新的数组 newArray,长度为 k,将待排数组内的元素值作为 newArray 的下标,将数组 newArray 从头遍历依次输出赋值给原数组即完成排序
特性:时间复杂度:O(n + m),空间复杂度:O(n + m),稳定排序,非原地排序
方法:

  function countingSort(arr) {
    let newArray = [];
    let k = 0;
    for (let i = 0; i < arr.length; i++) {
      newArray[arr[i]] = arr[i];
    }
    for (let j = 0; j < newArray.length; j++) {
      if (newArray[j]) {
        arr[k] = newArray[j];
        k++;
      }
    }
    return arr;
  }

9桶排序

原理:创建多个桶空间用来存放一定范围内的数据,每个桶内再排序,将数据按顺序从非空桶中取出,排序完成
特性:时间复杂度:O(n + m),空间复杂度:O(n + m),稳定排序,非原地排序
方法:创建一个数组,该数组内元素为数组表示不同的桶

  function bucketSort(arr) {
    let n = arr.length;
    let max = arr[0];
    let min = arr[0];
    //找到最大最小值
    arr.forEach(item => {
      if (max < item) {
        max = item;
      } else if (min > item) {
        min = item;
      }
    });
    //设数组长度为桶长度,获取桶数量
    let bucketCount = parseInt((max - min) / n);
    let bucketList = new Array(bucketCount + 1);
    for (let i = 0; i < bucketList.length; i++) {
      bucketList[i] = [];
    }
    //将每一个数据放入到合适的桶
    for (let j = 0; j < n; j++) {
      let index = parseInt((arr[j] - min) / n);
      bucketList[index].push(arr[j]);
    }
    //对每一个桶进行排序,这里选择计数排序
    bucketList.forEach(bucket => {
      countingSort(bucket);
    });
    for (let k = 0; k < bucketList.length; k++) {
      if (k === 0) {
        arr = [];
      }
      arr = arr.concat(bucketList[k]);
    }
    return arr;
  }

10基数排序

原理:将数组按照从低位到高位的顺序排序,当每一趟都排序完成之后整个数组有序
特性:时间复杂度:O(nm),空间复杂度:O(n + m),稳定排序,非原地排序
方法:先找到最大的数并获取其位数 n,然后从个位开始向上遍历,共 n 次,每次遍历获取该数当前位的数并和桶里的数的当前位的数比较,没有当前位则补 0

  function radixSort(arr) {
    let max = arr[0];
    //找到最大的数并获取其位数
    arr.forEach(number => {
      if (number > max) {
        max = number;
      }
    });
    const n = max.toString().length;
    // let divide = Math.pow(10, n);
    let divide = 1;

    for (let i = 0; i < n; i++) {
      let bucket = new Array(arr.length);
      for (let j = 0; j < arr.length; j++) {
        let tempNum = parseInt(arr[j] / divide);
        while (tempNum >= 10) {
          tempNum %= 10;
        }
        for (let k = 0; k < bucket.length; k++) {
          if (bucket[k]) {
            let tempBucketNum = parseInt(bucket[k] / divide);
            while (tempBucketNum >= 10) {
              tempBucketNum %= 10;
            }
            if (tempNum < tempBucketNum) {
              bucket.splice(k, 0, arr[j]);
              bucket.pop();
              break;
            }
          } else {
            bucket[k] = arr[j];
            break;
          }
        }
      }
      divide *= 10;
      arr = bucket;
    }
    return arr;
  }
上一篇下一篇

猜你喜欢

热点阅读