js的十种排序算法

十大算法解析

  • n: 数据规模
  • k:“桶”的个数
  • In-place: 占用常数内存,不占用额外内存
  • Out-place: 占用额外内存
  • 稳定性:排序后2个相等键值的顺序和排序之前它们的顺序相同

O(1), O(n), O(logn), O(nlogn) 的区别

1.冒泡排序

原理:
对一个数组里的前一个数和后一个数进行比较,把两者之间最大的数放在后面,循环一轮之后最大的就在末尾了。然后同理在排序剩下的n-1 ,n-2 …个数

冒泡排序动画演示

为什么会有两个for循环:之所以在最外层嵌套一个for循环是因为如果仅仅只有一个for循环,那么两两比较之后,只会让最大的值在数组末尾(也就是只执行了一轮),前面的数字依旧不是有序的。第一个for循环用于控制他的轮数,第二个控制比较的次数。
第二个for循环里面为什么要-i:因为i表示的是已经排序好了的最大数的长度(从右数第i个数都是已经排序好了的),不勇再参与排序,为了更简洁快速,所以进行-i操作。
第一个循环为什么要-1:不需要同自己比较,所需轮数只要数组长度-1就可以了。
第二个循环为什么要 - 1 - i:因为比较的次数首先要排除同自身比较,所以-1,其次循环了i轮,那么就有i个元素已经排序好处于数组末尾,也应省略。

function bubbleSort(arr) {
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        //数组元素位置替换
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
      }
    }
  }
  return arr;
}

//冒泡排序即使使用一个已经排序好的数组也会将流程都执行一遍,所以可以添加一个标识,若是已经排序好的,就提前停止

function bubbleSort(arr) {
  let isOk = true
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        //数组元素位置替换
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
        isOk = false
      }
    }
  if(isOk) break
  }
  return arr;
}
2.选择排序

原理:

  • 外层遍历数组,拿到下标保存
  • 内层再次遍历数组,将每个元素与外层保存的下标对应元素进行比较,将较小的值的下标进行保存
  • 内层遍历结束找到了最小值的下标,与外层保存的下标对应的值交换位置

选择排序动画演示

为什么有 minIndex = i:当判断arr[j] < arr[minIndex]如果不成立时,第二个的循环完成之后执行元素替换工作时,minIndex就会因不存在而报错。
为什么要len - 1:因为在第二个循环里最后一项是会跟所有项进行比较的,所以第一个循环的数组的最后一个元素已经比较过,不用重复比较。
第二个循环为什么要i + 1:因为经过元素替换后,i及i之前的下标的数组是已经排序好的最小元素,不用在参与比较。

function selectionSort(arr) {
  var len = arr.length;
//最小值的下标
  var minIndex;
  for (var i = 0; i < len - 1; i++) {
    minIndex = i;
    for (var j = i + 1; j < len; j++) {
      if (arr[j] < arr[minIndex]) {
        //寻找最小的数
        minIndex = j; //将最小数的索引保存
      }
    }
    [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
  }
  return arr;
}
3.插入排序

原理:
遍历数组,从后向前进行比较,符合条件交换位置。

插入排序动画演示

第一个循环为什么不从0开始:因为是从后向前进行比较,下标为0的元素前面没有值,所以从0开始无意义。
第二个循环为什么进行--操作:因为是从后向前进行比较

function insertSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    for (let j = i; j > 0; j--) {
      if (arr[j] < arr[j - 1]) {
        [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
      }
    }
  }
  return arr;
}
4.快速排序

原理

  • 先从数列中取出一个数作为“基准”。
  • 分区过程:将比这个“基准”大的数全放到“基准”的右边,小于或等于“基准”的数全放到“基准”的左边。
  • 再对左右区间重复第二步,直到各区间只有一个数。

quickSort(left).concat([middleItem], quickSort(right)):这里是利用递归调用函数进行不断的分段排序,最后使用concat方法合并。注意这里的合并顺序必须是先左分段数组,在基准值,最后在右边的数组。
arr.splice(midINdex, 1)[0]:使用splice取到中间基值,此方法修改掉了原数组。因为每次最后的拼接都会将中间基值拼接起来,如果不删除基值会造成拼接重复。

function quickSort(arr) {
    if (arr.length <= 1) {
	return arr;
    }
  let len = arr.length;
  let midINdex = Math.floor(len / 2);
  let midItem = arr.splice(midINdex, 1)[0];
  let left = [];
  let right = [];
  for (let i = 0; i < len - 1; i++) {
  if (arr[i] < midItem) {
	left.push(arr[i]);
	  } else {
	right.push(arr[i]);
	}
  }
  return quickSort(left).concat([midItem], quickSort(right));
  }
5.归并排序

原理
归并排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

归并排序动图演示

result.concat(leftArr).concat(rightArr):之所以最后要进行拼接操作,是因为有可能两边数组比较后,一边还有剩余(如一边的值特小,一边特大),所以要进行拼接。

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

