算法基础

309 阅读9分钟

插入排序

function insert_sort(A) {
  // 每次循环,0-i都是有序的
  for (let j = 1; j < A.length; j++) {
    const key = A[j];
    let t = j - 1;
    // 此处有优化,没必要每次都交换两个变量的位置,key最后填上坑就行
    while (t >= 0 && A[t] > key) {
      A[t + 1] = A[t];
      t = t - 1
    }
    A[t + 1] = key;
  }
}

选择排序

/********************************************************************************************/
function select_sort(A) {
  const exchange = (arr, j, min) => {
    const temp = arr[j];
    arr[j] = arr[min];
    arr[min] = temp;
  }

  for (let j = 0; j < A.length; j++) {
    let min = j;
    for (let i = j + 1; i < A.length; i++) {
      if (A[min] > A[i]) min = i;
    }
    exchange(A, j, min);
  }
}

快速排序

/**************************************快速排序*************************************************/
function swap(A, i, j) {
  [A[i], A[j]] = [A[j], A[i]];
}

function partition(A, low, high) {
  const center = A[high - 1];
  let i = low; j = high - 1;

  while (i !== j) {
    if (A[i] <= center) {
      i++;
    } else {
      swap(A, i, --j);
    }
  }
  swap(A, j, high - 1);
  return j;
}
function qsort(A, low = 0, high = A.length) {
  if (high - low < 2) return;
  const p = partition(A, low, high);
  qsort(A, low, p);
  qsort(A, p + 1, high);
}
let A = [3, 5, 7, 13, 22, 25, 4, 6, 8, 14, 23, 18];
qsort(A, 0, A.length);
console.log(A);

快速排序习题

<!--这是求前6(N)个数的修改模块-->
function qsort(A, low = 0, high = A.length) {
  if (high - low < 2) return;
  const p = partition(A, low, high);
  if(p + 1 > 6){
    return qsort(A, low, p);
  }else if(p + 1 < 4){
    return qsort(A, p + 1, high);
  }else{
    return A.slice(0, 6);
  }
}

归并排序

/**********************************归并*********************************************/
function merge(A, p, q, r) {
  const A1 = A.slice(p, q);
  const A2 = A.slice(q, r);

  A1.push(Number.MAX_SAFE_INTEGER);
  A2.push(Number.MAX_SAFE_INTEGER);

  let j = 0, k = 0;
  // 这个地方台坑了,i=p  i< r
  for (let i = p; i < r; i++) {
    A[i] = A1[j] < A2[k] ? A1[j++] : A2[k++];
  }
}

function merge_sort(A, p, r) {
  if (r - p < 2) return;
  const q = Math.ceil((p + r) / 2);
  merge_sort(A, p, q);
  merge_sort(A, q, r);
  merge(A, p, q, r);
}
let A = [3, 5, 7, 13, 22, 25];
merge_sort(A, 0, A.length);
console.log(A);

非递归版本排序

/* *******************************非递归版本******************************************* */
function merge_sort2(A) {
  for (let i = 1; i < A.length; i += i) {
    const step = i * 2;
    for (let start = 0; start < A.length; start += step) {
      const end = Math.min(start + step, A.length);
      if (end - start > 1) {
        const mid = start + i;
        merge(A, start, mid, end);
      }
    }
  }
}

非递归版本平衡优化

/* *******************************平衡优化******************************************* */
const L = 16;
function merge_sort3(A) {
  // 计算一个scale系数
  const p2 = 2 ** Math.floor(Math.log2(A.length));
  const scale = A.length / p2;

  // 优化2 在比较小的一个区间使用插入排序先对其排一下
  for (let i = 0; i < p2; i += L) {
    start = Math.floor(i * scale);
    end = Math.floor(start + L * scale);
    insertion_sort(A, start, end);
  }

  for (let i = 1; i < p2; i += i) {
    for (let m = 0; m < p2; m += i * 2) {
      const start = Math.floor(m * scale);
      const mid = Math.floor((m + i) * scale);
      const end = Math.floor((m + i * 2) * scale);
      if (A[end - 1] < A[start]) {
        // 优化1  如果两组数组,end比start小,直接整体挪位置就行了
        rotate(A, mid - start, start, end);
      } else {
        merge(A, start, mid, end);
      }
    }
  }
}


