算法入门

285 阅读4分钟

算法

切勿盲目刷题:刷题前的知识积累

想要提高自己的算法,我觉得就是脚踏实地着多动手去刷题,多刷题。

但是,连常见的数据结构,如链表、树以及常见的算法思想,如递归、枚举、动态规划这些都没学过,那么,我不建议你盲目疯狂着去刷题**的。

先看完算法图解,然后开始看剑指offer(10天),之后复习计算机网络(3天),之后看源码(5天),然后边看资料边面试(12天)。做到了尚有一线生机,做不到容易完蛋,高三模式开启。

算法图解

二分查找

对于包含n个元素的列表,用二分查找最多需要log2n步,而简单查找最多需要n步。

仅当列表是有序的时候,二分查找才管用

javascript使用实现二分法

module.exports.binary_search = (arr, item) => {
  let low = 0; //定义最小坐标
  let height = arr.length - 1; //最大坐标
  let mid; //中间坐标
  let select; //选择项
  while (low <= height) {
    mid = Math.floor(low + height);
    select = arr[mid];
    if (select == item) {
      return mid;
    }
    if (select < item) {
      low = mid + 1;
    }
    if (select > item) {
      height = mid - 1;
    }
  }
  return null;
};

大O表示法

仅知道算法需要多长时间才能运行完毕还不够,还需知道运行时间如何随列表增长而增加。这正是大O表示法的用武之地。指出的是算法需要执行的操作数,指出了最糟糕情况下的运行时间。谈论算法的速度时,我们说的是随着输入的增加,其运行时间将以什么样的速度增加

但是除最糟情况下的运行时间外,还应考虑平均情况的运行时间,这很重要。

选择排序

O(nXn):每次选取剩下的最大的(或最小的)需要的时间为O(n),一共需要执行n次,所以为O(nXn),大O表示法通常省略前面的常数

我们可以将选择排序分为两个阶段,首先是找出数组中的最小数,然后将这个数移到新数组中。

//找出最小的数,及其下标
module.exports.findSmallest = (arr) => {
  let min = arr[0],
    index = 0;
  for (let i = 0, length = arr.length; i < length; i++) {
    if (min > arr[i]) {
      min = arr[i];
      index = i;
    }
  }
  return {
    min,
    index,
  };
};
//实现选择排序
module.exports.selectSort = (arr) => {
  let newArr = [];
  while (arr.length > 0) {
    let res = this.findSmallest(arr);
    newArr.push(res.min);
    arr.splice(res.index, 1);
  }
  return newArr;
};

递归

递归只是让解决方案更清晰,并没有性能上的优势。实际上,在有些情况下,使用循环的性能更好。

基线递归和条件递归

编写递归函数时,必须告诉它何时停止递归。正因为如此,每个递归函数都有两部分:基线条件(base case)和递归条件(recursive case)。递归条件指的是函数调用自己,而基线条件则指的是函数不再调用自己,从而避免形成无限循环。

快速排序

分而治之

D&C算法是递归的。使用D&C解决问题的过程包括两个步骤。(1) 找出基线条件,这种条件必须尽可能简单。(2) 不断将问题分解(或者说缩小规模),直到符合基线条件。

编写涉及数组的递归函数时,基线条件通常是数组为空或只包含一个元素。陷入困境时,请检查基线条件是不是这样的。

//递归求数组的和
module.exports.sum = function sum(arr) {
  let length = arr.length;
  //基线条件
  if (length === 0) {
    return 0;
  }
  if (arr.length === 1) {
    //基线递归
    return arr[0];
  } else {
    let newArr = arr.slice(1, length); //slice不包含end项
    //条件递归
    return arr[0] + sum(newArr);
  }
};

快速排序

归纳证明是一种证明算法行之有效的方式,它分两步:基线条件和归纳条件(通常归纳证明用来证明某种算法是否可行然后使用递归来实现这种算法)

//快排实现
let quickSort = function quickSort(arr) {
  let length = arr.length;
  // 基线条件
  if (length == 0) {
    //空数组不用排序
    return [];
  }
  if (length == 1) {
    //只有一个的数组直接返回不用排序
    return arr[0];
  } else {
    //条件递归
    let smaller = [];
    let bigger = [];
    for (let i = 1; i < length; i++) {
      if (arr[i] < arr[0]) {
        //两个及以上的选择第一个基准
        //小于基准的放在左边的数组
        smaller.push(arr[i]);
      }
      if (arr[i] > arr[0]) {
        //大于基准的放在右边的数组
        bigger.push(arr[i]);
      }
    }
    return arrConcat(quickSort(smaller), arr[0], quickSort(bigger));
  }
};
//数组拼接函数
let arrConcat = function () {
  console.log(arguments);
  let newArr = [];
  let length = arguments.length;
  for (let i = 0; i < length; i++) {
    //如果是数组,直接放进去
    if (typeof arguments[i] === "object" && arguments[i] instanceof Array) {
      for (let j = 0, length = arguments[i].length; j < length; j++) {
        newArr.push(arguments[i][j]);
      }
    } else if (typeof arguments[i] === "number") {
      //如果不是数组,直接放进数组
      newArr.push(arguments[i]);
    }
  }
  return newArr;
};

module.exports = { quickSort };

散列表

快速排序的独特之处在于,其速度取决于选择的基准值