资讯专栏INFORMATION COLUMN

排序算法的Javascript实现

tommego / 2023人阅读

摘要:排序加了的插入排序。分别以索引数为的元素为起点,将其看做不同的组,,,为一组,,为一组依次分组,按照组为单位进行插入排序。各组都已经插入排序一轮过后,将除以向下取整,再进行分组并将各组分别进行插入排序,直到为。

1.冒泡排序:

比较相邻的两个数,如果前一个数大于后一个数,就将这两个数换位置。每一次遍历都会将本次遍历最大的数冒泡到最后。为了将n个数排好序,需要n-1次遍历。
如果某次遍历中,没有调整任何两个相邻的数的位置关系,说明此时数组已排好序,可以结束程序。

Array.prototype.bubbleSort = function () {
  let i, j;
  for (i = 1; i < this.length; i++) {  //表示本次是第i次遍历
    let changed = false;
    for (j = 0; j < this.length - i; j++) {   //访问序列为arr[0:length-i]
      if(this[j] > this[j + 1]){  //发现前一个数大于后一个时,互换位置
        [this[j],this[j+1]] = [this[j+1],this[j]];
        changed = true;
      }
    }
    if(!changed) {      //如果本轮遍历没有发现位置调整,结束排序函数
      break;
    }
  }
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.bubbleSort();
console.log(arr);

2.选择排序

第i轮遍历arr[0:n-i]选出最大的数,与arr[n-i]互换。

Array.prototype.selectSort = function () {
  let i, j;
  for (i = 1; i < this.length; i++) {     //表示本次是第i次遍历
    let maxIndex = 0;
    for (j = 0; j <= this.length - i; j++) {  //访问子序列为arr[0:this.length-i]
      if (this[j] > this[maxIndex]) {   //当前值大于当前最大值时,记录索引
        maxIndex = j;
      }
    }
    //将子数组最大值索引的值,与子数组末尾的值互换
    [this[this.length - i], this[maxIndex]] = [this[maxIndex], this[this.length - i]]
  }
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.selectSort();
console.log(arr);

3.插入排序
数组的前面部分已经排好序,要把当前数字插入到前面已排好序的数组的相应位置。可能有人会有疑问为什么默认数组前面部分已排好序?是怎么排好序的?是因为当排序开始时,从第2个数字开始进行向前插入,此时当前数字索引为1,当前数字前面仅有一个数字,因此可以认为前面部分已经排好序,将这个数字插入到相应位置之后数组仍然是有序的。每次都将当前数字插入到对应的位置,因此每次插入之后前面的数组仍是排好序的。

Array.prototype.insertSort = function () {
  let i, j;
  for (i = 1; i < this.length; i++) {   //i表示当前要向前插入的数字的索引,从1(即第2个数)开始前插
    let val = this[i];   //记录当前要前插的数的大小
    /*
    * 用指针j来遍历第i个数字前面的,已经排好序的子数组。当j没有指到头,并且j的数字大于要插入的数字时,说明
    * j还要向前遍历,直到发现一个比要插入数字小的位置pos,然后将这个数字插到pos+1处。如果j已经指到头了,
    * 到了-1了还没有找到比当前数字小的位置,就把当前数字放在索引0处。
    * */
    for (j = i - 1; j >= 0 && this[j] > val; j--) {  
      this[j + 1] = this[j];
    }
    this[j + 1] = val;
  }
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.insertSort();
console.log(arr);

4.shell排序
加了step的插入排序。分别以索引数为0,1,......step-1的元素为起点,将其看做不同的组,0、0+step,0+2step,......,0+nstep为一组,1,1+step,1+2step,.....,1+nstep为一组依次分组,按照组为单位进行插入排序。各组都已经插入排序一轮过后,将step除以2向下取整,再进行分组并将各组分别进行插入排序,直到step为0。
step的取值与性能直接相关,需要思考后取值。
并且这里的分组仅仅是逻辑上分组,并没有开辟新的地址空间将其进行物理上的分组。

const {floor} = Math;

//这个和插入排序相同,只不过加了step
Array.prototype.shellInsertSort = function (startIndex, step) {
  let i, j;
  for (i = startIndex + step; i < this.length; i += step) {
    let val = this[i];
    for (j = i - step; j >= 0 && this[j] > val; j -= step) {
      this[j + step] = this[j];
    }
    this[j + step] = val;
  }
};

Array.prototype.shellSort = function () {
  let i, step;
  for (step = floor(this.length / 2); step > 0; step = floor(step / 2)) {
    for (i = 0; i < step; i++) {
      this.shellInsertSort(i, step);
    }
  }
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.shellSort(true);
console.log(arr);

5.合并排序

举个例子: 有 43 12 32 29 66 78 31这个数组要用合并排序。
先将相邻两数分为一组进行合并 43|12 32|29 66|78 31
结果为12 43 29 32 66 78 31

再将组的大小乘以二 (12 43|29 32) (66 78|31)
本次合并后结果为 12 29 32 43 31 66 78

再将组的大小乘以二 12 43 29 32 | 66 78 31
合并结果:12 29 31 32 43 66 78

合并的过程中要开辟新的数组arr,建立两个指针i,j分别指向arr1与arr2,此时arr1与arr2都是排好序的,然后每次都将arr1[i]与arr2[j]较小的数加到arr中并将指针后移。最后哪个数组有剩余的数在追加到arr后面。

const {min} = Math;

function merge(arr1, arr2,) {
  let arr = [];
  let i = 0, j = 0;
  while (i < arr1.length && j < arr2.length) {
    arr1[i] < arr2[j] ? arr.push(arr1[i++]) : arr.push(arr2[j++]);
  }
  return i < arr1.length ? arr.concat(arr1.slice(i)) : arr.concat(arr2.slice(j))
}

Array.prototype.mergeSort = function () {
  let groupSize, i, secondPartSize, firstPart, secondPart, totalSize;
  //最初合并时,每组的大小仅为1,然后将组的大小乘以2。
  for (groupSize = 1; groupSize < this.length; groupSize *= 2) {
    for (i = 0; i < this.length; i += 2 * groupSize) {
      //前半段大小一定是groupSize,后半段则不一定
      secondPartSize = min(groupSize, this.length - i - groupSize);
      totalSize = secondPartSize + groupSize;
      //截取前后部分数组,将其排序
      firstPart = this.slice(i, i + groupSize);
      secondPart = this.slice(i + groupSize, i + groupSize + secondPartSize);
      this.splice(i, totalSize, ...merge(firstPart, secondPart));
    }
  }
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.mergeSort();
console.log(arr);

6.自然合并排序

合并排序的分组是死板的没有利用到数组中原本就是顺序的子序列。

如果数组为 43 56 79 12 33 90 66
将其分组为 43 56 79 | 12 33 90 | 66
再将相邻的,原本就是从小到大的顺序的数组进行合并,效果会更好。

function merge(arr1, arr2) {
  let arr = [], i = 0, j = 0;
  while (i < arr1.length && j < arr2.length) {
    arr.push(arr1[i] < arr2[j] ? arr1[i++] : arr2[j++])
  }
  return arr.concat(i < arr1.length ? arr1.slice(i) : arr2.slice(j));
}

function getSortedArrList(arr) {
  //记录下已经原本就是从小到大顺序的子数组
  let sortedArrList = [];
  let childArr = [arr[0]];
  for (let i = 1; i < arr.length; i++) {
    //当前值小于上一个值时,将childArr加入sortedArrList中,创建新的childArr,并加入当前值。
    if (arr[i] < arr[i - 1]) {
      sortedArrList.push(childArr);
      childArr = [arr[i]];
    }
    //否则,将当前值加入到childArr中
    else {
      childArr.push(arr[i]);
    }
  }
  sortedArrList.push(childArr);
  return sortedArrList;
}

Array.prototype.naturalMergeSort = function() {
  let sortedArrList = getSortedArrList(this);  //获取原本从小到大顺序的子数组

  while (sortedArrList.length > 1) {    //当还有两个及以上的数组没合并完成时
    let newSortedArrList = [];
    for (let i = 0; i < sortedArrList.length; i += 2) {
      if (i !== sortedArrList.length - 1) {
        newSortedArrList.push(merge(sortedArrList[i], sortedArrList[i + 1]));
      }
      else {
        newSortedArrList.push(sortedArrList[i]);
      }
    }
    sortedArrList = newSortedArrList;
  }
  this.splice(0,this.length,...sortedArrList[0]);
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.naturalMergeSort();
console.log(arr);

7.基数排序(LSD least significant digit first)
LSD中没有数值之间的比较。建立一个[10][]的二维数组arr。
挑选出要排序数组中最大的数字,计算该数字的位数记为digitNum。将数组中的所有数字填充到digitNum位,位数不够的高位补0。
然后遍历digitNum次,从低位开始。第i次遍历按照将数组中元素的第i位的数值,将元素num放到二维数组相应位置处,如果num第i位数值为n,则执行arr[n].push(num)的操作。每次遍历之后,将arr[0:9]各数组的元素依次取出,并且重新初始化二维数组。直到遍历到最高位为止,再取出的就是已经排好序的。

const {max} = Math;

function initBarrel() {
  let barrel = [];
  for (let i = 0; i < 10; i++) {
    barrel[i] = [];
  }
  return barrel;
}

function radixSort(arr) {
  let barrel = initBarrel();
  let figureNum = max(...arr).toString().length;  //计算最大的数字的位数
  arr = arr.map(num => num.toString().padStart(figureNum, "0"));  //将数字填充到figureNum位
  for (let i = 0; i < figureNum; i++) {
    let index = figureNum - i - 1;  //本次根据第index位来选择放入哪个桶
    arr.forEach(numStr => {         //将填充过的数组放入桶中
      let num = Number(numStr[index]);
      barrel[num].push(numStr);
    });
    arr = barrel.reduce((prevArr, curArr) => prevArr.concat(curArr), []);//汇总barrel中的数
    barrel = initBarrel();    //初始化barrel
  }
  return arr.map(num => Number(num));   //最终转为数字形式
}

Array.prototype.radixSort = function () {
  let arr = radixSort(this);
  this.splice(0, this.length, ...arr);
};

let arr = [1234342, 52165, 75, 1, 356, 575, 765433212, 57994, 3535];
arr.radixSort();
console.log(arr);

8.基数排序(MSD most significant digit first)
从高位开始,依然没有数值之间的比较。
将最初的元素序列按照各元素最高位的数值进行分组,将分组后,组中只有一个元素或者多个相等元素的组拼接到result数组中,而有多个不同元素的组再递归地向下分,取的位次依次减少。

const {max} = Math;

function initBarrel() {
  let barrel = [];
  for (let i = 0; i < 10; i++) {
    barrel[i] = [];
  }
  return barrel;
}

//判断当前桶中是否只有唯一值 有的桶中可能只有一种值,但是有多个重复项
function unique(barrel) {
  return new Set(barrel).size <= 1;
}

Array.prototype.radixSort = function () {
  let result = [];
  let figureNum = max(...this).toString().length;
  this.splice(0, this.length, ...this.map(num => num.toString().padStart(figureNum, "0")));
  radixGroup(this, 0, figureNum, result);
  this.splice(0, this.length, ...result.map(numStr => Number(numStr)));
};

function radixGroup(group, index, figureNum, result) {    //输入的group是一组numStr,index是当前分桶依据第几位数
  if (index < figureNum) {
    let barrel = initBarrel();
    group.forEach(numStr => {
      let idx = Number(numStr[index]);
      barrel[idx].push(numStr);
    });

    barrel.forEach(subBarrel => {
      if(unique(subBarrel)) {
        subBarrel.forEach(num => {
          result.push(num);
        })
      }
      else {
        radixGroup(subBarrel,index+1,figureNum,result);
      }
    })
  }
}
let arr = [1234342, 52165, 75, 1, 356, 575, 765433212, 57994, 3535];
arr.radixSort();
console.log(arr);

9.快速排序

将数组头部的元素pivotNum作为一个基准,通过两个指针指向数组的头部和尾部,经过一次partition以后将pivotNum放在一个位置pivot,pivot前面的数小于pivotNum,后面的数大于pivotNum。
为了防止最坏情况的发生,可以在数组中随机选出一个数来与数组头部元素换位置,来降低具体实例与最坏情况的关联性。

const {floor, random} = Math;

function randomIndex(start, end) {
  return floor(random() * (end - start + 1)) + start;
}

function partition(arr, start, end) {
  let index = randomIndex(start, end);
  [arr[start], arr[index]] = [arr[index], arr[start]];

  let value = arr[start];

  while (start < end) {
    while (start < end && arr[end] > value) end--;
    arr[start] = arr[end];
    while (start < end && arr[start] < value) start++;
    arr[end] = arr[start];
  }

  arr[start] = value;
  return start;
}

function quickSort(arr, start, end) {
  if (start < end) {
    let pivot = partition(arr, start, end);
    quickSort(arr, start, pivot - 1);
    quickSort(arr, pivot + 1, end);
  }
}

Array.prototype.quickSort = function (asc = true) {
  quickSort(this, 0, this.length - 1, asc)
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.quickSort();
console.log(arr);

10.堆排序
将数组看做完全二叉树,因此节点i的左右子节点的索引分别为2i+1与2i+2。通过从根节点开始令小的值下沉,或者从最后的叶节点开始令大的值上浮的方法,将一个数组构造成一个大根堆。再将大根堆的头元素与尾元素换位置,这样就将当前最大值置换到了尾部。然后下次构建大根堆的时候,将刚置换过的尾部元素刨除在外不做为节点。

const {floor, max} = Math;

function getBiggestNodeIndex(...nodes) {
  return nodes.indexOf(max(...nodes));
}

//将arr从0开始,长度为length的子数组构建为堆
function constructHeap(arr, length) {
  let adjusted = true;  //adjusted来标识本次堆是否作出了调整,若未调整说明堆已构建完毕
  while (adjusted) {
    adjusted = false;
    for (let i = 0; i < floor(length / 2); i++) {
      //当只有左节点时
      if (2 * i + 2 === length) {
        //当父节点比左节点小的时候
        if (arr[i] < arr[2 * i + 1]) {
          //互换
          [arr[i], arr[2 * i + 1]] = [arr[2 * i + 1], arr[i]];
          adjusted = true;
        }
      }
      //当同时有左节点和右节点时
      else {
        //判断三个中最大的节点
        let biggestNodeIndex = getBiggestNodeIndex(arr[i], arr[2 * i + 1], arr[2 * i + 2]);
        //若父节点不是最大的,则和最大的交换
        //如果biggestNodeIndex为0,说明自己最大,为1,说明左节点大,为2,说明右节点大
        switch (biggestNodeIndex) {
          case 0:
            break;
          case 1:
            [arr[i], arr[2 * i + 1]] = [arr[2 * i + 1], arr[i]];
            adjusted = true;
            break;
          case 2:
            [arr[i], arr[2 * i + 2]] = [arr[2 * i + 2], arr[i]];
            adjusted = true;
            break;
        }
      }
    }
  }
}

function heepSort(arr) {
  //只将arr从0开始,长度为length的子数组构建成大根堆
  let length = arr.length;
  while (length > 1) {
    constructHeap(arr, length);
    [arr[0], arr[length-- - 1]] = [arr[length - 1], arr[0]];
  }
}

Array.prototype.heepSort = function () {
  heepSort(this);
};

let arr = [43, 21, 10, 5, 9, 15, 32, 57, 35];
arr.heepSort();
console.log(arr);

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93279.html

相关文章

  • 深入浅出 JavaScript Array.prototype.sort 排序算法

    摘要:快速排序是不稳定的排序算法。浏览器的实现不同有什么影响排序算法不稳定有什么影响举个例子某市的机动车牌照拍卖系统,最终中标的规则为按价格进行倒排序相同价格则按照竞标顺位即价格提交时间进行正排序。 本文要解决的问题 1、找出 Array.prototype.sort 使用的什么排序算法 2、用一种直观的方式展示 Array.prototype.sort 的时间复杂度,看看它有多快? 3、...

    itvincent 评论0 收藏0
  • 常用排序算法JavaScript实现

    摘要:代码实现六堆排序算法简介堆排序是指利用堆这种数据结构所设计的一种排序算法。九计数排序算法简介计数排序是一种稳定的排序算法。计数排序不是比较排序,排序的速度快于任何比较排序算法。 赞助我以写出更好的文章,give me a cup of coffee? 2017最新最全前端面试题 1、插入排序 1)算法简介 插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它...

    jerry 评论0 收藏0
  • JavaScript算法 ,Python算法,Go算法,java算法,C#算法系列之插入排序

    摘要:常见的内部排序算法有插入排序希尔排序选择排序冒泡排序归并排序快速排序堆排序基数排序等。插入排序在实现上,通常采用排序即只需用到的额外空间的排序,因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括: showImg(https://segm...

    GeekQiaQia 评论0 收藏0
  • JavaScript算法 ,Python算法,Go算法,java算法,C#算法系列之插入排序

    摘要:常见的内部排序算法有插入排序希尔排序选择排序冒泡排序归并排序快速排序堆排序基数排序等。插入排序在实现上,通常采用排序即只需用到的额外空间的排序,因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括: showImg(https://segm...

    cgspine 评论0 收藏0
  • JavaScript算法 ,Python算法,Go算法,java算法,C#算法系列之插入排序

    摘要:常见的内部排序算法有插入排序希尔排序选择排序冒泡排序归并排序快速排序堆排序基数排序等。插入排序在实现上,通常采用排序即只需用到的额外空间的排序,因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括: showImg(https://segm...

    lufficc 评论0 收藏0
  • 优秀程序员都应该学习 GitHub 上开源数据结构与算法项目

    摘要:强烈推荐上值得前端学习的数据结构与算法项目,包含图的演示过程与视频讲解。该仓库包含了多种基于的算法与数据结构,提供进一步阅读的解释和链接。数据结构和算法必知必会的个代码实现。 showImg(https://segmentfault.com/img/bVbvpYZ); 前言 算法为王。想学好前端,先练好内功,内功不行,就算招式练的再花哨,终究成不了高手;只有内功深厚者,前端之路才会走得...

    cheukyin 评论0 收藏0

发表评论

0条评论

tommego

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<