# JS 冒泡算法

  • 稳定性:a=b,a 前,b 后,a 依然在 b 前面
  • 不稳定性:a=b,a 前,b 后,a 可在前也可在后
排序算法 平均时间复杂度 最好的情况 最坏的情况 空间复杂度 排序方式 稳定性
冒泡排序 O(n2) O(n) O(n2) O(1) In-place 稳定
选择排序 O(n2) O(n2) O(n2) O(1) In-place 不稳定
插入排序 O(n2) O(n) O(n2) O(1) In-place 稳定
希尔排序 O(n log n) O(n log2n) O(n log2n) O(1) In-place 不稳定
归并排序 O(n log n) O(n log n) O(n log n) O(n) Oun-place 稳定
快速排序 O(n log n) O(n log n) O(n2) O(log n) In-place 不稳定
堆排序 O(n log n) O(n log n) O(n log n) O(1) In-place 不稳定
计数排序 O(n+k) O(n+k) O(n+k) O(k) Oun-place 稳定
桶排序 O(n+k) O(n+k) O(n2) O(n+k) Oun-place 稳定
基数排序 O(n x k) O(n x k) O(n x k) O(n+k) Oun-place 稳定

# 冒泡算法

/**
 *  冒泡排序
 * 1. 比较相邻的两个元素,如果前者比后者大
 * 2. 第一轮结束,最后一个是最大值
 * 3. 开始第二轮,最后一个最大的了,可不参与比较,巧用 `arr.length-i-1`
 *
 *
 */

var arr = [12, 12, 15, 445, 451, 12, 123456, 61, 20, 136, 4856, 1, 0];
for (let i = 0; i < arr.length; i++) {
  for (let j = 0; j < arr.length - i - 1; j++) {
    console.log("j=>", i, j);
    if (arr[i] > arr[j]) {
      const temp = arr[i];
      arr[i] = arr[j];
      arr[j] = temp;
    }
  }
}

console.log("arr==>", arr);

# 选择排序

/**
 * 选择排序
 *
 * 1. 在未排序的序列中找到最小(大)的元素,
 * 2. 存放到排序序列的起始位置
 * 3. 再从剩余的未排序元素中继续寻找最小(大)
 * 4. 排到已排序序列的末尾
 *
 * selectionSort: 15.938ms
 *
 */

function selectionSort(data) {
  console.time("selectionSort");
  const len = data.length;
  let minIndex = null;
  let temp = null;
  for (let i = 0; i < len; i++) {
    minIndex = i;
    // 这个技巧让 当前向后,减少搜索范围
    for (let j = i + 1; j < len; j++) {
      if (arr[j] < arr[minIndex]) {
        // 寻找最小值
        minIndex = j; // 将最小的索引保存
      }
    }
    temp = arr[i];
    arr[i] = arr[minIndex];
    arr[minIndex] = temp;
  }
  console.timeEnd("selectionSort");
  return arr;
}

var arr = [12, 12, 15, 445, 451, 12, 123456, 61, 20, 136, 4856, 1, 0];

console.log("===>", selectionSort(arr));

# 插入排序

/**
 * 插入排序
 *
 * 1. 从第一个元素开始,可以认为已被排序
 * 2. 取出下一个元素,在已排序的元素序列中从后往前扫描
 * 3. 如果该元素(已排序)大于新元素,则将该元素移到下一个位置
 * 4. 重复3,直到找到已排序的元素小于或者等于新元素的位置
 * 5. 新元素插入到下一个位置中
 * 6. 重复步骤2
 */

function insertSort(els) {
  // 从第二个元素开始,即i=1
  for (let i = 1; i < els.length; i++) {
    // 后一个小于前一个,当前的值小于前面的
    if (els[i] < els[i - 1]) {
      const currentItem = els[i];
      let j = i - 1;
      els[i] = els[j]; // 把前面的值给当前的
      // 比大小,找到被插入元素所在的位置
      while (j >= 0 && currentItem < els[j]) {
        els[j + 1] = els[j];
        j--;
      }
      els[j + 1] = currentItem;
    }
  }
  return els;
}

const arr = [12, 12, 15, 445, 451, 12, 123456, 61, 20, 136, 4856, 1, 0];

console.log(insertSort(arr));