二分查找

/* *******************************二分查找******************************************* */

function bsearch(A, x) {
  let l = 0, r = A.length - 1, guess;

  while (l <= r) {
    guess = Math.floor((l + r) / 2);
    if (x === A[guess]) return guess;
    else if (x < A[guess]) {
      r = guess + 1;
    } else {
      l = guess + 1;
    }
  }
  return 'not found'
}

// digui
function bsearch2(A, l, r, x) {
  if (l > r) return 'not found';

  guess = Math.floor((l + r) / 2);

  if (x === A[guess]) {
    return guess;
  } else if (x < A[guess]) {
    return bsearch(A, l, guess + 1, x);
  } else {
    return bsearch(A, guess + 1, r, x);
  }
}

计数排序

/* ************************计数排序********************* */
  function counting_sort(A) {
    const max = Math.max(...A);
    const B = Array(max + 1).fill(0);
    const C = Array(A.length);
    A.forEach((_, i) => B[A[i]]++);
    for (let i = 1; i < B.length; i++) {
      B[i] = B[i - 1] + B[i];
    }
    for (let i = 0; i < A.length; i++) {
      const p = B[A[i]] - 1;
      B[A[i]]--;
      C[p] = A[i];
    }
    return C;
  }
  let A = [3, 5, 7, 13, 22, 25, 4, 6, 8, 14, 23, 18, 3];
  let c1 = counting_sort(A);
  console.log(c1);

最大子数组

/* ************************最大子数组********************* */
function find_cross(A, low, mid, high) {
  let leftSum = Number.MIN_SAFE_INTEGER;
  let sum = 0, maxLeft, maxRight;
  for (let i = mid; i >= low; i--) {
    sum += A[i];
    if (sum > leftSum) {
      leftSum = sum;
      maxLeft = i;
    }
  }

  let rightSum = Number.MIN_SAFE_INTEGER;
  let sum2 = 0;
  for (let i = mid + 1; i < high; i++) {
    sum2 += A[i];
    if (sum2 > rightSum) {
      rightSum = sum2;
      maxRight = i;
    }
  }
  return { maxLeft, maxRight, crossSum: maxLeft === maxRight ? A[maxLeft] : rightSum + leftSum }
}

function find_max_sub_array(A, low, high) {
  if (high - low < 2) {
    return {
      low,
      high,
      v: A[low]
    }
  } else {
    let mid = Math.floor((low + high) / 2);

    let { low: leftLow, high: leftHigh, v: leftSum } = find_max_sub_array(A, low, mid);
    let { low: rightLow, high: rightHigh, v: rightSum } = find_max_sub_array(A, mid, high);
    let { maxLeft, maxRight, crossSum } = find_cross(A, low, mid, high);

    if (leftSum >= rightSum && leftSum >= crossSum) {
      return { low: leftLow, high: leftHigh, v: leftSum }
    } else if (rightSum >= leftSum && rightSum >= crossSum) {
      return { low: rightLow, high: rightHigh, v: rightSum }
    } else {
      return { low: maxLeft, high: maxRight, v: crossSum }
    }
  }
}


桶排序