function merge(leftArr, rightArr) {
  var result = [];
  while (leftArr.length > 0 && rightArr.length > 0) {
    if (leftArr[0] < rightArr[0]) result.push(leftArr.shift());
    else result.push(rightArr.shift());
  }
  return result.concat(leftArr).concat(rightArr); 
}
6.计数排序

原理
理由数组下标天然排序的原理,以数组元素值为键,出现次数为值存进一个临时数组,最后再遍历这个临时数组还原回原数组。

function countSort(arr) {
  //克隆原数组
  let cloneArr = Array.from(arr)
  // 定义一个空数组
  let arr2 = []; 
  // 找到这个数组的 最大 最小值
  let max = Math.max(...cloneArr); 
  let min = Math.min(...cloneArr);
  // 定义一个索引 这个索引是 后面用来改变原数组的

  let index = 0; 
  // 装桶操作
  for (let i = 0, len = cloneArr.length; i < len; i++) {
    // 把传入数组的值作为下标  装入一个长度为数组最大值长度的临时数组
    let temp = cloneArr[i];
    arr2[temp] = 1;
  }
  // 还原原数组
  for (let i = min; i <= max; i++) {
    // 如果arr2[i] > 0表示他的下标是从原数组获取的,需要进行还原操作
     if(arr2[i] > 0) {
       // 就把原数组的第index位赋值为 i(i本身就是从数组里获取的下标)
      cloneArr[index] = i;
       //赋值完成后进行+1操作,等待给原数组下一位赋值
      index ++
      // 每赋值完一次后 临时数组当前位的值就--,原本值为1,--就为0了,如果=0 就说明这位上没有值了,则不会进行赋值操作
      arr2[i]--; 
    }
  }
  return cloneArr;
}
7.桶排序
//插入排序方法(在各个桶里调用)
function insertSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    for (let j = i; j > 0; j--) {
      if (arr[j] < arr[j - 1]) {
        [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]];
      }
    }
  }
  return arr;
}
//桶排序
function bucketSort(arr, bucketCount) {
  let result = [];
  // 找出最大值和最小值,为给每个桶分配大小做准备
  let minValue = Math.min(...arr);
  let maxValue = Math.max(...arr);

  // 求得每个桶的大小
  bucketSize = Math.ceil(maxValue / bucketCount);
  //创建桶
  bucket = new Array(bucketCount);
  for (let i = 0; i < bucketCount; i++) {
    bucket[i] = [];
  }
  // 往桶里放数据
  for (let i = 0; i < arr.length; i++) {
    bucket[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
  }
  // 对每个桶进行单独排序,放进结果数组中
  for (let i = 0; i < bucketCount; i++) {
    let data = insertSort(bucket[i])
    let len = data.length
    if(len > 0) {
      data.forEach(item => {
        result.push(item)
      });
    }
  }
  return result;
}
8.希尔排序(未理解)
function shellSort(arr) {
  let len = arr.length;
  // gap 即为增量
  for (let gap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) {
    console.log("gap", gap)
    for (let i = gap; i < len; i++) {
      console.log('i', i)
      let j = i;
      let current = arr[i];
      while(j - gap >= 0 && current < arr[j - gap]) {
        arr[j] = arr[j - gap];
        j = j - gap;
      }
      arr[j] = current;
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript中,冒泡排序是一种常见的排序算法。虽然冒泡排序的基本思想是相同的,但是可以通过一些优化来改进算法的性能和效率。下面我将介绍几种常见的改进型冒泡排序算法: 1. 基本冒泡排序算法:基本的冒泡排序算法通过比较相邻的两个元素,并根据需要交换它们的位置来排序数组。具体代码如下所示: 2. 改进的冒泡排序算法:在基本冒泡排序算法的基础上,我们可以添加一个标志位来标记是否发生了交换。如果在一趟排序中没有发生任何交换,说明数组已经有序,可以提前结束排序。这种改进可以减少比较次数,提高排序效率。 3. 鸡尾酒排序算法:鸡尾酒排序算法是对基本冒泡排序算法的一个改进。它从数组的两端开始同时进行排序,一端是最小的元素,另一端是最大的元素。每趟排序交替进行,先从左到右排序,再从右到左排序。这样可以减少排序的次数。 4. 递归冒泡排序算法:递归冒泡排序算法是一种通过递归实现的冒泡排序算法。它通过递归将数组分割为较小的部分,并分别对这些部分进行排序。然后再合并这些部分,最终得到排序好的数组。 5. 鸽巢排序算法:鸽巢排序算法是一种类似于计数排序的算法。它通过统计每个元素出现的次数,并根据次数生成一个新的数组来排序。这种算法适用于一定范围内的整数排序。 这里只列举了几种常见的改进型冒泡排序算法,实际上还有其他一些改进方法。根据具体的需求和场景,可以选择适合的排序算法来进行排序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值