# 希尔排序

# 归并排序

/**
 * 归并排序,一种稳定的排序方法
 * 1. 已有序的子序列合并
 * 2. 得到完全有序的序列
 * 3. 先让每个子序列有序
 * 4. 在让子序列段间有序
 *
 */

function mergeSort(data) {
  let len = data.length;
  if (len < 2) {
    return data;
  }
  var middle = Math.floor(len / 2);
  var left = data.slice(0, middle);
  var right = data.slice(middle);
  return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right) {
  var result = [];
  console.time("mergeSort=>");
  while (left.length && right.length) {
    if (left[0] <= right[0]) {
      result.push(left.shift());
    } else {
      result.push(right.shift());
    }
  }
  while (left.length) {
    result.push(left.shift());
  }
  while (right.length) {
    result.push(right.shift());
  }
  console.timeEnd("mergeSort=>");
  return result;
}
var arr = [9, 8, 6, 4, 5, 3, 2];

console.log("===>", mergeSort(arr));

# 快速排序

/**
 *  快速排序
 * 对冒泡算法的改进
 * 第一趟:
 *  - 一部分: 比另外部分都要小,递归调用
 *  - 另外一部分:递归调用
 * 在两边都实行快速排序
 *
 */

function fastSort(el) {
  if (el.length <= 1) return el;
  // 向下取整获得中间数
  const halfIndex = Math.floor(el.length / 2);
  const pivot = el.splice(halfIndex, 1)[0];
  const left = [];
  const right = [];
  for (let i = 0; i < el.length; i++) {
    if (el[i] < pivot) {
      left.push(el[i]);
    } else {
      right.push(el[i]);
    }
  }
  console.log("===>", { halfIndex, pivot, left, right });
  return fastSort(left).concat([pivot], fastSort(right));
}

const arr = [12, 12, 15, 445, 451, 12, 123456, 61, 20, 136, 4856, 1, 0];

console.log(fastSort(arr));

# 堆排序

@todo 未实现

# 计数排序

/**
 * 计数排序
 * 1. 该方法使用了一个额外的数组C,这意味着增加了内存空间,
 * 2. 第 i 个元素是待排序数组A中值,等于 i 的元素的个数
 * 3. 然后根据C数组 来将 A中的元素排到正确的位置
 * 4. 只能对整数进行排序
 */
function countingSort(data) {
  console.time("counting=>");
  var len = data.length;
  var B = [];
  var C = [];
  var min = (max = data[0]);

  for (let i = 0; i < len; i++) {
    min = min <= data[i] ? min : data[i];
    max = max >= data[i] ? max : data[i];

    C[data[i]] = C[data[i]] ? C[data[i]] + 1 : 1;
  }

  for (let j = min; j < max; j++) {
    C[j + 1] = (C[j + 1] || 0) + (C[j] || 0);
  }

  for (let k = len - 1; k >= 0; k--) {
    debugger;
    B[C[data[k]] - 1] = data[k];
    C[data[k]]--;
  }
  console.timeEnd("counting=>");
  return B;
}

var arr = [9, 8, 6, 4, 5, 3, 2];

console.log("===>", countingSort(arr));

# 桶排序

# 基数排序

# 二分查找:其他

/**
 * 二分查找方法1:通过非递归的方式,
 * 1. 先找到中间值
 * 2. 通过中间值比较,大的在右,小的放左
 * 3. 再在两边分别寻找中间值,吃持续以上操作
 * (最多Lon2N)
 * 根据,
 */
function binarySearchTwo(data, dest) {
  let low = 0;
  let high = data.length - 1;

  while (low <= high) {
    const mid = Math.floor((high + low) / 2);
    if (data[mid] == dest) {
      return mid;
    }
    if (dest > data[mid]) {
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }
  return undefined;
}

const arr = [1, 3, 5, 7, 9, 10];

console.log(binarySearchTwo(arr, 5)); // 返回在的索引值

# 总结

基数排序 vs 计数排序 vs 桶排序

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

基数排序:根据键值的每位数字来分配桶 计数排序:每个桶只存储单一键值 桶排序:每个桶存储一定范围的数值

# 参考

  • [js 十大排序算法:冒泡排序]https://www.cnblogs.com/ybygb-geng/p/9355425.html