<!--桶排序-->
function insert_sort(A) {
  // 每次循环,0-i都是有序的
  for (let j = 1; j < A.length; j++) {
    const key = A[j];
    let t = j - 1;
    // 此处有优化,没必要每次都交换两个变量的位置,key最后填上坑就行
    while (t >= 0 && A[t] > key) {
      A[t + 1] = A[t];
      t = t - 1
    }
    A[t + 1] = key;
  }
}
function bucket_sort(A, k, S) {
  const buckets = Array.from({ length: k }, () => []);
  // 放入桶中
  for (let i = 0; i < A.length; i++) {
    const index = ~~(A[i] / S);
    buckets[index].push(A[i]);
  }
  // 排序每只桶
  for (let i = 0; i < buckets.length; i++) {
    insert_sort(buckets[i]);
  }
  // 取出数据
  return [].concat(...buckets);
}
const A = [29, 25, 3, 49, 9, 37, 21, 43];
console.log(bucket_sort(A, 5, 10));

基数排序

<!--基数排序-->
function radix_sort(A) {
  const max = Math.max(...A);
  const buckets = Array.from({ length: 10 }, () => []);
  let m = 1;
  while (m < max) {
    A.forEach(number => {
      const digit = ~~((number % (m * 10)) / m);
      buckets[digit].push(number);
    });
    let j = 0;
    buckets.forEach(bucket => {
      while (bucket.length > 0) {
        A[j++] = bucket.shift();
      }
    });
    m *= 10;
  }
}
const A = [10, 200, 13, 12, 7, 88, 91, 24];
radix_sort(A)
console.log(A);

function sort(A) {
  let buckets = Array.from({ length: 27 }, () => []);
  let j = 7;
  while (j > -1) {
    A.forEach(item => item[j] ? buckets[item[j].charCodeAt() - 96].push(item) : buckets[0].push(item));
    A = [].concat(...buckets);
    buckets = Array.from({ length: 27 }, () => []);
    j -= 1;
  }
  return A;
}
let A = ['xyz', 'okr', 'oop', 'ofo', 'abc', 'bu', 'nlju', 'ab'];
console.log(sort(A));

求前N大的数字, 1.基于比较的排序,先排序。 nlogn 2.快速排序 on 3.先建堆取N次。

单项链表

双向链表

大根堆

<!--大根堆-->
// 二叉树  性质
// 一个节点的左索引 left = index*2 + 1
// 一个节点的右索引 right = index*2 + 2
// 如果索引 >= floor(arr.length/2) ---> 是叶子节点
// 反过来除以2,floor可以拿到父亲

// 堆  二叉树的一种  最大堆  or  最小堆
// 构建最大堆

class Heap {

  constructor(arr) {
    this.data = [...arr];
    this.size = this.data.length;
  }

  /**
   * 所有节点都不满足堆的性质
   *       1
   *     2   3
   *   4  5
   */
  rebuildHeap() {
    const L = Math.floor(this.size / 2);
    for (let i = L - 1; i >= 0; i--) {
      this.maxHeadpify(i);
    }
  }

  ifHeap() {
    const L = Math.floor(this.size / 2);

    for (let i = 0; i < L; i++) {
      const l = this.data[left(i)] || Number.MIN_SAFE_INTEGER;
      const r = this.data[right(i)] || Number.MIN_SAFE_INTEGER;
      const max = Math.max(this.data[i], l, r);

      if (max !== this.data[i]) {
        return false;
      }
    }
    return true;
  }


  /**
   * 建堆后,能能确定的就是顶上的那个是最大的,左右不确定,
   * 我们就取顶上的那个,然后把最后一个元素放顶上,
   * 由于之前是贱好堆的,符合其他地方都满足堆的性质,就直接其他地方都满足堆的性质
   */
  sort() {
    for (let i = this.size - 1; i > 0; i--) {
      swap(this.data, 0, this.size - 1);
      this.size--;
      this.maxHeadpify(0);
    }
  }

