大厂面试题练习-排序算法汇总


排序算法汇总

1. 冒泡排序

思想

从数组的第一项开始,一次和后面的每一项相比较,如果比较的项大于第一项的值则将两者交换位置,直到数组中所有的数都比较一遍位置,然后开始下一轮循环;

注意的点:

  1. 每一轮完成一轮比较都会归位一个数字,因此只用归位 length-1 个数字那么整个数组的排序就完成了。因此只用循环 length-1 次;
  2. 每一轮比较的时候因为不用和自己比较,只用和数组中剩下的 length-1 项进行比较即可。因此每一轮比较只用比较 length-1 次;
  3. 在每一轮比较中如果从头到尾都没有需要交换位置的操作发生的话,则说明数组已经是排好序的状态了,可以终止循环。因此可以用一个 flag 标志位来记录是否发生过换位操作,可以根据 flag 判断是否退出循环;
代码
Array.prototype.bubble = function bubble() {
  // 外层循环I控制比较的轮数
  let flag = true;
  for (let i = 0; i < this.length - 1; i++) {
    // 里层循环控制每一轮比较的次数J
    for (let j = 0; j < this.length - 1 - i; j++) {
      if (this[j] > this[j + 1]) {
        // 当前项大于后一项,交换位置
        const temp = this[j];
        this[j] = this[j + 1];
        this[j + 1] = temp;
        flag = false;
      }
    }
    if (flag) {
      break;
    }
  }
  return this;
};
let array = [12, 8, 24, 16, 1];
array.bubble();
console.log(array);

2. 选择排序

思想

第一轮先假设第一项是最小的值,然后将最小值与后面的每一项相比较,找出最小的值将其位置与第一项交换;然后进行下一轮循环,假设第二项是最小的值……

注意的点:

  1. 每一轮完成一轮比较都会归位一个数字,因此只用归位 length-1 个数字那么整个数组的排序就完成了。因此只用循环 length-1 次;
代码
function swap(arr, i, j) {
  let temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
  return arr;
}
Array.prototype.select = function select() {
  for (let j = 0; j < this.length - 1; j++) {
    let min = j;
    // 找到比当前项还小的这一项索引
    for (let i = min + 1; i < this.length; i++) {
      if (this[i] < this[min]) {
        min = i;
      }
    }
    // 让最小的项和当前首位交换位置
    swap(this, min, j);
  }
  return this;
};
let array = [12, 8, 24, 16, 1];
array.select();
console.log(array);

3. 插入排序

思想

类似于玩扑克牌拿牌时的情景:
1、首先拿到第一张牌放到第一位;
2、拿下一张牌,跟手中已经拿到的牌从后往前比较,插到比他小的第一张牌后,如果没有则插到第一项;
3、拿第三张牌……

代码
// 这个方法会返回一个新数组,更加耗内存
Array.prototype.insert = function insert() {
  // 1.准备一个新数组,用来存储抓到手里的牌,开始先抓一张牌进来
  let handle = [];
  handle.push(this[0]);

  // 2.从第二项开始依次抓牌,一直到把台面上的牌抓光
  for (let i = 1; i < this.length; i++) {
    // A是新抓的牌
    let A = this[i];
    // 和HANDDLE手里的牌依次比较(从后向前比)
    for (let j = handle.length - 1; j >= 0; j--) {
      // 每一次要比较的手里的牌
      let B = handle[j];
      // 如果当前新牌A比要比较的牌B大了,把A放到B的后面
      if (A > B) {
        handle.splice(j + 1, 0, A);
        break;
      }
      // 已经比到第一项,我们把新牌放到手中最前面即可
      if (j === 0) {
        handle.unshift(A);
      }
    }
  }
  return handle;
};
let ary = [12, 8, 24, 16, 1];
const newAry = ary.insert();
console.log(newAry);

// 此种方法是在原数组上操作,不耗内存
Array.prototype.insert = function insert() {
  for (let i = 1; i < this.length; i++) {
    let temp = this[i];
    let j = i - 1;
    for (; j >= 0; j--) {
      if (temp >= this[j]) break;
      this[j + 1] = this[j];
    }
    this[j + 1] = temp;
  }
  return this;
};
let ary2 = [12, 8, 24, 16, 1];
ary2.insert();
console.log(ary2);

