演算法基礎

JuoJuo發表於2019-02-22

插入排序

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', '是個傻逼')
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 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];
    }

    let r = [];
    r = r.concat(find_subsets(S, decisions.concat(S[i]), i+1));
    r = r.concat(find_subsets(S, decisions, i+1));
    return r;
}
console.log(find_subsets('abc'));
空間優化版本

function * subnets(S){
    // 子集有2的N次方個,所以遍歷2的N次方次
    for (let i = 0; i < 1 << S.length; i++) {
        let s = [];
        for (let k = 0; k < S.length; k++) {
           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);
        b.push(permutation(othersStr, select.concat(str[i])))
    }
    return r.concat(...b)
}

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));


複製程式碼

相關文章