【前端学算法】排序算法知多少(二)

希尔排序

原理:希尔排序就是按照一定的gap值,不断地对数组进行插入排序 。不一样的希尔排序算法可能采用不一样的gap值。经典希尔算法的gap值为N/2, N/4, ...... 直到gap值为1,这里的N为数组的长度。

希尔排序是一种插入排序的算法,它是对简单的插入排序进行改进后,更高效的版本。

优势:

  • 空间复杂度低,仅为O(1)
  • 作为改进版的插入排序,希尔排序是一种相对高效的基于交换元素的排序方法。

劣势:

  • 不是稳定的排序算法。
  • 时间复杂度难以分析:希尔排序的时间复杂度依赖于增量序列函数,因此分析起来比较困难

实现:

js 复制代码
function shellSort (arr) { 
  let len = arr.length; 
  let gap = parseInt(len / 2); 
  while (gap) { 
    //从第gap个元素开始遍历 
    for (let i = gap; i < len; i++) { 
      // 逐步和前面其他的组成员进行比较和交换 
      for (let j = i - gap; j >= 0; j -= gap) { 
        if (arr[j] > arr[j + gap]) { 
           [arr[j], arr[j + gap]] = [arr[j + gap], arr[j]]; 
        }; 
      };
    }; 
    gap = parseInt(gap / 2); 
  };
 };
};
堆排序

原理:堆排序可以认为是选择排序的改进版,像选择排序一样将数组划分为已排序和待排序,它的核心是先建堆,再排序。

什么是堆,堆是满足下几点的完全二叉树:

  • 每个节点的值都大于等于(或者小于等于)其左右子节点的值

    • 每个结点的值都大于或等于其左右子结点的值,称为大顶堆;
    • 或每个结点的值都小于或等于其左右子结点的值,称为小顶堆。
  • 除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列

  • 堆可以用一个数组表示,给定最后一个非叶子节点的下标 ii = Math.floor(array.length / 2) - 1) ,那么左子节点为 A[2i+1] ,右子节点为 A[2i+2]

优势:

  • 时间复杂度较低:堆排序的时间复杂度为𝑂(𝑛log⁡𝑛),这使得它在处理大量数据时具有较高的效率。
  • 空间复杂度较低:堆排序只需要一个常数级别的额外空间,因此在内存使用方面非常高效。
  • 适用于大数据量的排序:堆排序的时间复杂度不随数据量的增加而变化,因此适用于大数据量的排序。

劣势:

  • 不是稳定的排序算法。

实现:

js 复制代码
function heapSort(arr){
  const len = arr.length;
  if(len <= 1) return arr; // 如果数组长度为1,直接返回
  buildMaxHeap(arr);//构建一个大顶堆
  // 调整为大顶堆后,顶元素为最大元素并与末尾元素交互
  while(len > 0) {// 当len<=0时,说明已经到堆顶
    [arr[0], array[len - 1]] = [arr[len - 1], arr[0]]; // 交换
    len--; // 交换之后相当于把树中的最大值弹出去了,所以length-- 
    adjustHeap(arr, 0, len); // 交换值并剔除了最大值后,继续进行调整使之再次成为大顶堆
  };
  return arr;
};

// 将原数组调整为一个对应数组的大顶堆
function buildMaxHeap(arr) {
  // 大顶堆的构建是从最后一个非叶子节点开始,从下往上,从右往左调整
  // 最后一个非叶子节点的位置:Math.floor(arr.length/2) - 1
  for(let i = Math.floor(arr.length/2) - 1; i >= 0; i--;) {
    adjustHeap(arr, i, arr.length);
  };
};

