jiangwei小站
1343 字
7 分钟
面试题 算法
2024-01-23

链接

衡量不同算法之间的优劣主要是通过时间和空间两个维度去考量:

时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。
空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述

时间复杂度#

一个算法花费的时间与算法中语句的「执行次数成正比」,执行次数越多,花费的时间就越多

空间复杂度#

空间复杂度主要指执行算法所需内存的大小,用于对程序运行过程中所需要的临时存储空间的度量

冒泡排序#

它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来

function bubbleSort(arr) {
  var len = arr.length;
  for (var i = 0; i < len; i++) {
    for (var j = 0; j < len - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        var temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  return arr;
}

选择排序#

在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好
唯一的好处是不占用额外的内存存储空间

function selectionSort(arr) {
  var len = arr.length;
  for (var i = 0; i < len; i++) {
    var minIndex = i;
    for (var j = i + 1; j < len; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j;
      }
    }
    var temp = arr[minIndex];
    arr[minIndex] = arr[i];
    arr[i] = temp;
  }
  return arr;
}

插入排序#

它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入

function insertionSort(arr) {
  var len = arr.length;
  for (var i = 1; i < len; i++) {
    var j = i - 1;
    var temp = arr[i];
    while (j >= 0 && arr[j] > temp) {
      arr[j + 1] = arr[j];
      j--;
    }
    arr[j + 1] = temp;
  }
  return arr;
}

快速排序#

快速排序是对冒泡排序算法的一种改进,从数列中挑出一个元素,称为”基准”(pivot)
重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序

function quickSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
}

归并排序#

分而治之,二叉树

sort 排序(Arrays.sort 并不是单一的排序,而是插入排序,快速排序,归并排序三种排序的组合)#

const numberArray = [40, 1, 5, 200];
function compareNumbers(a, b) {
  return a - b;
}
numberArray.sort(compareNumbers);

const items = [
  { name: "Edward", value: 21 },
  { name: "Sharpe", value: 37 },
  { name: "And", value: 45 },
  { name: "The", value: -12 },
  { name: "Magnetic", value: 13 },
  { name: "Zeros", value: 37 },
];
items.sort((a, b) => a.value - b.value);

// 使用 map 改善排序
// 需要被排序的数组
const data = ["delta", "alpha", "charlie", "bravo"];

// 用于存放位置和排序值的对象数组
const mapped = data.map((v, i) => {
  return { i, value: someSlowOperation(v) };
});

// 按照多个值排序数组
mapped.sort((a, b) => {
  if (a.value > b.value) {
    return 1;
  }
  if (a.value < b.value) {
    return -1;
  }
  return 0;
});

const result = mapped.map((v) => data[v.i]);

贪心算法#

其期待每一个阶段都是局部最优的选择,从而达到全局最优,但是结果并不一定是最优的

回溯算法#

回溯算法会先从一个可能的工作开始解决问题,如果不行,就回溯并选择另一个动作,知道将问题解决

#

栈(stack)又名堆栈,它是一种运算受限的线性表,限定仅在表尾进行插入和删除操作的线性表

class Stack {
  constructor() {
    this.items = [];
  }

  /**
   * 添加一个(或几个)新元素到栈顶
   * @param {*} element 新元素
   */
  push(element) {
    this.items.push(element);
  }

  /**
   * 移除栈顶的元素,同时返回被移除的元素
   */
  pop() {
    return this.items.pop();
  }

  /**
   * 返回栈顶的元素,不对栈做任何修改(这个方法不会移除栈顶的元素,仅仅返回它)
   */
  peek() {
    return this.items[this.items.length - 1];
  }

  /**
   * 如果栈里没有任何元素就返回true,否则返回false
   */
  isEmpty() {
    return this.items.length === 0;
  }

  /**
   * 移除栈里的所有元素
   */
  clear() {
    this.items = [];
  }

  /**
   * 返回栈里的元素个数。这个方法和数组的length属性很类似
   */
  size() {
    return this.items.length;
  }
}

队列#

跟栈十分相似,队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作

class Queue {
  constructor() {
    this.items = [];
  }

  enqueue(element) {
    this.items.push(element);
  }

  dequeue() {
    if (this.isEmpty()) {
      return "队列为空";
    }
    return this.items.shift();
  }

  front() {
    if (this.isEmpty()) {
      return "队列为空";
    }
    return this.items[0];
  }

  isEmpty() {
    return this.items.length === 0;
  }

  size() {
    return this.items.length;
  }

  clear() {
    this.items = [];
  }
}