0
点赞
收藏
分享

微信扫一扫

算法梳理之排序

艾晓雪 2022-03-17 阅读 75
  • 1.冒泡排序

1.冒泡排序

  • 我们只要是上过计算机课的,只要是有基础的同学应该都会知道的一种算法,冒泡排序的理念也跟好理解,因为计算机是固定格式的计算,那我们就给一种固定格式的判断进行固定格式的操作,冒泡就是对相邻的元素进行两两比较,顺序相反则进行交换,这样,进行n-1次循环,每一趟会将最小或最大的元素“浮”到顶端,最终达到完全有序
  • 我用比较通俗的话解释一下,其实就是把第一个数跟第二个数对比,谁大的谁放后边,然后第二个跟第三个比,第三个跟第四个比,一个回合比下来,到倒数第二个数跟倒数第一个数比较的时候,就会把这组数里最大的数排到最后一位,第二次循环依旧进行这样的比较,筛选出第二个最大的,且第二次比较就可以剔除最后一位数了,这样比较到n-1次的时候只剩下两个数,大的排第二位之后,第一位自然就是最小的了,这也是n-1次循环的原因,我们来看下代码
const arr = [1, 20, 10, 30, 22, 11, 55, 24, 31, 88, 12, 100, 50];

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

function swap(arr, j){
  let temp = arr[j];
  arr[j] = arr[j+1];
  arr[j+1] = temp;
}
2.选择排序
  • 选择排序的概念:第一个概念就是从第一个数到最后一个数依次固定位置,第二个概念就是尽量减少数据位置的移动,每次就只进行最小值下标的记录,如果当前要固定的值不是最小值,选出未固定的剩余数值中最小的值跟当前要固定的值做交换
  • 通俗来讲就是我从第一个数开始,跟剩下的数做比较,记录下标,如果发现第三个数比当前的要小,那就记下是第几个,然后拿这个记住的数跟后边的数作比较,直到这轮比较结束,我记录了一个最小数的下标,然后把这个下标跟第一个数交换位置,然后第二个,找到了跟第二个交换位置,依次循环,一直到倒数第二个,因为第二个一固定位置无疑最后一个肯定是最大的
  • 选择排序是不稳定的,为啥是不稳定的很多人有疑惑,举个栗子就是说有 2 2 1三个数进行排序第一次比较会把第三个跟第一个交换位置,第二次因为俩数相同所以不叫唤位置,虽然最终结果是1 2 2,但是这个结果实际上跟我们预期的不太相符,我们预期的是第一三个的1在两个2前边,但是并不希望两个2的顺序是颠倒的
function selectionSort(arr) {
	var minIndex, temp;
	for (var i = 0; i < arr.length - 1; i++) {
		minIndex = i;
		for (var j = i + 1; j < arr.length; j++) {
			if (arr[j] < arr[minIndex]) {
				minIndex = j;
			}
		}
		temp = arr[i];
		arr[i] = arr[minIndex];
		arr[minIndex] = temp;
	}
	return arr;
}

3.快速排序

  • 听名字我们就知道,这个排序就突出一个字,快,时间复杂度最低,排序进度最快,基本概念是:选择一个基数,这个基数可以选择数组中的第一个数或者最后一个数,因为我们要递归拆分,所以最终的数组会二分到只有一个数甚至没有,给定两个容器,把比基数大的放在前边的容器,把基数小的放在后边的容器,然后对两个容器进行同样方法的拆分排序
  • 通俗点来讲,就是我把所有的数像二叉树一样的掰开,然后选中两个叉里边的一个基数,继续根据大小掰开,这样一直分下去,那就会产生一个效果,在拆分到最小的时候,比如只剩三个数甚至两个数的时候,就区分出来哪个大哪个小了,然后再递归的拼接起来返回回去,就排好了一个有序的序列

这个我觉得我有必要模拟一下情况,我就不画图了

                         [5,3,7,2,1,6,9,8,4]//原始的数组
            [3,2,1,4]              5             [7,6,9,8]//把第一个数5当作基数,拆成俩数组,前边放小的后边放大的
       [2,1]     3      [4]        5       [6]       7         [9,8]//继续拆,中数依旧放着
    [1]  2  []   3  []   4   []    5   []   6    []  7    [8]    9    [] //继续拆,空数组的判断拆分结束
 [] 1 [] 2  []   3  []   4   []    5   []   6    []  7  [] 8 []  9    [] //当所有拆分值都无法拆分的时候,拆分结束,开始递归

这个图就是表达一个拆分的思想,无限评分然后再收集拼接就是快排的算法

const arr = [5, 3, 7, 2, 1, 6, 9, 8, 4];

function quickSort(arr){
  if(arr.length <= 1){
    return arr;
  }
  let temp = arr[0];
  const left = [];
  const right = [];
  for(var i = 1; i < arr.length; i++){
    if(arr[i] > temp){
      right.push(arr[i]);
    }else{
      left.push(arr[i]);
    }
  }
  return quickSort(left).concat([temp], quickSort(right));
}

console.log(quickSort(arr));

4.插入排序

  • 插入排序的概念就是:从第一位开始确认顺序,依次往后,确认位置的方法就是向前查找是否有比自己小的数,有的话就插在这个数的后边,
  • 通俗的讲就是我从第二个开始看第一个比不比我小,比我小插第二个后边,比我大插第一个前边,第三个的时候跟当前的第二个比,如果第二个比我小我就插在第二个的后边,因为前边的大小顺序已经确定了,所以比第二个大一定比第一个大,如果比第二个小,先不急插入,继续向前找看比不比第一个小,如果比第一个小插第一个的前边,后续依旧按照这个逻辑插入
function Insertion(arr) {
  let len = arr.length;
  let preIndex, current;
  for (let i = 1; i < len; i++) {
    preIndex = i - 1;
    current = arr[i];
    while (preIndex >= 0 && current < arr[preIndex]) {
      arr[preIndex + 1] = arr[preIndex];
      preIndex--;
    }
    arr[preIndex + 1] = current;
  }
  return arr;
}
 
 
var arr = [3,5,7,1,4,56,12,78,25,0,9,8,42,37];
Insertion(arr);

5.二路归并

  • 二路归并是处理两个排好序的数组进行合并的,其实我们在前端更多用的方法是先合并再排序,其实归并排序比先合并再排序复杂度要低一些,算法方面耗时也更少一些,因为归并排序的概念是挨个比较两个数组的最小值,谁小的谁先进新的数组容器,最终就会是一个合并好的排好序的数组
  • 这里我没讲传统意义的归并排序,是我觉得这个归并排序实在是不好,要进行完全拆分然后再两两二路归并,复杂度很高很不友好
function merge(left, right) {
    let arr = [];
    while(left.length > 0 && right.length > 0){
        if(left[0] > right[0]) {
            arr.push(right.shift())
        }else{
            arr.push(left.shift())
        }
    }
    return arr.concat(left, right);
}
举报

相关推荐

0 条评论