/** 
  * 调整 
  * @param i 最后一个非叶子节点 
  * @param length 数组的长度
*/ 
function adjustHeap(arr, i, len) {
  const maxIndex = i;// 最大值索引
  const left = i * 2 + 1; // 左子节点索引
  const right = i * 2 + 2;// 右子节点索引
  // 判断是否有子节点,再比较父节点和左右子节点的大小
  // 因为i是最后一个非叶子节点,所以如果有左右子节点则节点的位置都小于数组的长度
  if(left <  len && arr[left] > arr[maxIndex] {//若左子节点比父节点大
    maxIndex = left;
  };
  if(right < len && arr[right] > arr[maxIndex]){//若右子节点比父节点大
    maxIndex = right;
  };
  if(maxIndex != i) {
    [arr[maxIndex],arr[i]] = [arr[i], arr[maxIndex]];
    adjustHeap(arr, maxIndex, len); //交换之后递归再次调整比较
  };
}; 
桶排序

原理:

  • 分:创建桶并将元素分布到不同的桶中,桶排序的一个重要步骤是计算每个桶中需要分布的元素个数
  • 排:将分好的桶中的各个元素进行插入排序
  • 并:最后合并各个桶的数据,完成排序

优势:

  • 是一种稳定的排序算法
  • 对于元素分布均匀、桶内排序算法高效的情况,桶排序具有出色的性能。

缺点:

  • 空间复杂度高:桶排序需要额外的存储空间来存储桶和桶中的元素。如果待排序元素数量非常大,可能需要分配大量的桶和相应的存储空间,占用更多的内存。
  • 若数据分布不均匀,可能导致部分桶过载,影响整体效率。

实现:

值得注意的是,桶的个数是人为指定的,不随着数组大小和数值大小改变(可以根据文件大小和内存大小,得到桶的个数)。

js 复制代码
  function bucketSort(arr, bucketSize = 5){
     if(arr.length <= 1) return arr;
     const buckets = createBuckets(arr, bucketSize);// 创建桶并将元素分布到不同的桶中
     return sortBuckets(buckets);
  };
  function createBuckets(arr, bucketSize){// bucketSize 表示桶的数量
    const maxValue = Math.max(...arr);
    const minValue = Math.min(...arr);
    const bucketCount = Math.ceil((maxValue - minValue) / bucketSize);// 桶排序的第一个重要步骤是计算每个桶中需要分布的元素个数
    const buckets = [];
    for(let i = 0; i < bucketCount; i++) {
      buckets[i] = [];// 将每个桶置空初始化
    };
    for(let i = 0; i< arr.length; i++) {
      const bucketIndex = Math.floor((arr[i] - minValue) / bucketSize);//计算要将元素放到哪个桶中
      buckets[bucketIndex].push(arr[i]);
    };
    return buckets
  }
  // 利用插入排序方法对每个桶进行排序
  function sortBuckets(buckets){
    const sortArr = [];
    for(let i = 0; i < buckets.length; i++){
      sortArr.push(...insertSort(buckets[i]));
    };
  };
计数排序

原理:将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

时间复杂度上是O(n + k); 其中k代表着数组最大值 - 最小值的差; 这意味着计数排序适合什么时候使用呢?当然是数值越接近的数组越适合用计数排序。

优势:

  • 计数排序不是比较排序,排序的速度快于任何比较排序算法。
  • 是一种稳定的排序算法

劣势:

数据范围很大,比较稀疏,会导致辅助空间很大,造成空间的浪费

实现:

js 复制代码
function countingSort(arr){
  const maxValue = Math.max(...arr);
  const minValue = Math.min(...arr);
  const counts = new Array(maxValue - minValue + 1);//创建计数数组
  // 生成计数数组
  arr.forEach((item,index) => {
    if(!counts[item]) {
      counts[item] = 0;
    };
     counts[item]++;
  });
  // 开始排序数组,由于是最大值有多大,索引就有多大,所以对应的i即为原数组arr对应的值
  let sortIndex = 0;
  counts.forEach((count, i)=>{
    while(count > 0) {
      arr[sortIndex++) = i;
      count--; 
    };
  });
  return arr;
  
};
基数排序

原理:将整数按位数切割成不同的数字, 然后按每个位数分别比较。即将所有待比较数值统一为同样的数位长度,数位较短的数前面补零 。然后,从最低位开始 ,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

优势:

  • 效率高
  • 是稳定的排序算法

劣势:

在某个数字可能很大的时候,基数排序没有任何性能上的优势,还会浪费非常多的内存。

实现:非负整数+升序

js 复制代码
function radixSort(arr) {
  // 获取数组中最大整数的位数
  const max = Math.max(...arr);
  const maxDigits = max === 0? 1 : Math.floor(Math.log10(max)) + 1;
  // 基数排序循环
  for (let i = 0; i < maxDigits; i++) {
    const buckets = Array.from({ length: 10 }).map(() => []);
    // 分配
    for (const num of arr) {
      // 获取数字的指定位数上的数字
      const dest = Math.floor(((num / Math.pow(10, i)) % 10));
      buckets[dest].push(num);
    };
    // 收集
    let pos = 0;
    for (const bucket of buckets) {
      for (const num of bucket) {
         arr[pos++] = num;
      };
    };
  };
  return arr;
};
相关推荐
全栈技术负责人2 小时前
vue3中使用nexttick
前端·javascript·vue.js
程楠楠&M2 小时前
uni-app页面调用接口和路由(四)
前端·vue.js·小程序·uni-app·flex布局·弹性布局
hmz3602 小时前
最新绿豆影视系统 /反编译版源码/PC+WAP+APP端 /附搭建教程+软件
前端·后端·php
胡西风_foxww2 小时前
canvas分享,从入门到入门。
javascript·教程·canvas·入门·分享
L_cl3 小时前
JavaWeb JavaScript 11.XML —— 配置文件
xml·java·前端
小夏同学呀3 小时前
vue3ElementPlus使两个日期联动控制(限制结束时间为开始时间的一个月)
前端·javascript·vue.js
变形金刚卖人寿保险还是汽车保险4 小时前
elementplus修改表格数据
javascript·vue.js·elementui
变形金刚卖人寿保险还是汽车保险4 小时前
element-plus表格操作
前端·javascript·vue.js
A阳俊yi4 小时前
Vue(16)——Vue3.3新特性
前端·javascript·vue.js