【算法】排序(三)

173 阅读4分钟

快速排序

递归思路——以x为基准,比他小的排到他前面,比他大的排到他后面。

这样x位置就永远定下来了!因为他前面的全是比他小的,后面全是比他大的。那前面那些数字自己在排序,后面的也自己在排序。x就永远不动了!

那左边和右边的数组也像这样选个基准排序。

阮一峰是每次都选择数组中最中间的元素作为基准

//对一个数组进行快排
let quickSort = arr => {
  if (arr.length <= 1) { return arr; }  //如果数组就一个元素,就不用快排了
  let pivotIndex = Math.floor(arr.length / 2);   //把数组中最中间(或中间偏左)的那个元素作为基准
  let pivot = arr.splice(pivotIndex,1)[0];  //从数组中删去基准元素,得到返回值就是基准元素
  let left =[];   //定义一个空数组left
  let right = []; //定义一个空数组right
  for (let i = 0;i < arr.length; i++) {  //遍历除去了基准元素的那个数组,比基准元素小的就放到left,比基准元素大的就放到right
    if (arr[i] < pivot) {left.push(arr[i])} else { right.push(arr[i]) }
  }
  
  //最后返回一个数组,这个数组:对left快排得到的数组+基准元素+对right快排得到的数组
  return quickSort(left).concat(
    [pivot], quickSort(right))
}

归并排序(merge)

我只会做一件事情:把两个排好序的数组合并merge成一个新的排好序的数组。(先别管我是怎么做的)

那数组怎么才算排好序?一个元素组成的数组不就算这个数组排好序了?

把一个数组分为左右两边数组

所以把一个数组一直分左右两个数组,再分左右两个数组,直到变成一个元素组成的数组。好啦,就可以开始合并了。把一个元素的数组和另一个一个元素的数组合并,在和其他合并合并合并完事

//给数组arr排序
let mergeSort = arr => {
  //如果数组中就一个元素就算排好序了,直接返回该数组
  if (arr.length === 1) {return arr;}  
  //数组中有两个及以上元素,那就把这些元素分左右两个数组
  let left = arr.slice(0, Math.floor(arr.length / 2));
  let right = arr.slice(Math.floor(arr.length / 2));
  //再把左右两个数组再分为
  return merge(mergeSort(left), mergeSort(right));
};

如何把两个排好序的数组合并merge成一个新的排好序的数组?

let merge = (a, b) => {
  if (a.length === 0) return b;
  if (b.length === 0) return a;
  return a[0] > b[0]
    ? [b[0]].concat(merge(a, b.slice(1)))
    : [a[0]].concat(merge(a.slice(1), b));
};

计数排序

  • 只需遍历一次数组生成一个哈希表,让数组中的元素成为key,元素出现的次数成为value。(这叫做用空间换时间)
  • 还要记下数组中的最大值
  • 最后从0到最大值,看看哈希表里面有没有这个数字对应的key。有就推到新数组里。
let countSort = arr => {
  let hashTable = {}, max = 0, result = [];
  //遍历数组,生成一个哈希表
  for (let i = 0; i < arr.length; i++) {
    if (!(arr[i] in hashTable)) {
      hashTable[arr[i]] = 1;
    } else {
      hashTable[arr[i]] += 1;
    }
    if (arr[i] > max) {
      max = arr[i];
    }
  }
  //遍历0到最大数,看哈希表里面有没有这个key
  for (let j = 0; j <= max; j++) {
    if (j in hashTable) {
      for (let i = 0; i < hashTable[j]; i++) {
        result.push(j);
      }
    }
  }
  return result;
};

时间复杂度对比

  • 选择排序O(n^2)
  • 快速排序O(n log2 n)
  • 归并排序O(n log2 n)
  • 计数排序O(n + max) 时间快但同时内存也多了

冒泡排序

  • 请看生动的例子
  • 每次都从第一个数开始看,如果第一个数比后面的大就换位置,没后面的大就后面的开始和后面的这样比。
  • 每次都从第一个开始。相邻的比较,大的在前就前后交换位置。
	bubbleSortSoul1 = (arr = []) => {
		let count = 0;
		// i为轮数(因i从0开始 即i<arr.length-1)
		for (let i = 0; i < arr.length - 1; i++) {
			count++;
			// 第i轮仅需比较length-1-i次
			for (let j = 0; j < arr.length - 1 - i; j++) {
				// 这里能不能写成arr[j-1]>arr[j]? 如果有这种特殊癖好 那么j就从1开始吧,然后j<arr.length-i
				if (arr[j] > arr[j + 1]) {
					let temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
		}
		console.log(`bubbleSortSoul1排序完成用了${count}轮`);
		return arr;
	}

插入排序

  • 请看生动的例子点INS
  • 跟打牌一样。每次抽到一张牌就把它放到手中已经排好的牌里的正确的位置
  • 从已经排好的牌后面往前看,直到新抽的牌比他小(前面的比他小,后面的比他大)

基数排序

适合多位数