  /**
   * 假设堆的其他地方都满足堆的性质
   * 唯独根节点(三个哈),重构堆性质
   */
  maxHeadpify(i) {
    let max = i;

    if (i >= this.size) {
      return;
    }

    const leftIndex = left(i);
    const rightIndex = right(i);

    if (leftIndex < this.size && this.data[leftIndex] > this.data[max]) {
      max = leftIndex;
    }

    if (rightIndex < this.size && this.data[rightIndex] > this.data[max]) {
      max = rightIndex;
    }

    if (max === i) return;

    swap(this.data, i, max);

    this.maxHeadpify(max);

  }

}
function left(i) { return i * 2 + 1 }
function right(i) { return i * 2 + 2 }
function swap(arr, i, j) {
  const t = arr[i];
  arr[i] = arr[j];
  arr[j] = t;
}

const heap = new Heap([15, 2, 8, 12, 5, 2, 3, 4, 7])
heap.maxHeadpify(1)
console.log(heap.data);

const heap1 = new Heap([1, 2, 3, 4, 5])
heap1.rebuildHeap()
console.log(heap1.data);


const heap2 = new Heap([5, 4, 3, 2, 1]);
heap2.rebuildHeap();
console.log(heap2.data);
heap2.sort();
console.log(heap2.data);

HashTable

<!--hashTable-->
// hash冲突链表版本
// 1.还有开放地址版本,插入时候22  -》  %10  得到2放入数组中索引为2的地方,  再来一个12模10还是2,发现2的位置有了,就看3有没有,没有就放3,有就继续看4
// 2.查找的时候12模后,去2位置对比,看相不相等,不相等就看3的位置
// 3.布隆过滤器,原理,拿着值‘hello’,定义三个hash函数,算出三个hash值,算出三个位置,在然后按位置为1,假如存储空间是4字节32位,
// 查找的时候,拿着三个值,虽然不能判断是否存在某个元素,但是如果一旦发现某个位是0,就能够过滤某个元素肯定不存在的情况,作为数据处理的第一次处理,减小数据量
class HashTable {

  constructor(num = 1000) {
    this.M = num;
    this.slots = new Array(num);
  }

  hash(str) {
    return [...str].reduce((hash, c) => {
      hash = (331 * hash + c.charCodeAt(0)) % this.M;
      return hash;
    }, 1);
  }

  add(key, value) {
    const hashCode = this.hash(key);
    if(!this.slots[hashCode]){
      this.slots[hashCode] = [];
    }
    this.slots[hashCode].unshift({ key, value });
  }

  delete(key){
    const hashCode = this.hash(key);
    this.slots[hashCode] = this.slots[hashCode].filter(item => item.key !== key);
  }

  search(key){
    const hashCode = this.hash(key);
    const target = this.slots[hashCode].find((item) => item.key === key);
    return target? target.value: null;
  }
}

// const handler = {
//   get function(target) {

//   },
//   set function(target) {

//   }
// }
// const mp = new Proxy(map, handler)



const map = new HashTable();
map.add('jack', '是个xx')
console.log(map.search('jack'));

经典组合问题


经典组合问题
function combination(S, k) {
  if (k === 0 || S.length === k) {
    return [S.slice(0, k)];
  }

  const [first, ...others] = S;
  let r = [];

  const A2 = combination(others, k - 1).map(c => [first, ...c]);

  const A3 = combination(others, k);

  r = r.concat(A2);
  r = r.concat(A3);
  return r;
}

const S = ['a', 'b,', 'c', 'd'];
console.log(combination(S, 2));

//解法二
function combination(s, k, decisions=[], result=[]) {
  if (k === 0) {
    return result.push(decisions.join(''));
  }

  for(let i=0; i<s.length; i++) {
    //如果ab跟ba算两种的话就用这一行
    //const left = s.slice(0, i).concat(s.slice(i+1));
    const left = s.slice(i+1);
    combination(left, k-1, decisions.concat(s[i]), result);
  }

  return result;
}
const S = ['a', 'b', 'c', 'd'];
console.log(combination(S, 2));