4. 希尔排序

思想
  1. 第一轮循环时 gap 为 length / 2,数组中的第 i 项和第 i+gap 项比较,如果第 i 项>第 i+gap 项的值,则将两者的位置交换;
  2. 第二轮循环 gap 为上一轮 gap 的 1/2,后面操作同上一轮;
  3. 直到 gap 为 0 为止;
代码
function swap(arr, i, j) {
  let temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
  return arr;
}
Array.prototype.shell = function shell() {
  let gap = Math.floor(this.length / 2);
  while (gap >= 1) {
    for (let i = gap; i < this.length; i++) {
      while (i - gap >= 0 && this[i] < this[i - gap]) {
        swap(this, i, i - gap);
        i = i - gap;
      }
    }
    gap = Math.floor(gap / 2);
  }
};
let arr = [58, 23, 67, 36, 40, 46, 35, 28, 20, 10];
arr.shell();
console.log(arr);

5. 快速排序

思想

一般而言快排的思想是:

  1. 找到数组的中间项,在原有的数组中把它移除;
  2. 准备左右两个数组,循环剩下数组中的每一项,比当前项小的放到左边数组中,反之放到右边数组中;
  3. 递归方式让左右两边的数组持续这样处理,一直到左右两边都排好序为止(最后让左边+中间+右边拼接成为最后的结果)
代码
// 实现方式一
function quick(nums) {
  if (nums.length <= 1) {
    return nums;
  }
  let middleIndex = Math.floor(nums.length / 2);
  let middleValue = nums.splice(middleIndex, 1)[0];

  let aryLeft = [],
    aryRight = [];
  for (let i = 0; i < nums.length; i++) {
    let item = nums[i];
    item < middleValue ? aryLeft.push(item) : aryRight.push(item);
  }
  return quick(aryLeft).concat(middleValue, quick(aryRight));
}
// 实现方式二
function quick(nums) {
  if (nums.length < 2) return nums;
  return quickSort(nums, 0, nums.length - 1);
}

function quickSort(nums, left, right) {
  if (left >= right) return;
  let pivotIndex = partition(nums, left, right);
  quickSort(nums, left, pivotIndex - 1);
  quickSort(nums, pivotIndex + 1, right);
  return nums;
}

function partition(nums, left, right) {
  let pivot = right;
  let leftIndex = left;
  for (let i = left; i < right; i++) {
    if (nums[i] < nums[pivot]) {
      [nums[leftIndex], nums[i]] = [nums[i], nums[leftIndex]];
      leftIndex++;
    }
  }
  [nums[leftIndex], nums[pivot]] = [nums[pivot], nums[leftIndex]];
  return leftIndex;
}
let ary = [12, 8, 15, 16, 1, 24];
console.log(quick(ary));

6. 归并排序

思想

基本思想与过程:先递归的分解数列,再合并数列(分治思想的典型应用)

  1. 将一个数组拆成 A、B 两个小组,两个小组继续拆,直到每个小组只有一个元素为止。
  2. 按照拆分过程逐步合并小组,由于各小组初始只有一个元素,可以看做小组内部是有序的,合并小组可以被看做是合并两个有序数组的过程。
  3. 对左右两个小数列重复第二步,直至各区间只有 1 个数。
代码
function Merger(a, b) {
  let n = a && a.length;
  let m = b && b.length;
  let c = [];
  let i = 0,
    j = 0;

  while (i < n && j < m) {
    if (a[i] < b[j]) c.push(a[i++]);
    else c.push(b[j++]);
  }
  while (i < n) c.push(a[i++]);
  while (j < m) c.push(b[j++]);
  return c;
}
function merge_sort(arr) {
  if (arr.length == 1) return arr;

  let mid = Math.floor(arr.length / 2);
  let left = arr.slice(0, mid);
  let right = arr.slice(mid);

  return Merger(merge_sort(left), merge_sort(right)); //合并左右部分
}
let array = [12, 8, 15, 16, 1, 24];
console.log(merge_sort(array));

文章作者: CassielLee
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 CassielLee !
评论
  目录