//语言特性干掉结果收集
function *combination(s, k, decisions=[]) {
  if (k === 0) {
    yield decisions.join('');
    return;
  }

  for(let i=0; i<s.length; i++) {
    //如果ab跟ba算两种的话就用这一行
    //const left = s.slice(0, i).concat(s.slice(i+1));
    const left = s.slice(i+1);

    yield *combination(left, k-1, decisions.concat(s[i]));
  }
}
const S = ['a', 'b', 'c', 'd'];
console.log(...combination(S, 2));

子集问题 遍历决策树

function find_subsets(S, decisions=[]) {
    // 所有决策已经完成
    if (S.length === decisions.length) {
        // 返回递归结果
        return [decisions];
    }

    let r = [];
    r = r.concat(find_subsets(S, decisions.concat(true)));
    r = r.concat(find_subsets(S, decisions.concat(false)));
    return r;
}
//自己写出来了
function find_subsets(S, decisions = [], i=0) {
  // 所有决策已经完成
  if (S.length === i) {
      // 返回递归结果
      return [decisions.join('')];
  }

  const r1 = find_subsets(S, decisions.concat(S[i]), i+1);
  const r2 = find_subsets(S, decisions, i+1);
  
  return [].concat(r1, r2);
}
console.log(find_subsets('abc'));

//空间优化版本
function * subnets(S){
    // 子集有2的N次方个,所以遍历2的N次方次, 000 - 111
    for (let i = 0; i < 1 << S.length; i++) {
        let s = [];
        for (let k = 0; k < S.length; k++) {
          // 001 010 100 看i的哪一位是1,是1就push进那个字母,完了就join
           const take = i & (1<<k);
           take && s.push(S[k]);
        }
        yield s.join('');
    }
}
const S = ['a', 'b', 'c'];
console.log([...subnets(S)])

全排列问题 遍历决策树

function permutation(str, select = []) {
    if (str.length === 0) {
        return select.join('')
    }
    let r = []
    let b = []
    for (let i = 0; i < str.length; i++) {
        const prev = str.slice(0, i);
        const next = str.slice(i + 1);
        const othersStr = prev.concat(next);
        const res = permutation(othersStr, select.concat(str[i]));
        b.push(res)
        //最好的是使用concat降维
        // b = b.concat(res);
        // 末尾就可以直接return b;r就可以不要了
    }
    
    //因为b是个数组['acb'],每次都往数组里push数组[['acb'],['bca']]不好降下维
    return r.concat(...b)
}
console.log(permutation('abc'))

// 变下写法收集结果的写法
function permutation(str, select = [], result = []) {
  if (str.length === 0) {
      return result.push(select.join(''))
  }
  for (let i = 0; i < str.length; i++) {
      const prev = str.slice(0, i);
      const next = str.slice(i + 1);
      const othersStr = prev.concat(next);
      permutation(othersStr, select.concat(str[i]), result)
  }

  return result;
}

console.log(permutation('abc'))

if(所有决策都完成){
  返回结果
}
根据当前状态算出所有可能的决策
递归调用这些决策
收集递归的结果,返回

搜索问题

function compatible(p, q, n) {
  const [x1, y1] = [~~(p / n), p % n];
  const [x2, y2] = [~~(q / n), q % n];
  return x1 !== x2 && y1 !== y2 && Math.abs(x1 - x2) !== Math.abs(y1 - y2);
}

// 4*4 转成的一维数组, 里面的值,就等于索引
function queen(n, decisions = []) {
  if (decisions.length === n) {
    return [decisions];
  }
  let r = [];
  const start = decisions[decisions.length - 1] || -1;
  for (let i = start + 1; i < n * n; i++) {//0-16
    // decision里就是选择的一个决策,我们每次都跟最后的一个比一下是不是ok的
    if (decisions.every(j => compatible(j, i, n))) {
      r = r.concat(queen(n, decisions.concat(i)))
    }
  }
  return r;
}

console.log(queen(10));